diff --git a/CMakeLists.txt b/CMakeLists.txt index 6956fd22c3..c8710eed05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,8 +80,28 @@ endif() if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) + add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\") + if ( + (${HIFI_ANDROID_APP} STREQUAL "questInterface") OR + (${HIFI_ANDROID_APP} STREQUAL "questFramePlayer") OR + (${HIFI_ANDROID_APP} STREQUAL "framePlayer") + ) + # We know the quest hardware has this extension, so we can force the use of instanced stereo + add_definitions(-DHAVE_EXT_clip_cull_distance) + # We can also use multiview stereo techniques + add_definitions(-DHAVE_OVR_multiview2) + add_definitions(-DHAVE_OVR_multiview) + # We can also use our own foveated textures + add_definitions(-DHAVE_QCOM_texture_foveated) + + # if set, the application itself or some library it depends on MUST implement + # `DisplayPluginList getDisplayPlugins()` and `InputPluginList getInputPlugins()` + add_definitions(-DCUSTOM_INPUT_PLUGINS) + add_definitions(-DCUSTOM_DISPLAY_PLUGINS) + set(PLATFORM_PLUGIN_LIBRARIES oculusMobile oculusMobilePlugin) + endif() else () - set(PLATFORM_QT_COMPONENTS WebEngine) + set(PLATFORM_QT_COMPONENTS WebEngine Xml) endif () if (USE_GLES AND (NOT ANDROID)) diff --git a/android/apps/framePlayer/build.gradle b/android/apps/framePlayer/build.gradle index fc8651fce1..5bf8176863 100644 --- a/android/apps/framePlayer/build.gradle +++ b/android/apps/framePlayer/build.gradle @@ -1,15 +1,8 @@ +import com.android.builder.core.BuilderConstants + 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" @@ -32,19 +25,17 @@ android { 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' + + variantFilter { variant -> + def build = variant.buildType.name + if (build == BuilderConstants.RELEASE) { + variant.setIgnore(true) } } - - 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/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java deleted file mode 100644 index aad769de70..0000000000 --- a/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java +++ /dev/null @@ -1,42 +0,0 @@ -// -// InterfaceActivity.java -// gvr-interface/java -// -// Created by Stephen Birarda on 1/26/15. -// Copyright 2015 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 -// - -package io.highfidelity.gvrinterface; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.util.Log; -import org.qtproject.qt5.android.bindings.QtActivity; - -public class InterfaceActivity extends QtActivity { - - public static native void handleHifiURL(String hifiURLString); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - // Get the intent that started this activity in case we have a hifi:// URL to parse - Intent intent = getIntent(); - if (intent.getAction() == Intent.ACTION_VIEW) { - Uri data = intent.getData(); - - if (data.getScheme().equals("hifi")) { - handleHifiURL(data.toString()); - } - } - - } -} \ No newline at end of file diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index b7d2157737..a7bda3c29b 100644 --- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -24,6 +24,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Vibrator; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -31,6 +32,7 @@ import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.SlidingDrawer; +import org.qtproject.qt5.android.QtNative; import org.qtproject.qt5.android.QtLayout; import org.qtproject.qt5.android.QtSurface; import org.qtproject.qt5.android.bindings.QtActivity; @@ -53,6 +55,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW public static final String DOMAIN_URL = "url"; public static final String EXTRA_GOTO_USERNAME = "gotousername"; private static final String TAG = "Interface"; + public static final String EXTRA_ARGS = "args"; private static final int WEB_DRAWER_RIGHT_MARGIN = 262; private static final int WEB_DRAWER_BOTTOM_MARGIN = 150; private static final int NORMAL_DPI = 160; @@ -77,6 +80,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private boolean nativeEnterBackgroundCallEnqueued = false; private SlidingDrawer mWebSlidingDrawer; + private boolean mStartInDomain; // private GvrApi gvrApi; // Opaque native pointer to the Application C++ object. // This object is owned by the InterfaceActivity instance and passed to the native methods. @@ -92,8 +96,14 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW public void onCreate(Bundle savedInstanceState) { super.isLoading = true; Intent intent = getIntent(); - if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) { + if (intent.hasExtra(DOMAIN_URL) && !TextUtils.isEmpty(intent.getStringExtra(DOMAIN_URL))) { intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL)); + } else if (intent.hasExtra(EXTRA_ARGS)) { + String args = intent.getStringExtra(EXTRA_ARGS); + if (!TextUtils.isEmpty(args)) { + mStartInDomain = true; + intent.putExtra("applicationArguments", args); + } } super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); @@ -124,7 +134,10 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW getActionBar().hide(); } }); - startActivity(new Intent(this, SplashActivity.class)); + Intent splashIntent = new Intent(this, SplashActivity.class); + splashIntent.putExtra(SplashActivity.EXTRA_START_IN_DOMAIN, mStartInDomain); + startActivity(splashIntent); + mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE); headsetStateReceiver = new HeadsetStateReceiver(); } @@ -166,8 +179,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW @Override protected void onDestroy() { - super.onDestroy(); nativeOnDestroy(); + /* + cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly + After Qt upgrade to 5.11 we had a black screen crash after closing the application with + the hardware button "Back" and trying to start the app again. It could only be fixed after + totally closing the app swiping it in the list of running apps. + This problem did not happen with the previous Qt version. + After analysing changes we came up with this case and change: + https://codereview.qt-project.org/#/c/218882/ + In summary they've moved libs loading to the same thread as main() and as a matter of correctness + in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();` + That exit call is the main reason of this problem. + + In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the + entire onDestroy method including that thread exit line for other three lines that purposely + terminate qt (borrowed from QtActivityDelegate::onDestroy as well). + */ + QtNative.terminateQt(); + QtNative.setActivity(null, null); + System.exit(0); + super.onDestroy(); } @Override diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index 78a6421746..ef9876c71a 100644 --- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -9,6 +9,7 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.text.TextUtils; import org.json.JSONException; import org.json.JSONObject; @@ -27,9 +28,14 @@ public class PermissionChecker extends Activity { private static final boolean CHOOSE_AVATAR_ON_STARTUP = false; private static final String TAG = "Interface"; + private static final String EXTRA_ARGS = "args"; + private String mArgs; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mArgs =(getIntent().getStringExtra(EXTRA_ARGS)); + Intent myIntent = new Intent(this, BreakpadUploaderService.class); startService(myIntent); if (CHOOSE_AVATAR_ON_STARTUP) { @@ -76,6 +82,11 @@ public class PermissionChecker extends Activity { private void launchActivityWithPermissions(){ Intent i = new Intent(this, InterfaceActivity.class); + + if (!TextUtils.isEmpty(mArgs)) { + i.putExtra(EXTRA_ARGS, mArgs); + } + startActivity(i); finish(); } diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java index bb42467ace..536bf23603 100644 --- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java @@ -7,6 +7,9 @@ import android.view.View; public class SplashActivity extends Activity { + public static final String EXTRA_START_IN_DOMAIN = "start-in-domain"; + private boolean mStartInDomain; + private native void registerLoadCompleteListener(); @Override @@ -36,13 +39,27 @@ public class SplashActivity extends Activity { } public void onAppLoadedComplete() { - if (HifiUtils.getInstance().isUserLoggedIn()) { - startActivity(new Intent(this, MainActivity.class)); - } else { - Intent menuIntent = new Intent(this, LoginMenuActivity.class); - menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true); - startActivity(menuIntent); + if (!mStartInDomain) { + if (HifiUtils.getInstance().isUserLoggedIn()) { + startActivity(new Intent(this, MainActivity.class)); + } else { + Intent menuIntent = new Intent(this, LoginMenuActivity.class); + menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true); + startActivity(menuIntent); + } } SplashActivity.this.finish(); } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(EXTRA_START_IN_DOMAIN, mStartInDomain); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mStartInDomain = savedInstanceState.getBoolean(EXTRA_START_IN_DOMAIN, false); + } } diff --git a/android/apps/questFramePlayer/CMakeLists.txt b/android/apps/questFramePlayer/CMakeLists.txt new file mode 100644 index 0000000000..5889585a6c --- /dev/null +++ b/android/apps/questFramePlayer/CMakeLists.txt @@ -0,0 +1,9 @@ +set(TARGET_NAME questFramePlayer) +setup_hifi_library(AndroidExtras) +link_hifi_libraries(shared ktx shaders gpu gl oculusMobile ${PLATFORM_GL_BACKEND}) +target_include_directories(${TARGET_NAME} PRIVATE ${HIFI_ANDROID_PRECOMPILED}/ovr/VrApi/Include) +target_link_libraries(${TARGET_NAME} android log m) +target_opengl() +target_oculus_mobile() + + diff --git a/android/apps/questFramePlayer/build.gradle b/android/apps/questFramePlayer/build.gradle new file mode 100644 index 0000000000..0b153af0a9 --- /dev/null +++ b/android/apps/questFramePlayer/build.gradle @@ -0,0 +1,43 @@ +import com.android.builder.core.BuilderConstants + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "io.highfidelity.frameplayer" + minSdkVersion 25 + targetSdkVersion 28 + ndk { abiFilters 'arm64-v8a' } + externalNativeBuild { + cmake { + arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=questFramePlayer', + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_STL=c++_shared', + + '-DCMAKE_VERBOSE_MAKEFILE=ON' + targets = ['questFramePlayer'] + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + externalNativeBuild.cmake.path '../../../CMakeLists.txt' + + variantFilter { variant -> + def build = variant.buildType.name + if (build == BuilderConstants.RELEASE) { + variant.setIgnore(true) + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + implementation project(':oculus') + implementation project(':qt') +} diff --git a/android/apps/questFramePlayer/proguard-rules.pro b/android/apps/questFramePlayer/proguard-rules.pro new file mode 100644 index 0000000000..b3c0078513 --- /dev/null +++ b/android/apps/questFramePlayer/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/questFramePlayer/src/main/AndroidManifest.xml b/android/apps/questFramePlayer/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..721e8cee89 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp new file mode 100644 index 0000000000..ec2986298e --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp @@ -0,0 +1,25 @@ +// +// 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 + +PlayerWindow::PlayerWindow() { + installEventFilter(this); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + setSurfaceType(QSurface::OpenGLSurface); + create(); + showFullScreen(); + // Ensure the window is visible and the GL context is valid + QCoreApplication::processEvents(); + _renderThread.initialize(this); +} + +PlayerWindow::~PlayerWindow() { +} diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h new file mode 100644 index 0000000000..e4dd6cef43 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h @@ -0,0 +1,29 @@ +// +// 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; + +private: + QSettings _settings; + RenderThread _renderThread; +}; diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp new file mode 100644 index 0000000000..5eabe6b9b1 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp @@ -0,0 +1,240 @@ +// +// 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 +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +static JNIEnv* _env { nullptr }; +static JavaVM* _vm { nullptr }; +static jobject _activity { nullptr }; + +struct HandController{ + ovrInputTrackedRemoteCapabilities caps {}; + ovrInputStateTrackedRemote state {}; + ovrResult stateResult{ ovrSuccess }; + ovrTracking tracking {}; + ovrResult trackingResult{ ovrSuccess }; + + void update(ovrMobile* session, double time = 0.0) { + const auto& deviceId = caps.Header.DeviceID; + stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header); + trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking); + } +}; + +std::vector devices; + +extern "C" { + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) { + __android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__); + return JNI_VERSION_1_6; +} + + +JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) { + env->GetJavaVM(&_vm); + _activity = env->NewGlobalRef(obj); +} +} + +static const char* FRAME_FILE = "assets:/frames/20190121_1220.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 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::initialize(QWindow* window) { + std::unique_lock lock(_frameLock); + setObjectName("RenderThread"); + Parent::initialize(); + _window = window; + _thread->setObjectName("RenderThread"); +} + +void RenderThread::setup() { + // Wait until the context has been moved to this thread + { std::unique_lock lock(_frameLock); } + + + ovr::VrHandler::initVr(); + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity"); + _vm->AttachCurrentThread(&_env, nullptr); + jclass cls = _env->GetObjectClass(_activity); + jmethodID mid = _env->GetMethodID(cls, "launchOculusActivity", "()V"); + _env->CallVoidMethod(_activity, mid); + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity done"); + ovr::VrHandler::setHandler(this); + + makeCurrent(); + + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _gpuContext->beginFrame(); + _gpuContext->endFrame(); + + makeCurrent(); + glGenTextures(1, &_externalTexture); + glBindTexture(GL_TEXTURE_2D, _externalTexture); + static const glm::u8vec4 color{ 0,1,0,0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color); + + if (QFileInfo(FRAME_FILE).exists()) { + auto frame = gpu::readFrame(FRAME_FILE, _externalTexture, &textureLoader); + submitFrame(frame); + } +} + +void RenderThread::shutdown() { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _gpuContext->shutdown(); + _gpuContext.reset(); +} + +void RenderThread::handleInput() { + static std::once_flag once; + std::call_once(once, [&]{ + withOvrMobile([&](ovrMobile* session){ + int deviceIndex = 0; + ovrInputCapabilityHeader capsHeader; + while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { + if (capsHeader.Type == ovrControllerType_TrackedRemote) { + HandController controller = {}; + controller.caps.Header = capsHeader; + controller.state.Header.ControllerType = ovrControllerType_TrackedRemote; + vrapi_GetInputDeviceCapabilities( session, &controller.caps.Header); + devices.push_back(controller); + } + ++deviceIndex; + } + }); + }); + + auto readResult = ovr::VrHandler::withOvrMobile([&](ovrMobile *session) { + for (auto &controller : devices) { + controller.update(session); + } + }); + + if (readResult) { + for (auto &controller : devices) { + const auto &caps = controller.caps; + if (controller.stateResult >= 0) { + const auto &remote = controller.state; + if (remote.Joystick.x != 0.0f || remote.Joystick.y != 0.0f) { + glm::vec3 translation; + float rotation = 0.0f; + if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) { + translation = glm::vec3{0.0f, -remote.Joystick.y, 0.0f}; + } else { + translation = glm::vec3{remote.Joystick.x, 0.0f, -remote.Joystick.y}; + } + float scale = 0.1f + (1.9f * remote.GripTrigger); + _correction = glm::translate(glm::mat4(), translation * scale) * _correction; + } + } + } + } +} + +void RenderThread::renderFrame() { + GLuint finalTexture = 0; + glm::uvec2 finalTextureSize; + const auto& tracking = beginFrame(); + if (_activeFrame) { + const auto& frame = _activeFrame; + auto& eyeProjections = frame->stereoState._eyeProjections; + auto& eyeOffsets = frame->stereoState._eyeViews; + // Quest + auto frameCorrection = _correction * ovr::toGlm(tracking.HeadPose.Pose); + _backend->setCameraCorrection(glm::inverse(frameCorrection), frame->view); + ovr::for_each_eye([&](ovrEye eye){ + const auto& eyeInfo = tracking.Eye[eye]; + eyeProjections[eye] = ovr::toGlm(eyeInfo.ProjectionMatrix); + eyeOffsets[eye] = ovr::toGlm(eyeInfo.ViewMatrix); + }); + _backend->recycle(); + _backend->syncCache(); + _gpuContext->enableStereo(true); + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + } + auto& glbackend = (gpu::gl::GLBackend&)(*_backend); + finalTextureSize = { frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; + finalTexture = glbackend.getTextureID(frame->framebuffer->getRenderBuffer(0)); + } + presentFrame(finalTexture, finalTextureSize, tracking); +} + +bool RenderThread::process() { + pollTask(); + + if (!vrActive()) { + QThread::msleep(1); + return true; + } + + std::queue pendingFrames; + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + } + + makeCurrent(); + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + pendingFrames.pop(); + _gpuContext->consumeFrameUpdates(_activeFrame); + _activeFrame->stereoState._enable = true; + } + + handleInput(); + renderFrame(); + return true; +} diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.h b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h new file mode 100644 index 0000000000..701cd25f5b --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h @@ -0,0 +1,44 @@ +// +// 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 +#include +#include + +class RenderThread : public GenericThread, ovr::VrHandler { + 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; + + void move(const glm::vec3& v); + void setup() override; + bool process() override; + void shutdown() override; + + void handleInput(); + + void submitFrame(const gpu::FramePointer& frame); + void initialize(QWindow* window); + void renderFrame(); +}; diff --git a/android/apps/questFramePlayer/src/main/cpp/main.cpp b/android/apps/questFramePlayer/src/main/cpp/main.cpp new file mode 100644 index 0000000000..4730d3fa15 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/main.cpp @@ -0,0 +1,56 @@ +// +// 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; + __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec"); + app.exec(); + __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done"); + qInstallMessageHandler(oldMessageHandler); + return 0; +} + + diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java new file mode 100644 index 0000000000..d498e27547 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java @@ -0,0 +1,53 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// 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 +// +package io.highfidelity.frameplayer; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import org.qtproject.qt5.android.bindings.QtActivity; + +import io.highfidelity.oculus.OculusMobileActivity; + + +public class QuestQtActivity extends QtActivity { + private native void nativeOnCreate(); + private boolean launchedQuestMode = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.w("QQQ_Qt", "QuestQtActivity::onCreate"); + super.onCreate(savedInstanceState); + nativeOnCreate(); + } + + @Override + public void onDestroy() { + Log.w("QQQ_Qt", "QuestQtActivity::onDestroy"); + super.onDestroy(); + } + + public void launchOculusActivity() { + Log.w("QQQ_Qt", "QuestQtActivity::launchOculusActivity"); + runOnUiThread(()->{ + keepInterfaceRunning = true; + launchedQuestMode = true; + moveTaskToBack(true); + startActivity(new Intent(this, QuestRenderActivity.class)); + }); + } + + @Override + public void onResume() { + super.onResume(); + if (launchedQuestMode) { + moveTaskToBack(true); + } + } +} diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java new file mode 100644 index 0000000000..a395a32b68 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java @@ -0,0 +1,14 @@ +package io.highfidelity.frameplayer; + +import android.content.Intent; +import android.os.Bundle; + +import io.highfidelity.oculus.OculusMobileActivity; + +public class QuestRenderActivity extends OculusMobileActivity { + @Override + public void onCreate(Bundle savedState) { + super.onCreate(savedState); + startActivity(new Intent(this, QuestQtActivity.class)); + } +} diff --git a/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/apps/questFramePlayer/src/main/res/values/strings.xml b/android/apps/questFramePlayer/src/main/res/values/strings.xml new file mode 100644 index 0000000000..8bf550f74e --- /dev/null +++ b/android/apps/questFramePlayer/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + GPU Frame Player + diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index c37f73cb2a..fe3a83950a 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -73,13 +73,10 @@ RUN mkdir "$HIFI_BASE" && \ RUN git clone https://github.com/jherico/hifi.git && \ cd ~/hifi && \ - git checkout feature/quest_move_interface + git checkout feature/quest_frame_player WORKDIR /home/jenkins/hifi -RUN touch .test6 && \ - git fetch && git reset origin/feature/quest_move_interface --hard - RUN mkdir build # Pre-cache the vcpkg managed dependencies diff --git a/android/libraries/oculus/build.gradle b/android/libraries/oculus/build.gradle new file mode 100644 index 0000000000..b072f99eb7 --- /dev/null +++ b/android/libraries/oculus/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} diff --git a/android/libraries/oculus/src/main/AndroidManifest.xml b/android/libraries/oculus/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..57df1a4226 --- /dev/null +++ b/android/libraries/oculus/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java new file mode 100644 index 0000000000..01d74ea94d --- /dev/null +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -0,0 +1,103 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// 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 +// +package io.highfidelity.oculus; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.WindowManager; + +/** + * Contains a native surface and forwards the activity lifecycle and surface lifecycle + * events to the OculusMobileDisplayPlugin + */ +public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback { + private static final String TAG = OculusMobileActivity.class.getSimpleName(); + static { System.loadLibrary("oculusMobile"); } + private native void nativeOnCreate(); + private native static void nativeOnResume(); + private native static void nativeOnPause(); + private native static void nativeOnDestroy(); + private native static void nativeOnSurfaceChanged(Surface s); + + private SurfaceView mView; + private SurfaceHolder mSurfaceHolder; + + + public static void launch(Activity activity) { + if (activity != null) { + activity.runOnUiThread(()->{ + activity.startActivity(new Intent(activity, OculusMobileActivity.class)); + }); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.w(TAG, "QQQ onCreate"); + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + // Create a native surface for VR rendering (Qt GL surfaces are not suitable + // because of the lack of fine control over the surface callbacks) + mView = new SurfaceView(this); + setContentView(mView); + mView.getHolder().addCallback(this); + + // Forward the create message to the JNI code + nativeOnCreate(); + } + + @Override + protected void onDestroy() { + Log.w(TAG, "QQQ onDestroy"); + if (mSurfaceHolder != null) { + nativeOnSurfaceChanged(null); + } + nativeOnDestroy(); + super.onDestroy(); + } + + @Override + protected void onResume() { + Log.w(TAG, "QQQ onResume"); + super.onResume(); + nativeOnResume(); + } + + @Override + protected void onPause() { + Log.w(TAG, "QQQ onPause"); + nativeOnPause(); + super.onPause(); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.w(TAG, "QQQ surfaceCreated"); + nativeOnSurfaceChanged(holder.getSurface()); + mSurfaceHolder = holder; + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.w(TAG, "QQQ surfaceChanged"); + nativeOnSurfaceChanged(holder.getSurface()); + mSurfaceHolder = holder; + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.w(TAG, "QQQ surfaceDestroyed"); + nativeOnSurfaceChanged(null); + mSurfaceHolder = null; + } +} \ No newline at end of file diff --git a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java index 93ae2bc227..6a6688ac41 100644 --- a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -364,25 +364,7 @@ public class QtActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); - /* - cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly - After Qt upgrade to 5.11 we had a black screen crash after closing the application with - the hardware button "Back" and trying to start the app again. It could only be fixed after - totally closing the app swiping it in the list of running apps. - This problem did not happen with the previous Qt version. - After analysing changes we came up with this case and change: - https://codereview.qt-project.org/#/c/218882/ - In summary they've moved libs loading to the same thread as main() and as a matter of correctness - in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();` - That exit call is the main reason of this problem. - - In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the - entire onDestroy method including that thread exit line for other three lines that purposely - terminate qt (borrowed from QtActivityDelegate::onDestroy as well). - */ - QtNative.terminateQt(); - QtNative.setActivity(null, null); - System.exit(0); + QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @@ -668,6 +650,8 @@ public class QtActivity extends Activity { if (!keepInterfaceRunning) { QtApplication.invokeDelegate(); } + QtNative.terminateQt(); + QtNative.setActivity(null,null); } //--------------------------------------------------------------------------- diff --git a/android/settings.gradle b/android/settings.gradle index 1e7b3c768a..699f617cce 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,8 +1,26 @@ +// +// Libraries +// + +include ':oculus' +project(':oculus').projectDir = new File(settingsDir, 'libraries/oculus') + include ':qt' project(':qt').projectDir = new File(settingsDir, 'libraries/qt') +// +// Applications +// + include ':interface' project(':interface').projectDir = new File(settingsDir, 'apps/interface') -//include ':framePlayer' -//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') +// +// Test projects +// + +include ':framePlayer' +project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') + +include ':questFramePlayer' +project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer') diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..801f28c6f5 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -38,6 +38,19 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; // FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now. const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; +const QRegularExpression AvatarMixer::suffixedNamePattern { R"(^\s*(.+)\s*_(\d)+\s*$)" }; + +// Lexicographic comparison: +bool AvatarMixer::SessionDisplayName::operator<(const SessionDisplayName& rhs) const { + if (_baseName < rhs._baseName) { + return true; + } else if (rhs._baseName < _baseName) { + return false; + } else { + return _suffix < rhs._suffix; + } +} + AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message), _slavePool(&_slaveSharedData) @@ -313,27 +326,40 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { bool sendIdentity = false; if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { AvatarData& avatar = nodeData->getAvatar(); - const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); - if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { - _sessionDisplayNames.remove(existingBaseDisplayName); + const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName(); + if (!existingBaseDisplayName.isEmpty()) { + SessionDisplayName existingDisplayName { existingBaseDisplayName }; + + auto suffixMatch = suffixedNamePattern.match(existingBaseDisplayName); + if (suffixMatch.hasMatch()) { + existingDisplayName._baseName = suffixMatch.captured(1); + existingDisplayName._suffix = suffixMatch.captured(2).toInt(); + } + _sessionDisplayNames.erase(existingDisplayName); } QString baseName = avatar.getDisplayName().trimmed(); const QRegularExpression curses { "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?). baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk. - const QRegularExpression trailingDigits { "\\s*(_\\d+\\s*)?(\\s*\\n[^$]*)?$" }; // trailing whitespace "_123" and any subsequent lines + static const QRegularExpression trailingDigits { R"(\s*(_\d+\s*)?(\s*\n[^$]*)?$)" }; // trailing whitespace "_123" and any subsequent lines baseName = baseName.remove(trailingDigits); if (baseName.isEmpty()) { baseName = "anonymous"; } - QPair& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want. - int& highWater = soFar.first; - nodeData->setBaseDisplayName(baseName); - QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName; + SessionDisplayName newDisplayName { baseName }; + auto nameIter = _sessionDisplayNames.lower_bound(newDisplayName); + if (nameIter != _sessionDisplayNames.end() && nameIter->_baseName == baseName) { + // Existing instance(s) of name; find first free suffix + while (*nameIter == newDisplayName && ++newDisplayName._suffix && ++nameIter != _sessionDisplayNames.end()) + ; + } + + _sessionDisplayNames.insert(newDisplayName); + QString sessionDisplayName = (newDisplayName._suffix > 0) ? baseName + "_" + QString::number(newDisplayName._suffix) : baseName; avatar.setSessionDisplayName(sessionDisplayName); - highWater++; - soFar.second++; // refcount + nodeData->setBaseDisplayName(baseName); + nodeData->flagIdentityChange(); nodeData->setAvatarSessionDisplayNameMustChange(false); sendIdentity = true; @@ -409,10 +435,19 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { { // decrement sessionDisplayNames table and possibly remove QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex()); AvatarMixerClientData* nodeData = dynamic_cast(avatarNode->getLinkedData()); - const QString& baseDisplayName = nodeData->getBaseDisplayName(); - // No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case. - if (--_sessionDisplayNames[baseDisplayName].second <= 0) { - _sessionDisplayNames.remove(baseDisplayName); + const QString& displayName = nodeData->getAvatar().getSessionDisplayName(); + SessionDisplayName exitingDisplayName { displayName }; + + auto suffixMatch = suffixedNamePattern.match(displayName); + if (suffixMatch.hasMatch()) { + exitingDisplayName._baseName = suffixMatch.captured(1); + exitingDisplayName._suffix = suffixMatch.captured(2).toInt(); + } + auto displayNameIter = _sessionDisplayNames.find(exitingDisplayName); + if (displayNameIter == _sessionDisplayNames.end()) { + qCDebug(avatars) << "Exiting avatar displayname" << displayName << "not found"; + } else { + _sessionDisplayNames.erase(displayNameIter); } } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 764656a2d5..2992e19b8f 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -15,6 +15,7 @@ #ifndef hifi_AvatarMixer_h #define hifi_AvatarMixer_h +#include #include #include @@ -88,7 +89,24 @@ private: RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; - QHash> _sessionDisplayNames; + + // Pair of basename + uniquifying integer suffix. + struct SessionDisplayName { + explicit SessionDisplayName(QString baseName = QString(), int suffix = 0) : + _baseName(baseName), + _suffix(suffix) { } + // Does lexicographic ordering: + bool operator<(const SessionDisplayName& rhs) const; + bool operator==(const SessionDisplayName& rhs) const { + return _baseName == rhs._baseName && _suffix == rhs._suffix; + } + + QString _baseName; + int _suffix; + }; + static const QRegularExpression suffixedNamePattern; + + std::set _sessionDisplayNames; quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window quint64 _ignoreCalculationElapsedTime { 0 }; diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 8b7c8771e8..a6542689e0 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -111,7 +111,7 @@ bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); newView.lodScaleFactor = powf(2.0f, lodLevelOffset); - startNewTraversal(newView, root); + startNewTraversal(newView, root, isFullScene); // When the viewFrustum changed the sort order may be incorrect, so we re-sort // and also use the opportunity to cull anything no longer in view @@ -220,9 +220,10 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) { +void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root, + bool forceFirstPass) { - DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root); + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, forceFirstPass); // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 199769ca09..7eedc2f1ba 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -42,7 +42,7 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root); + void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root, bool forceFirstPass = false); bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; void preDistributionProcessing() override; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index ef0c807bc4..f1a6c97831 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -112,7 +112,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointerunloadEntityScript(entityID); checkAndCallPreload(entityID, true); } } @@ -184,7 +183,6 @@ void EntityScriptServer::updateEntityPPS() { pps = std::min(_maxEntityPPS, pps); } _entityEditSender.setPacketsPerSecond(pps); - qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps); } void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { @@ -525,23 +523,25 @@ void EntityScriptServer::deletingEntity(const EntityItemID& entityID) { void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) { if (_entityViewer.getTree() && !_shuttingDown) { - _entitiesScriptEngine->unloadEntityScript(entityID, true); checkAndCallPreload(entityID, reload); } } -void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool reload) { +void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload) { if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID); EntityScriptDetails details; - bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details); - if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) { + bool isRunning = _entitiesScriptEngine->getEntityScriptDetails(entityID, details); + if (entity && (forceRedownload || !isRunning || details.scriptText != entity->getServerScripts())) { + if (isRunning) { + _entitiesScriptEngine->unloadEntityScript(entityID, true); + } + QString scriptUrl = entity->getServerScripts(); if (!scriptUrl.isEmpty()) { scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); - qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID; - _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); + _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, forceRedownload); } } } diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 9c6c4c752e..944fee36a3 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -67,7 +67,7 @@ private: void addingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID); void entityServerScriptChanging(const EntityItemID& entityID, bool reload); - void checkAndCallPreload(const EntityItemID& entityID, bool reload = false); + void checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload = false); void cleanupOldKilledListeners(); diff --git a/cmake/macros/FixupNitpick.cmake b/cmake/macros/FixupNitpick.cmake new file mode 100644 index 0000000000..8477b17823 --- /dev/null +++ b/cmake/macros/FixupNitpick.cmake @@ -0,0 +1,36 @@ +# +# FixupNitpick.cmake +# cmake/macros +# +# Copyright 2019 High Fidelity, Inc. +# Created by Nissim Hadar on January 14th, 2016 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(fixup_nitpick) + if (APPLE) + string(REPLACE " " "\\ " ESCAPED_BUNDLE_NAME ${NITPICK_BUNDLE_NAME}) + string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${NITPICK_INSTALL_DIR}) + set(_NITPICK_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app") + + find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH) + + if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD)) + message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin.\ + It is required to produce a relocatable nitpick application.\ + Check that the environment variable QT_DIR points to your Qt installation.\ + ") + endif () + + install(CODE " + execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\ + \${CMAKE_INSTALL_PREFIX}/${_NITPICK_INSTALL_PATH}/\ + -verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\ + )" + COMPONENT ${CLIENT_COMPONENT} + ) + + endif () +endmacro() diff --git a/cmake/macros/IncludeHifiLibraryHeaders.cmake b/cmake/macros/IncludeHifiLibraryHeaders.cmake index 913d1e1181..008d76a8dc 100644 --- a/cmake/macros/IncludeHifiLibraryHeaders.cmake +++ b/cmake/macros/IncludeHifiLibraryHeaders.cmake @@ -10,5 +10,5 @@ # macro(include_hifi_library_headers LIBRARY) - include_directories("${HIFI_LIBRARY_DIR}/${LIBRARY}/src") + target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${LIBRARY}/src") endmacro(include_hifi_library_headers _library _root_dir) \ No newline at end of file diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake index 7a6a136799..6a430f5b13 100644 --- a/cmake/macros/LinkHifiLibraries.cmake +++ b/cmake/macros/LinkHifiLibraries.cmake @@ -19,8 +19,8 @@ function(LINK_HIFI_LIBRARIES) endforeach() foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK}) - include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") - include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}") + target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src") + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}") # link the actual library - it is static so don't bubble it up target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY}) endforeach() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 1b7243d4f2..3ebc44e931 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -77,6 +77,9 @@ macro(SET_PACKAGING_PARAMETERS) add_definitions(-DDEV_BUILD) endif () + set(NITPICK_BUNDLE_NAME "nitpick") + set(NITPICK_ICON_PREFIX "nitpick") + string(TIMESTAMP BUILD_TIME "%d/%m/%Y") # if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and @@ -140,8 +143,9 @@ macro(SET_PACKAGING_PARAMETERS) set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc") - set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) + set(NITPICK_INSTALL_DIR ${DMG_SUBFOLDER_NAME}) if (CLIENT_ONLY) set(CONSOLE_EXEC_NAME "Console.app") @@ -159,11 +163,14 @@ macro(SET_PACKAGING_PARAMETERS) set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app") set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns") + set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.icns") else () if (WIN32) set(CONSOLE_INSTALL_DIR "server-console") + set(NITPICK_INSTALL_DIR "nitpick") else () set(CONSOLE_INSTALL_DIR ".") + set(NITPICK_INSTALL_DIR ".") endif () set(COMPONENT_INSTALL_DIR ".") @@ -173,6 +180,7 @@ macro(SET_PACKAGING_PARAMETERS) if (WIN32) set(INTERFACE_EXEC_PREFIX "interface") set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.ico") + set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.ico") set(CONSOLE_EXEC_NAME "server-console.exe") diff --git a/cmake/macros/TargetEGL.cmake b/cmake/macros/TargetEGL.cmake new file mode 100644 index 0000000000..1d8ce26d83 --- /dev/null +++ b/cmake/macros/TargetEGL.cmake @@ -0,0 +1,4 @@ +macro(target_egl) + find_library(EGL EGL) + target_link_libraries(${TARGET_NAME} ${EGL}) +endmacro() diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake new file mode 100644 index 0000000000..3eaa008b14 --- /dev/null +++ b/cmake/macros/TargetOculusMobile.cmake @@ -0,0 +1,20 @@ + +macro(target_oculus_mobile) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi) + + # Mobile SDK + set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include) + target_include_directories(${TARGET_NAME} PRIVATE ${OVR_MOBILE_INCLUDE_DIRS}) + set(OVR_MOBILE_LIBRARY_DIR ${INSTALL_DIR}/Libs/Android/arm64-v8a) + set(OVR_MOBILE_LIBRARY_RELEASE ${OVR_MOBILE_LIBRARY_DIR}/Release/libvrapi.so) + set(OVR_MOBILE_LIBRARY_DEBUG ${OVR_MOBILE_LIBRARY_DIR}/Debug/libvrapi.so) + select_library_configurations(OVR_MOBILE) + target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES}) + + # Platform SDK + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform) + set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include) + target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS}) + set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so) + target_link_libraries(${TARGET_NAME} ${OVR_PLATFORM_LIBRARIES}) +endmacro() diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 9ce11ca032..cc7a6929a2 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -300,6 +300,8 @@ Var substringResult SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags ;Checking lowest bit: IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + !insertmacro LoadVar ${SecName}_was_installed + IntOp $AR_SecFlags $AR_SecFlags | $R0 IntCmp $AR_SecFlags 1 "leave_${SecName}" ;Section is not selected: @@ -478,18 +480,6 @@ Var GAClientID ;-------------------------------- ; Installation types -Section "-Previous Install Cleanup" - ; Remove the resources folder so we don't end up including removed QML files - RMDir /r "$INSTDIR\resources" - - ; delete old assignment-client and domain-server so they're no longer present - ; in client only installs. - Delete "$INSTDIR\@DS_EXEC_NAME@" - Delete "$INSTDIR\@AC_EXEC_NAME@" - - ; delete interface so it's not there for server-only installs - Delete "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" -SectionEnd @CPACK_NSIS_INSTALLATION_TYPES@ diff --git a/gvr-interface/CMakeLists.txt b/gvr-interface/CMakeLists.txt deleted file mode 100644 index 72f1096881..0000000000 --- a/gvr-interface/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -set(TARGET_NAME gvr-interface) - -if (ANDROID) - set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build") - set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk") - - set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME}) - set(ANDROID_APP_DISPLAY_NAME Interface) - set(ANDROID_API_LEVEL 19) - set(ANDROID_APK_PACKAGE io.highfidelity.gvrinterface) - set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceActivity) - set(ANDROID_APK_VERSION_NAME "0.1") - set(ANDROID_APK_VERSION_CODE 1) - set(ANDROID_APK_FULLSCREEN TRUE) - set(ANDROID_DEPLOY_QT_INSTALL "--install") - - set(BUILD_SHARED_LIBS ON) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}") - - setup_hifi_library(Gui AndroidExtras) -else () - setup_hifi_project(Gui) -endif () - -include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) - -link_hifi_libraries(shared networking audio-client avatars) - -if (ANDROID) - find_package(LibOVR) - - if (LIBOVR_FOUND) - add_definitions(-DHAVE_LIBOVR) - target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${LIBOVR_ANDROID_LIBRARIES} ${TURBOJPEG_LIBRARY}) - include_directories(SYSTEM ${LIBOVR_INCLUDE_DIRS}) - - # we need VRLib, so add a project.properties to our apk build folder that says that - file(RELATIVE_PATH RELATIVE_VRLIB_PATH ${ANDROID_APK_OUTPUT_DIR} "${LIBOVR_VRLIB_DIR}") - file(WRITE "${ANDROID_APK_BUILD_DIR}/project.properties" "android.library.reference.1=${RELATIVE_VRLIB_PATH}") - - list(APPEND IGNORE_COPY_LIBS ${LIBOVR_ANDROID_LIBRARIES}) - endif () - -endif () - -# the presence of a HOCKEY_APP_ID means we are making a beta build -if (ANDROID AND HOCKEY_APP_ID) - set(HOCKEY_APP_ENABLED true) - set(HOCKEY_APP_ACTIVITY "\n") - set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceBetaActivity) - set(ANDROID_DEPLOY_QT_INSTALL "") - set(ANDROID_APK_CUSTOM_NAME "Interface-beta.apk") - - # set the ANDROID_APK_VERSION_CODE to the number of git commits - execute_process( - COMMAND git rev-list --first-parent --count HEAD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_COMMIT_COUNT - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - set(ANDROID_APK_VERSION_CODE ${GIT_COMMIT_COUNT}) - - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/InterfaceBetaActivity.java.in" "${ANDROID_APK_BUILD_DIR}/src/io/highfidelity/gvrinterface/InterfaceBetaActivity.java") -elseif (ANDROID) - set(HOCKEY_APP_ENABLED false) -endif () - -if (ANDROID) - - set(HIFI_URL_INTENT "\ - \n \ - \n \ - \n \ - \n \ - \n " - ) - - set(ANDROID_EXTRA_APPLICATION_XML "${HOCKEY_APP_ACTIVITY}") - set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}") - - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/hockeyapp.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/hockeyapp.xml") - qt_create_apk() - -endif (ANDROID) diff --git a/gvr-interface/res/drawable/icon.png b/gvr-interface/res/drawable/icon.png deleted file mode 100644 index 70aaf9b4ed..0000000000 Binary files a/gvr-interface/res/drawable/icon.png and /dev/null differ diff --git a/gvr-interface/src/Client.cpp b/gvr-interface/src/Client.cpp deleted file mode 100644 index 8f064c7fd5..0000000000 --- a/gvr-interface/src/Client.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// -// Client.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/15. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Client.h" - -#include -#include -#include -#include -#include - -Client::Client(QObject* parent) : - QObject(parent) -{ - // we need to make sure that required dependencies are created - DependencyManager::set(); - - setupNetworking(); -} - -void Client::setupNetworking() { - // once Application order of instantiation is fixed this should be done from AccountManager - AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL); - - // setup the NodeList for this client - DependencyManager::registerInheritance(); - auto nodeList = DependencyManager::set(NodeType::Agent, 0); - - // while datagram processing remains simple for targets using Client, we'll handle datagrams - connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &Client::processDatagrams); - - // every second, ask the NodeList to check in with the domain server - QTimer* domainCheckInTimer = new QTimer(this); - domainCheckInTimer->setInterval(DOMAIN_SERVER_CHECK_IN_MSECS); - connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); - - // TODO: once the Client knows its Address on start-up we should be able to immediately send a check in here - domainCheckInTimer->start(); - - // handle the case where the domain stops talking to us - // TODO: can we just have the nodelist do this when it sets up? Is there a user of the NodeList that wouldn't want this? - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); -} - -void Client::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) { - DependencyManager::get()->processNodeData(senderSockAddr, incomingPacket); -} - -void Client::processDatagrams() { - HifiSockAddr senderSockAddr; - - static QByteArray incomingPacket; - - auto nodeList = DependencyManager::get(); - - while (DependencyManager::get()->getNodeSocket().hasPendingDatagrams()) { - incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); - nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(), - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - - if (nodeList->packetVersionAndHashMatch(incomingPacket)) { - processVerifiedPacket(senderSockAddr, incomingPacket); - } - } -} diff --git a/gvr-interface/src/Client.h b/gvr-interface/src/Client.h deleted file mode 100644 index 6fbe40f165..0000000000 --- a/gvr-interface/src/Client.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Client.h -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/15. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Client_h -#define hifi_Client_h - -#include - -#include - -class Client : public QObject { - Q_OBJECT -public: - Client(QObject* parent = 0); - - virtual void cleanupBeforeQuit() = 0; -protected: - - void setupNetworking(); - virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket); -private slots: - void processDatagrams(); -}; - -#endif // hifi_Client_h diff --git a/gvr-interface/src/GVRInterface.cpp b/gvr-interface/src/GVRInterface.cpp deleted file mode 100644 index f9a29d4ac4..0000000000 --- a/gvr-interface/src/GVRInterface.cpp +++ /dev/null @@ -1,191 +0,0 @@ -// -// GVRInterface.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 11/18/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GVRInterface.h" - -#ifdef ANDROID - -#include - -#include -#include -#include - -#ifdef HAVE_LIBOVR - -#include -#include - -#endif -#endif - -#include -#include -#include - -#include "GVRMainWindow.h" -#include "RenderingClient.h" - -static QString launchURLString = QString(); - -#ifdef ANDROID - -extern "C" { - -JNIEXPORT void Java_io_highfidelity_gvrinterface_InterfaceActivity_handleHifiURL(JNIEnv *jni, jclass clazz, jstring hifiURLString) { - launchURLString = QAndroidJniObject(hifiURLString).toString(); -} - -} - -#endif - -GVRInterface::GVRInterface(int argc, char* argv[]) : - QApplication(argc, argv), - _mainWindow(NULL), - _inVRMode(false) -{ - setApplicationName("gvr-interface"); - setOrganizationName("highfidelity"); - setOrganizationDomain("io"); - - if (!launchURLString.isEmpty()) { - // did we get launched with a lookup URL? If so it is time to give that to the AddressManager - qDebug() << "We were opened via a hifi URL -" << launchURLString; - } - - _client = new RenderingClient(this, launchURLString); - - launchURLString = QString(); - - connect(this, &QGuiApplication::applicationStateChanged, this, &GVRInterface::handleApplicationStateChange); - -#if defined(ANDROID) && defined(HAVE_LIBOVR) - QAndroidJniEnvironment jniEnv; - - QPlatformNativeInterface* interface = QApplication::platformNativeInterface(); - jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity"); - - ovr_RegisterHmtReceivers(&*jniEnv, activity); - - // PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being - // stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES - // but that increases the size of the plugin by ~1MiB - OVR::linkerPlatformActivity++; -#endif - - // call our idle function whenever we can - QTimer* idleTimer = new QTimer(this); - connect(idleTimer, &QTimer::timeout, this, &GVRInterface::idle); - idleTimer->start(0); - - // call our quit handler before we go down - connect(this, &QCoreApplication::aboutToQuit, this, &GVRInterface::handleApplicationQuit); -} - -void GVRInterface::handleApplicationQuit() { - _client->cleanupBeforeQuit(); -} - -void GVRInterface::idle() { -#if defined(ANDROID) && defined(HAVE_LIBOVR) - if (!_inVRMode && ovr_IsHeadsetDocked()) { - qDebug() << "The headset just got docked - enter VR mode."; - enterVRMode(); - } else if (_inVRMode) { - - if (ovr_IsHeadsetDocked()) { - static int counter = 0; - - // Get the latest head tracking state, predicted ahead to the midpoint of the time - // it will be displayed. It will always be corrected to the real values by - // time warp, but the closer we get, the less black will be pulled in at the edges. - const double now = ovr_GetTimeInSeconds(); - static double prev; - const double rawDelta = now - prev; - prev = now; - const double clampedPrediction = std::min( 0.1, rawDelta * 2); - ovrSensorState sensor = ovrHmd_GetSensorState(OvrHmd, now + clampedPrediction, true ); - - auto ovrOrientation = sensor.Predicted.Pose.Orientation; - glm::quat newOrientation(ovrOrientation.w, ovrOrientation.x, ovrOrientation.y, ovrOrientation.z); - _client->setOrientation(newOrientation); - - if (counter++ % 100000 == 0) { - qDebug() << "GetSensorState in frame" << counter << "-" - << ovrOrientation.x << ovrOrientation.y << ovrOrientation.z << ovrOrientation.w; - } - } else { - qDebug() << "The headset was undocked - leaving VR mode."; - - leaveVRMode(); - } - } - - OVR::KeyState& backKeyState = _mainWindow->getBackKeyState(); - auto backEvent = backKeyState.Update(ovr_GetTimeInSeconds()); - - if (backEvent == OVR::KeyState::KEY_EVENT_LONG_PRESS) { - qDebug() << "Attemping to start the Platform UI Activity."; - ovr_StartPackageActivity(_ovr, PUI_CLASS_NAME, PUI_GLOBAL_MENU); - } else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP || backEvent == OVR::KeyState::KEY_EVENT_SHORT_PRESS) { - qDebug() << "Got an event we should cancel for!"; - } else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP) { - qDebug() << "The button is down!"; - } -#endif -} - -void GVRInterface::handleApplicationStateChange(Qt::ApplicationState state) { - switch(state) { - case Qt::ApplicationActive: - qDebug() << "The application is active."; - break; - case Qt::ApplicationSuspended: - qDebug() << "The application is being suspended."; - break; - default: - break; - } -} - -void GVRInterface::enterVRMode() { -#if defined(ANDROID) && defined(HAVE_LIBOVR) - // Default vrModeParms - ovrModeParms vrModeParms; - vrModeParms.AsynchronousTimeWarp = true; - vrModeParms.AllowPowerSave = true; - vrModeParms.DistortionFileName = NULL; - vrModeParms.EnableImageServer = false; - vrModeParms.CpuLevel = 2; - vrModeParms.GpuLevel = 2; - vrModeParms.GameThreadTid = 0; - - QAndroidJniEnvironment jniEnv; - - QPlatformNativeInterface* interface = QApplication::platformNativeInterface(); - jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity"); - - vrModeParms.ActivityObject = activity; - - ovrHmdInfo hmdInfo; - _ovr = ovr_EnterVrMode(vrModeParms, &hmdInfo); - - _inVRMode = true; -#endif -} - -void GVRInterface::leaveVRMode() { -#if defined(ANDROID) && defined(HAVE_LIBOVR) - ovr_LeaveVrMode(_ovr); - _inVRMode = false; -#endif -} diff --git a/gvr-interface/src/GVRInterface.h b/gvr-interface/src/GVRInterface.h deleted file mode 100644 index 9ffbd52909..0000000000 --- a/gvr-interface/src/GVRInterface.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// GVRInterface.h -// gvr-interface/src -// -// Created by Stephen Birarda on 11/18/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_GVRInterface_h -#define hifi_GVRInterface_h - -#include - -#if defined(ANDROID) && defined(HAVE_LIBOVR) -class ovrMobile; -class ovrHmdInfo; - -// This is set by JNI_OnLoad() when the .so is initially loaded. -// Must use to attach each thread that will use JNI: -namespace OVR { - // PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being - // stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES - // but that increases the size of the plugin by ~1MiB - extern int linkerPlatformActivity; -} - -#endif - -class GVRMainWindow; -class RenderingClient; -class QKeyEvent; - -#if defined(qApp) -#undef qApp -#endif -#define qApp (static_cast(QApplication::instance())) - -class GVRInterface : public QApplication { - Q_OBJECT -public: - GVRInterface(int argc, char* argv[]); - RenderingClient* getClient() { return _client; } - - void setMainWindow(GVRMainWindow* mainWindow) { _mainWindow = mainWindow; } - -protected: - void keyPressEvent(QKeyEvent* event); - -private slots: - void handleApplicationStateChange(Qt::ApplicationState state); - void idle(); -private: - void handleApplicationQuit(); - - void enterVRMode(); - void leaveVRMode(); - -#if defined(ANDROID) && defined(HAVE_LIBOVR) - ovrMobile* _ovr; - ovrHmdInfo* _hmdInfo; -#endif - - GVRMainWindow* _mainWindow; - - RenderingClient* _client; - bool _inVRMode; -}; - -#endif // hifi_GVRInterface_h diff --git a/gvr-interface/src/GVRMainWindow.cpp b/gvr-interface/src/GVRMainWindow.cpp deleted file mode 100644 index 5495354233..0000000000 --- a/gvr-interface/src/GVRMainWindow.cpp +++ /dev/null @@ -1,176 +0,0 @@ -// -// GVRMainWindow.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "GVRMainWindow.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef ANDROID - -#include - -#elif defined(HAVE_LIBOVR) - -#include - -const float LIBOVR_DOUBLE_TAP_DURATION = 0.25f; -const float LIBOVR_LONG_PRESS_DURATION = 0.75f; - -#endif - -#include - -#include "InterfaceView.h" -#include "LoginDialog.h" -#include "RenderingClient.h" - - - -GVRMainWindow::GVRMainWindow(QWidget* parent) : - QMainWindow(parent), -#if defined(ANDROID) && defined(HAVE_LIBOVR) - _backKeyState(LIBOVR_DOUBLE_TAP_DURATION, LIBOVR_LONG_PRESS_DURATION), - _wasBackKeyDown(false), -#endif - _mainLayout(NULL), - _menuBar(NULL), - _loginAction(NULL) -{ - -#ifndef ANDROID - const int NOTE_4_WIDTH = 2560; - const int NOTE_4_HEIGHT = 1440; - setFixedSize(NOTE_4_WIDTH / 2, NOTE_4_HEIGHT / 2); -#endif - - setupMenuBar(); - - QWidget* baseWidget = new QWidget(this); - - // setup a layout so we can vertically align to top - _mainLayout = new QVBoxLayout(baseWidget); - _mainLayout->setAlignment(Qt::AlignTop); - - // set the layout on the base widget - baseWidget->setLayout(_mainLayout); - - setCentralWidget(baseWidget); - - // add the interface view - new InterfaceView(baseWidget); -} - -GVRMainWindow::~GVRMainWindow() { - delete _menuBar; -} - -void GVRMainWindow::keyPressEvent(QKeyEvent* event) { -#ifdef ANDROID - if (event->key() == Qt::Key_Back) { - // got the Android back key, hand off to OVR KeyState - _backKeyState.HandleEvent(ovr_GetTimeInSeconds(), true, (_wasBackKeyDown ? 1 : 0)); - _wasBackKeyDown = true; - return; - } -#endif - QWidget::keyPressEvent(event); -} - -void GVRMainWindow::keyReleaseEvent(QKeyEvent* event) { -#ifdef ANDROID - if (event->key() == Qt::Key_Back) { - // release on the Android back key, hand off to OVR KeyState - _backKeyState.HandleEvent(ovr_GetTimeInSeconds(), false, 0); - _wasBackKeyDown = false; - } -#endif - QWidget::keyReleaseEvent(event); -} - -void GVRMainWindow::setupMenuBar() { - QMenu* fileMenu = new QMenu("File"); - QMenu* helpMenu = new QMenu("Help"); - - _menuBar = new QMenuBar(0); - - _menuBar->addMenu(fileMenu); - _menuBar->addMenu(helpMenu); - - QAction* goToAddress = new QAction("Go to Address", fileMenu); - connect(goToAddress, &QAction::triggered, this, &GVRMainWindow::showAddressBar); - fileMenu->addAction(goToAddress); - - _loginAction = new QAction("Login", fileMenu); - fileMenu->addAction(_loginAction); - - // change the login action depending on our logged in/out state - AccountManager& accountManager = AccountManager::getInstance(); - connect(&accountManager, &AccountManager::loginComplete, this, &GVRMainWindow::refreshLoginAction); - connect(&accountManager, &AccountManager::logoutComplete, this, &GVRMainWindow::refreshLoginAction); - - // refresh the state now - refreshLoginAction(); - - QAction* aboutQt = new QAction("About Qt", helpMenu); - connect(aboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); - helpMenu->addAction(aboutQt); - - setMenuBar(_menuBar); -} - -void GVRMainWindow::showAddressBar() { - // setup the address QInputDialog - QInputDialog* addressDialog = new QInputDialog(this); - addressDialog->setLabelText("Address"); - - // add the address dialog to the main layout - _mainLayout->addWidget(addressDialog); - - connect(addressDialog, &QInputDialog::textValueSelected, - DependencyManager::get().data(), &AddressManager::handleLookupString); -} - -void GVRMainWindow::showLoginDialog() { - LoginDialog* loginDialog = new LoginDialog(this); - - // have the acccount manager handle credentials from LoginDialog - AccountManager& accountManager = AccountManager::getInstance(); - connect(loginDialog, &LoginDialog::credentialsEntered, &accountManager, &AccountManager::requestAccessToken); - connect(&accountManager, &AccountManager::loginFailed, this, &GVRMainWindow::showLoginFailure); - - _mainLayout->addWidget(loginDialog); -} - -void GVRMainWindow::showLoginFailure() { - QMessageBox::warning(this, "Login Failed", - "Could not log in with that username and password. Please try again!"); -} - -void GVRMainWindow::refreshLoginAction() { - AccountManager& accountManager = AccountManager::getInstance(); - disconnect(_loginAction, &QAction::triggered, &accountManager, 0); - - if (accountManager.isLoggedIn()) { - _loginAction->setText("Logout"); - connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout); - } else { - _loginAction->setText("Login"); - connect(_loginAction, &QAction::triggered, this, &GVRMainWindow::showLoginDialog); - } - -} diff --git a/gvr-interface/src/GVRMainWindow.h b/gvr-interface/src/GVRMainWindow.h deleted file mode 100644 index c28c19a6c1..0000000000 --- a/gvr-interface/src/GVRMainWindow.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// GVRMainWindow.h -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_GVRMainWindow_h -#define hifi_GVRMainWindow_h - -#include - -#if defined(ANDROID) && defined(HAVE_LIBOVR) -#include -#endif - -class QKeyEvent; -class QMenuBar; -class QVBoxLayout; - -class GVRMainWindow : public QMainWindow { - Q_OBJECT -public: - GVRMainWindow(QWidget* parent = 0); - ~GVRMainWindow(); -public slots: - void showAddressBar(); - void showLoginDialog(); - - void showLoginFailure(); - -#if defined(ANDROID) && defined(HAVE_LIBOVR) - OVR::KeyState& getBackKeyState() { return _backKeyState; } -#endif - -protected: - void keyPressEvent(QKeyEvent* event); - void keyReleaseEvent(QKeyEvent* event); -private slots: - void refreshLoginAction(); -private: - void setupMenuBar(); - -#if defined(ANDROID) && defined(HAVE_LIBOVR) - OVR::KeyState _backKeyState; - bool _wasBackKeyDown; -#endif - - QVBoxLayout* _mainLayout; - QMenuBar* _menuBar; - QAction* _loginAction; -}; - -#endif // hifi_GVRMainWindow_h diff --git a/gvr-interface/src/InterfaceView.cpp b/gvr-interface/src/InterfaceView.cpp deleted file mode 100644 index e7992d3921..0000000000 --- a/gvr-interface/src/InterfaceView.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// InterfaceView.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 1/28/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "InterfaceView.h" - -InterfaceView::InterfaceView(QWidget* parent, Qt::WindowFlags flags) : - QOpenGLWidget(parent, flags) -{ - -} \ No newline at end of file diff --git a/gvr-interface/src/InterfaceView.h b/gvr-interface/src/InterfaceView.h deleted file mode 100644 index 3d358a3e64..0000000000 --- a/gvr-interface/src/InterfaceView.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// InterfaceView.h -// gvr-interface/src -// -// Created by Stephen Birarda on 1/28/14. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_InterfaceView_h -#define hifi_InterfaceView_h - -#include - -class InterfaceView : public QOpenGLWidget { - Q_OBJECT -public: - InterfaceView(QWidget* parent = 0, Qt::WindowFlags flags = 0); -}; - -#endif // hifi_InterfaceView_h diff --git a/gvr-interface/src/LoginDialog.cpp b/gvr-interface/src/LoginDialog.cpp deleted file mode 100644 index d4efd425bd..0000000000 --- a/gvr-interface/src/LoginDialog.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// LoginDialog.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 2015-02-03. -// Copyright 2015 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 "LoginDialog.h" - -#include -#include -#include -#include -#include - -LoginDialog::LoginDialog(QWidget* parent) : - QDialog(parent) -{ - setupGUI(); - setWindowTitle("Login"); - setModal(true); -} - -void LoginDialog::setupGUI() { - // setup a grid layout - QGridLayout* formGridLayout = new QGridLayout(this); - - _usernameLineEdit = new QLineEdit(this); - - QLabel* usernameLabel = new QLabel(this); - usernameLabel->setText("Username"); - usernameLabel->setBuddy(_usernameLineEdit); - - formGridLayout->addWidget(usernameLabel, 0, 0); - formGridLayout->addWidget(_usernameLineEdit, 1, 0); - - _passwordLineEdit = new QLineEdit(this); - _passwordLineEdit->setEchoMode(QLineEdit::Password); - - QLabel* passwordLabel = new QLabel(this); - passwordLabel->setText("Password"); - passwordLabel->setBuddy(_passwordLineEdit); - - formGridLayout->addWidget(passwordLabel, 2, 0); - formGridLayout->addWidget(_passwordLineEdit, 3, 0); - - QDialogButtonBox* buttons = new QDialogButtonBox(this); - - QPushButton* okButton = buttons->addButton(QDialogButtonBox::Ok); - QPushButton* cancelButton = buttons->addButton(QDialogButtonBox::Cancel); - - okButton->setText("Login"); - - connect(cancelButton, &QPushButton::clicked, this, &QDialog::close); - connect(okButton, &QPushButton::clicked, this, &LoginDialog::loginButtonClicked); - - formGridLayout->addWidget(buttons, 4, 0, 1, 2); - - setLayout(formGridLayout); -} - -void LoginDialog::loginButtonClicked() { - emit credentialsEntered(_usernameLineEdit->text(), _passwordLineEdit->text()); - close(); -} diff --git a/gvr-interface/src/LoginDialog.h b/gvr-interface/src/LoginDialog.h deleted file mode 100644 index 13f630818d..0000000000 --- a/gvr-interface/src/LoginDialog.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// LoginDialog.h -// gvr-interface/src -// -// Created by Stephen Birarda on 2015-02-03. -// Copyright 2015 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_LoginDialog_h -#define hifi_LoginDialog_h - -#include - -class QLineEdit; - -class LoginDialog : public QDialog { - Q_OBJECT -public: - LoginDialog(QWidget* parent = 0); -signals: - void credentialsEntered(const QString& username, const QString& password); -private slots: - void loginButtonClicked(); -private: - void setupGUI(); - - QLineEdit* _usernameLineEdit; - QLineEdit* _passwordLineEdit; -}; - -#endif // hifi_LoginDialog_h \ No newline at end of file diff --git a/gvr-interface/src/RenderingClient.cpp b/gvr-interface/src/RenderingClient.cpp deleted file mode 100644 index 4c691a48e6..0000000000 --- a/gvr-interface/src/RenderingClient.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// -// RenderingClient.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/15. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "RenderingClient.h" - -#include -#include - -#include -#include -#include -#include - -RenderingClient* RenderingClient::_instance = NULL; - -RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString) : - Client(parent) -{ - _instance = this; - - // connect to AddressManager and pass it the launch URL, if we have one - auto addressManager = DependencyManager::get(); - connect(addressManager.data(), &AddressManager::locationChangeRequired, this, &RenderingClient::goToLocation); - addressManager->loadSettings(launchURLString); - - // tell the NodeList which node types all rendering clients will want to know about - DependencyManager::get()->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer); - - DependencyManager::set(); - - // get our audio client setup on its own thread - auto audioClient = DependencyManager::set(); - audioClient->setPositionGetter(getPositionForAudio); - audioClient->setOrientationGetter(getOrientationForAudio); - audioClient->startThread(); - - - connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket); - _avatarTimer.setInterval(16); // 60 FPS - _avatarTimer.start(); - _fakeAvatar.setDisplayName("GearVR"); - _fakeAvatar.setFaceModelURL(QUrl(DEFAULT_HEAD_MODEL_URL)); - _fakeAvatar.setSkeletonModelURL(QUrl(DEFAULT_BODY_MODEL_URL)); - _fakeAvatar.toByteArray(); // Creates HeadData -} - -void RenderingClient::sendAvatarPacket() { - _fakeAvatar.setPosition(_position); - _fakeAvatar.setHeadOrientation(_orientation); - - QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData); - packet.append(_fakeAvatar.toByteArray()); - DependencyManager::get()->broadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer); - _fakeAvatar.sendIdentityPacket(); -} - -void RenderingClient::cleanupBeforeQuit() { - DependencyManager::get()->cleanupBeforeQuit(); - // destroy the AudioClient so it and its thread will safely go down - DependencyManager::destroy(); -} - -void RenderingClient::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) { - auto nodeList = DependencyManager::get(); - PacketType incomingType = packetTypeForPacket(incomingPacket); - - switch (incomingType) { - case PacketTypeAudioEnvironment: - case PacketTypeAudioStreamStats: - case PacketTypeMixedAudio: - case PacketTypeSilentAudioFrame: { - - if (incomingType == PacketTypeAudioStreamStats) { - QMetaObject::invokeMethod(DependencyManager::get().data(), "parseAudioStreamStatsPacket", - Qt::QueuedConnection, - Q_ARG(QByteArray, incomingPacket)); - } else if (incomingType == PacketTypeAudioEnvironment) { - QMetaObject::invokeMethod(DependencyManager::get().data(), "parseAudioEnvironmentData", - Qt::QueuedConnection, - Q_ARG(QByteArray, incomingPacket)); - } else { - QMetaObject::invokeMethod(DependencyManager::get().data(), "addReceivedAudioToStream", - Qt::QueuedConnection, - Q_ARG(QByteArray, incomingPacket)); - } - - // update having heard from the audio-mixer and record the bytes received - SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket); - - if (audioMixer) { - audioMixer->setLastHeardMicrostamp(usecTimestampNow()); - } - - break; - } - case PacketTypeBulkAvatarData: - case PacketTypeKillAvatar: - case PacketTypeAvatarIdentity: - case PacketTypeAvatarBillboard: { - // update having heard from the avatar-mixer and record the bytes received - SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket); - - if (avatarMixer) { - avatarMixer->setLastHeardMicrostamp(usecTimestampNow()); - - QMetaObject::invokeMethod(DependencyManager::get().data(), - "processAvatarMixerDatagram", - Q_ARG(const QByteArray&, incomingPacket), - Q_ARG(const QWeakPointer&, avatarMixer)); - } - break; - } - default: - Client::processVerifiedPacket(senderSockAddr, incomingPacket); - break; - } -} - -void RenderingClient::goToLocation(const glm::vec3& newPosition, - bool hasOrientationChange, const glm::quat& newOrientation, - bool shouldFaceLocation) { - qDebug().nospace() << "RenderingClient goToLocation - moving to " << newPosition.x << ", " - << newPosition.y << ", " << newPosition.z; - - glm::vec3 shiftedPosition = newPosition; - - if (hasOrientationChange) { - qDebug().nospace() << "RenderingClient goToLocation - new orientation is " - << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w; - - // orient the user to face the target - glm::quat quatOrientation = newOrientation; - - if (shouldFaceLocation) { - - quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - - // move the user a couple units away - const float DISTANCE_TO_USER = 2.0f; - shiftedPosition = newPosition - quatOrientation * glm::vec3( 0.0f, 0.0f,-1.0f) * DISTANCE_TO_USER; - } - - _orientation = quatOrientation; - } - - _position = shiftedPosition; - -} diff --git a/gvr-interface/src/RenderingClient.h b/gvr-interface/src/RenderingClient.h deleted file mode 100644 index c4724bc086..0000000000 --- a/gvr-interface/src/RenderingClient.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// RenderingClient.h -// gvr-interface/src -// -// Created by Stephen Birarda on 1/20/15. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#ifndef hifi_RenderingClient_h -#define hifi_RenderingClient_h - -#include -#include - -#include - -#include - -#include "Client.h" - -class RenderingClient : public Client { - Q_OBJECT -public: - RenderingClient(QObject* parent = 0, const QString& launchURLString = QString()); - - const glm::vec3& getPosition() const { return _position; } - const glm::quat& getOrientation() const { return _orientation; } - void setOrientation(const glm::quat& orientation) { _orientation = orientation; } - - static glm::vec3 getPositionForAudio() { return _instance->getPosition(); } - static glm::quat getOrientationForAudio() { return _instance->getOrientation(); } - - virtual void cleanupBeforeQuit(); - -private slots: - void goToLocation(const glm::vec3& newPosition, - bool hasOrientationChange, const glm::quat& newOrientation, - bool shouldFaceLocation); - void sendAvatarPacket(); - -private: - virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket); - - static RenderingClient* _instance; - - glm::vec3 _position; - glm::quat _orientation; - - QTimer _avatarTimer; - AvatarData _fakeAvatar; -}; - -#endif // hifi_RenderingClient_h diff --git a/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java deleted file mode 100644 index c7cbdd3dff..0000000000 --- a/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java +++ /dev/null @@ -1,41 +0,0 @@ -// -// InterfaceActivity.java -// gvr-interface/java -// -// Created by Stephen Birarda on 1/26/15. -// Copyright 2015 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 -// - -package io.highfidelity.gvrinterface; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.WindowManager; -import android.util.Log; -import org.qtproject.qt5.android.bindings.QtActivity; - -public class InterfaceActivity extends QtActivity { - - public static native void handleHifiURL(String hifiURLString); - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - // Get the intent that started this activity in case we have a hifi:// URL to parse - Intent intent = getIntent(); - if (intent.getAction() == Intent.ACTION_VIEW) { - Uri data = intent.getData(); - - if (data.getScheme().equals("hifi")) { - handleHifiURL(data.toString()); - } - } - - } -} \ No newline at end of file diff --git a/gvr-interface/src/main.cpp b/gvr-interface/src/main.cpp deleted file mode 100644 index 26576393fb..0000000000 --- a/gvr-interface/src/main.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// main.cpp -// gvr-interface/src -// -// Created by Stephen Birarda on 11/17/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 "GVRMainWindow.h" -#include "GVRInterface.h" - -int main(int argc, char* argv[]) { - GVRInterface app(argc, argv); - - GVRMainWindow mainWindow; -#ifdef ANDROID - mainWindow.showFullScreen(); -#else - mainWindow.showMaximized(); -#endif - - app.setMainWindow(&mainWindow); - - return app.exec(); -} \ No newline at end of file diff --git a/gvr-interface/templates/InterfaceBetaActivity.java.in b/gvr-interface/templates/InterfaceBetaActivity.java.in deleted file mode 100644 index 6698cfa409..0000000000 --- a/gvr-interface/templates/InterfaceBetaActivity.java.in +++ /dev/null @@ -1,51 +0,0 @@ -// -// InterfaceBetaActivity.java -// gvr-interface/java -// -// Created by Stephen Birarda on 1/27/15. -// Copyright 2015 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 -// - -package io.highfidelity.gvrinterface; - -import android.os.Bundle; -import net.hockeyapp.android.CrashManager; -import net.hockeyapp.android.UpdateManager; - -public class InterfaceBetaActivity extends InterfaceActivity { - - public String _hockeyAppID; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - _hockeyAppID = getString(R.string.HockeyAppID); - - checkForUpdates(); - } - - @Override - protected void onPause() { - super.onPause(); - UpdateManager.unregister(); - } - - @Override - protected void onResume() { - super.onResume(); - checkForCrashes(); - } - - private void checkForCrashes() { - CrashManager.register(this, _hockeyAppID); - } - - private void checkForUpdates() { - // Remove this for store / production builds! - UpdateManager.register(this, _hockeyAppID); - } -} \ No newline at end of file diff --git a/gvr-interface/templates/hockeyapp.xml.in b/gvr-interface/templates/hockeyapp.xml.in deleted file mode 100644 index edf2d0a8aa..0000000000 --- a/gvr-interface/templates/hockeyapp.xml.in +++ /dev/null @@ -1,5 +0,0 @@ - - - ${HOCKEY_APP_ID} - ${HOCKEY_APP_ENABLED} - diff --git a/hifi_android.py b/hifi_android.py index 13c9cdccf2..2e6a42d127 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -52,6 +52,13 @@ ANDROID_PACKAGES = { 'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release', 'includeLibs': ['libvrapi.so'] }, + 'oculusPlatform': { + 'file': 'OVRPlatformSDK_v1.32.0.zip', + 'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL', + 'checksum': 'ab5b203b3a39a56ab148d68fff769e05', + 'sharedLibFolder': 'Android/libs/arm64-v8a', + 'includeLibs': ['libovrplatformloader.so'] + }, 'openssl': { 'file': 'openssl-1.1.0g_armv8.tgz', 'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW', diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index c013cfacd3..74e25662c7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -211,7 +211,10 @@ link_hifi_libraries( render-utils entities-renderer avatars-renderer ui qml auto-updater midi controllers plugins image trackers ui-plugins display-plugins input-plugins + # Platform specific GL libraries ${PLATFORM_GL_BACKEND} + # Plaform specific input & display plugin libraries + ${PLATFORM_PLUGIN_LIBRARIES} shaders ) @@ -265,7 +268,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) endforeach() # include headers for interface and InterfaceConfig. -include_directories("${PROJECT_SOURCE_DIR}/src") +target_include_directories(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/src") if (ANDROID) find_library(ANDROID_LOG_LIB log) @@ -330,7 +333,11 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" - # add redirect json to macOS builds. + #copy serverless for android + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/resources/serverless" + "${RESOURCES_DEV_DIR}/serverless" + # add redirect json to macOS builds. COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${PROJECT_SOURCE_DIR}/resources/serverless/redirect.json" "${RESOURCES_DEV_DIR}/serverless/redirect.json" diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android_interface/backward.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/backward.svg rename to interface/resources/icons/+android_interface/backward.svg diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android_interface/bubble-a.svg similarity index 100% rename from interface/resources/icons/+android/bubble-a.svg rename to interface/resources/icons/+android_interface/bubble-a.svg diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android_interface/bubble-i.svg similarity index 100% rename from interface/resources/icons/+android/bubble-i.svg rename to interface/resources/icons/+android_interface/bubble-i.svg diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android_interface/button-a.svg similarity index 100% rename from interface/resources/icons/+android/button-a.svg rename to interface/resources/icons/+android_interface/button-a.svg diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android_interface/button.svg similarity index 100% rename from interface/resources/icons/+android/button.svg rename to interface/resources/icons/+android_interface/button.svg diff --git a/interface/resources/icons/+android/forward.svg b/interface/resources/icons/+android_interface/forward.svg similarity index 100% rename from interface/resources/icons/+android/forward.svg rename to interface/resources/icons/+android_interface/forward.svg diff --git a/interface/resources/icons/+android/go-a.svg b/interface/resources/icons/+android_interface/go-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/go-a.svg rename to interface/resources/icons/+android_interface/go-a.svg diff --git a/interface/resources/icons/+android/go-i.svg b/interface/resources/icons/+android_interface/go-i.svg similarity index 100% rename from interface/resources/icons/+android/go-i.svg rename to interface/resources/icons/+android_interface/go-i.svg diff --git a/interface/resources/icons/+android/hand.svg b/interface/resources/icons/+android_interface/hand.svg similarity index 100% rename from interface/resources/icons/+android/hand.svg rename to interface/resources/icons/+android_interface/hand.svg diff --git a/interface/resources/icons/+android/hide.svg b/interface/resources/icons/+android_interface/hide.svg similarity index 100% rename from interface/resources/icons/+android/hide.svg rename to interface/resources/icons/+android_interface/hide.svg diff --git a/interface/resources/icons/+android/mic-mute-a.svg b/interface/resources/icons/+android_interface/mic-mute-a.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-a.svg rename to interface/resources/icons/+android_interface/mic-mute-a.svg diff --git a/interface/resources/icons/+android/mic-mute-i.svg b/interface/resources/icons/+android_interface/mic-mute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-i.svg rename to interface/resources/icons/+android_interface/mic-mute-i.svg diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android_interface/mic-unmute-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/mic-unmute-a.svg rename to interface/resources/icons/+android_interface/mic-unmute-a.svg diff --git a/interface/resources/icons/+android/mic-unmute-i.svg b/interface/resources/icons/+android_interface/mic-unmute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-unmute-i.svg rename to interface/resources/icons/+android_interface/mic-unmute-i.svg diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android_interface/myview-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-a.svg rename to interface/resources/icons/+android_interface/myview-a.svg diff --git a/interface/resources/icons/+android/myview-hover.svg b/interface/resources/icons/+android_interface/myview-hover.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-hover.svg rename to interface/resources/icons/+android_interface/myview-hover.svg diff --git a/interface/resources/icons/+android/myview-i.svg b/interface/resources/icons/+android_interface/myview-i.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-i.svg rename to interface/resources/icons/+android_interface/myview-i.svg diff --git a/interface/resources/icons/+android/show-up.svg b/interface/resources/icons/+android_interface/show-up.svg similarity index 100% rename from interface/resources/icons/+android/show-up.svg rename to interface/resources/icons/+android_interface/show-up.svg diff --git a/interface/resources/icons/+android/stats.svg b/interface/resources/icons/+android_interface/stats.svg similarity index 100% rename from interface/resources/icons/+android/stats.svg rename to interface/resources/icons/+android_interface/stats.svg diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android_interface/tick.svg similarity index 100% rename from interface/resources/icons/+android/tick.svg rename to interface/resources/icons/+android_interface/tick.svg diff --git a/interface/resources/images/unsupportedImage.png b/interface/resources/images/unsupportedImage.png new file mode 100644 index 0000000000..87d238b67c Binary files /dev/null and b/interface/resources/images/unsupportedImage.png differ diff --git a/interface/resources/images/whitePixel.png b/interface/resources/images/whitePixel.png new file mode 100644 index 0000000000..fe2ec44cb7 Binary files /dev/null and b/interface/resources/images/whitePixel.png differ diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index b47faeb8a8..aa679e319a 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -10,10 +10,6 @@ joint = jointRoot = Hips joint = jointLeftHand = LeftHand joint = jointRightHand = RightHand joint = jointHead = Head -freeJoint = LeftArm -freeJoint = LeftForeArm -freeJoint = RightArm -freeJoint = RightForeArm bs = JawOpen = mouth_Open = 1 bs = LipsFunnel = Oo = 1 bs = BrowsU_L = brow_Up = 1 diff --git a/interface/resources/qml/+android/StatText.qml b/interface/resources/qml/+android_interface/StatText.qml similarity index 100% rename from interface/resources/qml/+android/StatText.qml rename to interface/resources/qml/+android_interface/StatText.qml diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml similarity index 100% rename from interface/resources/qml/+android/Stats.qml rename to interface/resources/qml/+android_interface/Stats.qml diff --git a/interface/resources/qml/+android/Web3DSurface.qml b/interface/resources/qml/+android_interface/Web3DSurface.qml similarity index 98% rename from interface/resources/qml/+android/Web3DSurface.qml rename to interface/resources/qml/+android_interface/Web3DSurface.qml index d7b8306d6c..c4a613222e 100644 --- a/interface/resources/qml/+android/Web3DSurface.qml +++ b/interface/resources/qml/+android_interface/Web3DSurface.qml @@ -1,5 +1,5 @@ // -// Web3DOverlay.qml +// Web3DSurface.qml // // Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 // Copyright 2016 High Fidelity, Inc. diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml new file mode 100644 index 0000000000..5c5cf2cfb9 --- /dev/null +++ b/interface/resources/qml/+webengine/BrowserWebView.qml @@ -0,0 +1,58 @@ +import QtQuick 2.5 +import QtWebChannel 1.0 +import QtWebEngine 1.5 + +import controlsUit 1.0 + +WebView { + id: webview + url: "https://highfidelity.com/" + profile: FileTypeProfile; + + property var parentRoot: null + + // Create a global EventBridge object for raiseAndLowerKeyboard. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.Deferred + worldId: WebEngineScript.MainWorld + } + + // Detect when may want to raise and lower keyboard. + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + + onFeaturePermissionRequested: { + if (feature == 2) { // QWebEnginePage::MediaAudioCapture + grantFeaturePermission(securityOrigin, feature, true); + } else { + permissionsBar.securityOrigin = securityOrigin; + permissionsBar.feature = feature; + parentRoot.showPermissionsBar(); + } + } + + onLoadingChanged: { + if (loadRequest.status === WebEngineView.LoadSucceededStatus) { + addressBar.text = loadRequest.url + } + parentRoot.loadingChanged(loadRequest.status); + } + + onWindowCloseRequested: { + parentRoot.destroy(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + desktop.initWebviewProfileHandlers(webview.profile); + } +} diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml new file mode 100644 index 0000000000..d2f1820e9a --- /dev/null +++ b/interface/resources/qml/+webengine/QmlWebWindowView.qml @@ -0,0 +1,53 @@ +import QtQuick 2.5 +import QtWebEngine 1.1 +import QtWebChannel 1.0 + +import controlsUit 1.0 as Controls +import stylesUit 1.0 +Controls.WebView { + id: webview + url: "about:blank" + anchors.fill: parent + focus: true + profile: HFWebEngineProfile; + + property string userScriptUrl: "" + + // Create a global EventBridge object for raiseAndLowerKeyboard. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // Detect when may want to raise and lower keyboard. + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webview.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + function onWebEventReceived(event) { + if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); + } + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + eventBridge.webEventReceived.connect(onWebEventReceived); + } +} diff --git a/interface/resources/qml/+webengine/TabletBrowser.qml b/interface/resources/qml/+webengine/TabletBrowser.qml new file mode 100644 index 0000000000..720a904231 --- /dev/null +++ b/interface/resources/qml/+webengine/TabletBrowser.qml @@ -0,0 +1,125 @@ +import QtQuick 2.5 +import QtWebChannel 1.0 +import QtWebEngine 1.5 + +import "controls" +import controlsUit 1.0 as HifiControls +import "styles" as HifiStyles +import stylesUit 1.0 +import "windows" + +Item { + id: root + HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifistyles } + + height: 600 + property variant permissionsBar: {'securityOrigin':'none','feature':'none'} + property alias url: webview.url + + property bool canGoBack: webview.canGoBack + property bool canGoForward: webview.canGoForward + + + signal loadingChanged(int status) + + x: 0 + y: 0 + + function setProfile(profile) { + webview.profile = profile; + } + + WebEngineView { + id: webview + objectName: "webEngineView" + x: 0 + y: 0 + width: parent.width + height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height + + profile: HFWebEngineProfile; + + property string userScriptUrl: "" + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webview.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + property string newUrl: "" + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + + // Ensure the JS from the web-engine makes it to our logging + webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); + }); + + webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; + web.address = url; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onLoadingChanged: { + keyboardRaised = false; + punctuationMode = false; + keyboard.resetShiftMode(false); + + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + urlAppend(loadRequest.url.toString()) + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } + } + } + } + + onNewViewRequested: { + request.openIn(webView); + } + + HifiControls.WebSpinner { } + } + + Keys.onPressed: { + switch(event.key) { + case Qt.Key_L: + if (event.modifiers == Qt.ControlModifier) { + event.accepted = true + addressBar.selectAll() + addressBar.forceActiveFocus() + } + break; + } + } +} diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 4ddee15a10..496209a2a8 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,16 +1,13 @@ import QtQuick 2.5 -import QtWebChannel 1.0 -import QtWebEngine 1.5 - import controlsUit 1.0 -import "styles" as HifiStyles import stylesUit 1.0 import "windows" +import "." ScrollingWindow { id: root HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifistyles } + title: "Browser" resizable: true destroyOnHidden: true @@ -68,7 +65,7 @@ ScrollingWindow { id: back; enabled: webview.canGoBack; text: hifi.glyphs.backward - color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + color: enabled ? hifi.colors.text : hifi.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goBack() } } @@ -77,7 +74,7 @@ ScrollingWindow { id: forward; enabled: webview.canGoForward; text: hifi.glyphs.forward - color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + color: enabled ? hifi.colors.text : hifi.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } @@ -86,7 +83,7 @@ ScrollingWindow { id: reload; enabled: webview.canGoForward; text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload - color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText + color: enabled ? hifi.colors.text : hifi.colors.disabledText size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } @@ -202,61 +199,15 @@ ScrollingWindow { } } - WebView { + BrowserWebView { id: webview - url: "https://highfidelity.com/" - profile: FileTypeProfile; - - // Create a global EventBridge object for raiseAndLowerKeyboard. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.Deferred - worldId: WebEngineScript.MainWorld - } - - // Detect when may want to raise and lower keyboard. - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + parentRoot: root anchors.top: buttons.bottom anchors.topMargin: 8 anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right - - onFeaturePermissionRequested: { - if (feature == 2) { // QWebEnginePage::MediaAudioCapture - grantFeaturePermission(securityOrigin, feature, true); - } else { - permissionsBar.securityOrigin = securityOrigin; - permissionsBar.feature = feature; - root.showPermissionsBar(); - } - } - - onLoadingChanged: { - if (loadRequest.status === WebEngineView.LoadSucceededStatus) { - addressBar.text = loadRequest.url - } - root.loadingChanged(loadRequest.status); - } - - onWindowCloseRequested: { - root.destroy(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - desktop.initWebviewProfileHandlers(webview.profile); - } } } // item diff --git a/interface/resources/qml/BrowserWebView.qml b/interface/resources/qml/BrowserWebView.qml new file mode 100644 index 0000000000..6db016c284 --- /dev/null +++ b/interface/resources/qml/BrowserWebView.qml @@ -0,0 +1,8 @@ +import QtQuick 2.5 +import controlsUit 1.0 + +ProxyWebView { + property var parentRoot: null + + function grantFeaturePermission(origin, feature) {} +} diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index 8c5900b4c3..2fd0ddf925 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -19,13 +19,12 @@ Windows.ScrollingWindow { width: 800 height: 800 resizable: true - Hifi.InfoView { id: infoView width: pane.contentWidth implicitHeight: pane.scrollHeight - WebView { + BaseWebView { id: webview objectName: "WebView" anchors.fill: parent diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml similarity index 100% rename from interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml rename to interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml similarity index 100% rename from interface/resources/qml/LoginDialog/+android/SignUpBody.qml rename to interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 322535641d..7ee4b1c50c 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -9,10 +9,9 @@ // import QtQuick 2.5 -import QtWebEngine 1.1 -import QtWebChannel 1.0 import "windows" as Windows +import "." import controlsUit 1.0 as Controls import stylesUit 1.0 @@ -57,52 +56,8 @@ Windows.ScrollingWindow { width: pane.contentWidth implicitHeight: pane.scrollHeight - Controls.WebView { + QmlWebWindowView { id: webview - url: "about:blank" - anchors.fill: parent - focus: true - profile: HFWebEngineProfile; - - property string userScriptUrl: "" - - // Create a global EventBridge object for raiseAndLowerKeyboard. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // Detect when may want to raise and lower keyboard. - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webview.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - function onWebEventReceived(event) { - if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); - } - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - eventBridge.webEventReceived.connect(onWebEventReceived); - } } } } diff --git a/interface/resources/qml/QmlWebWindowView.qml b/interface/resources/qml/QmlWebWindowView.qml new file mode 100644 index 0000000000..9210468ae2 --- /dev/null +++ b/interface/resources/qml/QmlWebWindowView.qml @@ -0,0 +1,5 @@ +import QtQuick 2.5 +import controlsUit 1.0 + +BaseWebView { +} diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index a65170ee3b..3b703d72e6 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -129,11 +129,11 @@ Item { } StatText { visible: root.expanded - text: "Intersection calls: Entities/Overlays/Avatars/HUD\n " + - "Styluses:\t" + root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "/" + root.stylusPicksUpdated.w + "\n " + - "Rays:\t" + root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "/" + root.rayPicksUpdated.w + "\n " + - "Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "/" + root.parabolaPicksUpdated.w + "\n " + - "Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z + "/" + root.collisionPicksUpdated.w + text: "Intersection calls: Entities/Avatars/HUD\n " + + "Styluses:\t" + root.stylusPicksUpdated.x + "/" + root.stylusPicksUpdated.y + "/" + root.stylusPicksUpdated.z + "\n " + + "Rays:\t" + root.rayPicksUpdated.x + "/" + root.rayPicksUpdated.y + "/" + root.rayPicksUpdated.z + "\n " + + "Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "\n " + + "Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z } } } diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 720a904231..f83a9c81a5 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -1,17 +1,11 @@ import QtQuick 2.5 -import QtWebChannel 1.0 -import QtWebEngine 1.5 -import "controls" import controlsUit 1.0 as HifiControls -import "styles" as HifiStyles import stylesUit 1.0 -import "windows" Item { id: root HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifistyles } height: 600 property variant permissionsBar: {'securityOrigin':'none','feature':'none'} @@ -30,96 +24,9 @@ Item { webview.profile = profile; } - WebEngineView { + HifiControls.ProxyWebView { id: webview - objectName: "webEngineView" - x: 0 - y: 0 width: parent.width - height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height - - profile: HFWebEngineProfile; - - property string userScriptUrl: "" - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webview.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - property string newUrl: "" - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - - // Ensure the JS from the web-engine makes it to our logging - webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); - }); - - webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; - web.address = url; - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onLoadingChanged: { - keyboardRaised = false; - punctuationMode = false; - keyboard.resetShiftMode(false); - - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - urlAppend(loadRequest.url.toString()) - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); - } - } - } - } - - onNewViewRequested: { - request.openIn(webView); - } - - HifiControls.WebSpinner { } - } - - Keys.onPressed: { - switch(event.key) { - case Qt.Key_L: - if (event.modifiers == Qt.ControlModifier) { - event.accepted = true - addressBar.selectAll() - addressBar.forceActiveFocus() - } - break; - } + height: parent.height } } diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index fdd5d8a7c6..32c19daf14 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -1,5 +1,5 @@ // -// Web3DOverlay.qml +// Web3DSurface.qml // // Created by David Rowe on 16 Dec 2016. // Copyright 2016 High Fidelity, Inc. diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml new file mode 100644 index 0000000000..823d0107a2 --- /dev/null +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -0,0 +1,189 @@ +import QtQuick 2.7 +import QtWebEngine 1.5 +import QtWebChannel 1.0 + +import QtQuick.Controls 2.2 + +import stylesUit 1.0 as StylesUIt + +Item { + id: flick + + property alias url: webViewCore.url + property alias canGoBack: webViewCore.canGoBack + property alias webViewCore: webViewCore + property alias webViewCoreProfile: webViewCore.profile + property string webViewCoreUserAgent + + property string userScriptUrl: "" + property string urlTag: "noDownload=false"; + + signal newViewRequestedCallback(var request) + signal loadingChangedCallback(var loadRequest) + + + width: parent.width + + property bool interactive: false + + property bool blurOnCtrlShift: true + + StylesUIt.HifiConstants { + id: hifi + } + + function stop() { + webViewCore.stop(); + } + + Timer { + id: delayedUnfocuser + repeat: false + interval: 200 + onTriggered: { + + // The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing. + // Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised) + // + // if (raised) { + // item->setProperty("keyboardRaised", QVariant(!raised)); + // } + // + // item->setProperty("keyboardRaised", QVariant(raised)); + // + + webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { + console.log('unfocus completed: ', result); + }); + } + } + + function unfocus() { + delayedUnfocuser.start(); + } + + function stopUnfocus() { + delayedUnfocuser.stop(); + } + + function onLoadingChanged(loadRequest) { + if (WebEngineView.LoadStartedStatus === loadRequest.status) { + + // Required to support clicking on "hifi://" links + var url = loadRequest.url.toString(); + url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag; + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + webViewCore.stop(); + } + } + } + + if (WebEngineView.LoadFailedStatus === loadRequest.status) { + console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); + } + + if (WebEngineView.LoadSucceededStatus === loadRequest.status) { + //disable Chromium's scroll bars + } + } + + WebEngineView { + id: webViewCore + + width: parent.width + height: parent.height + + profile: HFWebEngineProfile; + settings.pluginsEnabled: true + settings.touchIconsEnabled: true + settings.allowRunningInsecureContent: true + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: flick.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + + if (webViewCoreUserAgent !== undefined) { + webViewCore.profile.httpUserAgent = webViewCoreUserAgent + } else { + webViewCore.profile.httpUserAgent += " (HighFidelityInterface)"; + } + // Ensure the JS from the web-engine makes it to our logging + webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); + }); + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } + + // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false + // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns + // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed + // when QT fixes this. + property bool safeLoading: false + property bool loadingLatched: false + property var loadingRequest: null + onLoadingChanged: { + webViewCore.loadingRequest = loadRequest; + webViewCore.safeLoading = webViewCore.loading && !loadingLatched; + webViewCore.loadingLatched |= webViewCore.loading; + } + onSafeLoadingChanged: { + flick.onLoadingChanged(webViewCore.loadingRequest) + loadingChangedCallback(webViewCore.loadingRequest) + } + } + + AnimatedImage { + //anchoring doesnt works here when changing content size + x: flick.width/2 - width/2 + y: flick.height/2 - height/2 + source: "../../icons/loader-snake-64-w.gif" + visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString()) + playing: visible + z: 10000 + } + + Keys.onPressed: { + if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) { + webViewCore.focus = false; + } + } +} diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 823d0107a2..a844c8b624 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -1,10 +1,9 @@ import QtQuick 2.7 -import QtWebEngine 1.5 -import QtWebChannel 1.0 import QtQuick.Controls 2.2 import stylesUit 1.0 as StylesUIt +import controlsUit 1.0 as ControlsUit Item { id: flick @@ -33,157 +32,21 @@ Item { } function stop() { - webViewCore.stop(); - } - Timer { - id: delayedUnfocuser - repeat: false - interval: 200 - onTriggered: { - - // The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing. - // Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised) - // - // if (raised) { - // item->setProperty("keyboardRaised", QVariant(!raised)); - // } - // - // item->setProperty("keyboardRaised", QVariant(raised)); - // - - webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { - console.log('unfocus completed: ', result); - }); - } } function unfocus() { - delayedUnfocuser.start(); } function stopUnfocus() { - delayedUnfocuser.stop(); } function onLoadingChanged(loadRequest) { - if (WebEngineView.LoadStartedStatus === loadRequest.status) { - - // Required to support clicking on "hifi://" links - var url = loadRequest.url.toString(); - url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag; - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - webViewCore.stop(); - } - } - } - - if (WebEngineView.LoadFailedStatus === loadRequest.status) { - console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); - } - - if (WebEngineView.LoadSucceededStatus === loadRequest.status) { - //disable Chromium's scroll bars - } } - WebEngineView { + ControlsUit.ProxyWebView { id: webViewCore - width: parent.width height: parent.height - - profile: HFWebEngineProfile; - settings.pluginsEnabled: true - settings.touchIconsEnabled: true - settings.allowRunningInsecureContent: true - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: flick.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - - if (webViewCoreUserAgent !== undefined) { - webViewCore.profile.httpUserAgent = webViewCoreUserAgent - } else { - webViewCore.profile.httpUserAgent += " (HighFidelityInterface)"; - } - // Ensure the JS from the web-engine makes it to our logging - webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); - }); - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - //disable popup - onContextMenuRequested: { - request.accepted = true; - } - - onNewViewRequested: { - newViewRequestedCallback(request) - } - - // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false - // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns - // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed - // when QT fixes this. - property bool safeLoading: false - property bool loadingLatched: false - property var loadingRequest: null - onLoadingChanged: { - webViewCore.loadingRequest = loadRequest; - webViewCore.safeLoading = webViewCore.loading && !loadingLatched; - webViewCore.loadingLatched |= webViewCore.loading; - } - onSafeLoadingChanged: { - flick.onLoadingChanged(webViewCore.loadingRequest) - loadingChangedCallback(webViewCore.loadingRequest) - } - } - - AnimatedImage { - //anchoring doesnt works here when changing content size - x: flick.width/2 - width/2 - y: flick.height/2 - height/2 - source: "../../icons/loader-snake-64-w.gif" - visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString()) - playing: visible - z: 10000 - } - - Keys.onPressed: { - if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) { - webViewCore.focus = false; - } } } diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 3959dbf01b..9cbbd48a22 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -1,5 +1,4 @@ import QtQuick 2.7 -import QtWebEngine 1.5 import controlsUit 1.0 as HiFiControls import "../styles" as HifiStyles import stylesUit 1.0 diff --git a/interface/resources/qml/controlsUit/+android/ImageButton.qml b/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml similarity index 100% rename from interface/resources/qml/controlsUit/+android/ImageButton.qml rename to interface/resources/qml/controlsUit/+android_interface/ImageButton.qml diff --git a/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml b/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml new file mode 100644 index 0000000000..fdd9c12220 --- /dev/null +++ b/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml @@ -0,0 +1,38 @@ +// +// WebView.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtWebEngine 1.5 + +WebEngineView { + id: root + + Component.onCompleted: { + console.log("Connecting JS messaging to Hifi Logging") + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); + }); + } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } + } + } + } + + WebSpinner { } +} diff --git a/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml new file mode 100644 index 0000000000..c2e2ca4b40 --- /dev/null +++ b/interface/resources/qml/controlsUit/+webengine/WebSpinner.qml @@ -0,0 +1,24 @@ +// +// WebSpinner.qml +// +// Created by David Rowe on 23 May 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 + +AnimatedImage { + property WebEngineView webview: parent + source: "qrc:////icons//loader-snake-64-w.gif" + visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) + playing: visible + z: 10000 + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } +} diff --git a/interface/resources/qml/controlsUit/BaseWebView.qml b/interface/resources/qml/controlsUit/BaseWebView.qml index fdd9c12220..52b2d1f3db 100644 --- a/interface/resources/qml/controlsUit/BaseWebView.qml +++ b/interface/resources/qml/controlsUit/BaseWebView.qml @@ -8,31 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.7 -import QtWebEngine 1.5 +import "." -WebEngineView { +ProxyWebView { id: root - - Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") - // Ensure the JS from the web-engine makes it to our logging - root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); - }); - } - - onLoadingChanged: { - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); - } - } - } - } - - WebSpinner { } } diff --git a/interface/resources/qml/controlsUit/ProxyWebView.qml b/interface/resources/qml/controlsUit/ProxyWebView.qml new file mode 100644 index 0000000000..2b13760962 --- /dev/null +++ b/interface/resources/qml/controlsUit/ProxyWebView.qml @@ -0,0 +1,31 @@ +import QtQuick 2.7 +import stylesUit 1.0 + +Rectangle { + HifiConstants { + id: hifi + } + + color: hifi.colors.darkGray + + signal onNewViewRequested(); + + property string url: ""; + property bool canGoBack: false + property bool canGoForward: false + property string icon: "" + property var profile: {} + + property bool safeLoading: false + property bool loadingLatched: false + property bool loading: false + property var loadingRequest: null + + + Text { + anchors.centerIn: parent + text: "This feature is not supported" + font.pixelSize: 32 + color: hifi.colors.white + } +} diff --git a/interface/resources/qml/controlsUit/WebSpinner.qml b/interface/resources/qml/controlsUit/WebSpinner.qml index e8e01c4865..bcf415e0c0 100644 --- a/interface/resources/qml/controlsUit/WebSpinner.qml +++ b/interface/resources/qml/controlsUit/WebSpinner.qml @@ -9,11 +9,15 @@ // import QtQuick 2.5 -import QtWebEngine 1.5 -AnimatedImage { - property WebEngineView webview: parent - source: "../../icons/loader-snake-64-w.gif" +Image { + Item { + id: webView + property bool loading: false + property string url: "" + } + + source: "qrc:////images//unsupportedImage.png" visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) playing: visible z: 10000 diff --git a/interface/resources/qml/controlsUit/qmldir b/interface/resources/qml/controlsUit/qmldir index d0577f5575..e1665df40e 100644 --- a/interface/resources/qml/controlsUit/qmldir +++ b/interface/resources/qml/controlsUit/qmldir @@ -29,6 +29,7 @@ TextEdit 1.0 TextEdit.qml TextField 1.0 TextField.qml ToolTip 1.0 ToolTip.qml Tree 1.0 Tree.qml +ProxyWebView 1.0 ProxyWebView.qml VerticalSpacer 1.0 VerticalSpacer.qml WebGlyphButton 1.0 WebGlyphButton.qml WebSpinner 1.0 WebSpinner.qml diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android_interface/FocusHack.qml similarity index 100% rename from interface/resources/qml/desktop/+android/FocusHack.qml rename to interface/resources/qml/desktop/+android_interface/FocusHack.qml diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/ActionBar.qml rename to interface/resources/qml/hifi/+android_interface/ActionBar.qml diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/AudioBar.qml rename to interface/resources/qml/hifi/+android_interface/AudioBar.qml diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android_interface/AvatarOption.qml similarity index 100% rename from interface/resources/qml/hifi/+android/AvatarOption.qml rename to interface/resources/qml/hifi/+android_interface/AvatarOption.qml diff --git a/interface/resources/qml/hifi/+android/Desktop.qml b/interface/resources/qml/hifi/+android_interface/Desktop.qml similarity index 100% rename from interface/resources/qml/hifi/+android/Desktop.qml rename to interface/resources/qml/hifi/+android_interface/Desktop.qml diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android_interface/HifiConstants.qml similarity index 100% rename from interface/resources/qml/hifi/+android/HifiConstants.qml rename to interface/resources/qml/hifi/+android_interface/HifiConstants.qml diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/StatsBar.qml rename to interface/resources/qml/hifi/+android_interface/StatsBar.qml diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml similarity index 100% rename from interface/resources/qml/hifi/+android/WindowHeader.qml rename to interface/resources/qml/hifi/+android_interface/WindowHeader.qml diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml similarity index 100% rename from interface/resources/qml/hifi/+android/bottomHudOptions.qml rename to interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml similarity index 100% rename from interface/resources/qml/hifi/+android/button.qml rename to interface/resources/qml/hifi/+android_interface/button.qml diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/modesbar.qml rename to interface/resources/qml/hifi/+android_interface/modesbar.qml diff --git a/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml b/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml new file mode 100644 index 0000000000..56cc38254f --- /dev/null +++ b/interface/resources/qml/hifi/+webengine/DesktopWebEngine.qml @@ -0,0 +1,44 @@ +import QtQuick 2.7 +import QtWebEngine 1.5 + +Item { + id: root + + property bool webViewProfileSetup: false + property string currentUrl: "" + property string downloadUrl: "" + property string adaptedPath: "" + property string tempDir: "" + function setupWebEngineSettings() { + WebEngine.settings.javascriptCanOpenWindows = true; + WebEngine.settings.javascriptCanAccessClipboard = false; + WebEngine.settings.spatialNavigationEnabled = false; + WebEngine.settings.localContentCanAccessRemoteUrls = true; + } + + + function initWebviewProfileHandlers(profile) { + downloadUrl = currentUrl; + if (webViewProfileSetup) return; + webViewProfileSetup = true; + + profile.downloadRequested.connect(function(download){ + adaptedPath = File.convertUrlToPath(downloadUrl); + tempDir = File.getTempDir(); + download.path = tempDir + "/" + adaptedPath; + download.accept(); + if (download.state === WebEngineDownloadItem.DownloadInterrupted) { + console.log("download failed to complete"); + } + }) + + profile.downloadFinished.connect(function(download){ + if (download.state === WebEngineDownloadItem.DownloadCompleted) { + File.runUnzip(download.path, downloadUrl, autoAdd); + } else { + console.log("The download was corrupted, state: " + download.state); + } + autoAdd = false; + }) + } +} diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 731477e2ae..c44ebdbab1 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,5 +1,4 @@ import QtQuick 2.7 -import QtWebEngine 1.5; import Qt.labs.settings 1.0 as QtSettings import QtQuick.Controls 2.3 @@ -88,43 +87,20 @@ OriginalDesktop.Desktop { })({}); Component.onCompleted: { - WebEngine.settings.javascriptCanOpenWindows = true; - WebEngine.settings.javascriptCanAccessClipboard = false; - WebEngine.settings.spatialNavigationEnabled = false; - WebEngine.settings.localContentCanAccessRemoteUrls = true; + webEngineConfig.setupWebEngineSettings(); } // Accept a download through the webview - property bool webViewProfileSetup: false - property string currentUrl: "" - property string downloadUrl: "" - property string adaptedPath: "" - property string tempDir: "" + property alias webViewProfileSetup: webEngineConfig.webViewProfileSetup + property alias currentUrl: webEngineConfig.currentUrl + property alias downloadUrl: webEngineConfig.downloadUrl + property alias adaptedPath: webEngineConfig.adaptedPath + property alias tempDir: webEngineConfig.tempDir + property var initWebviewProfileHandlers: webEngineConfig.initWebviewProfileHandlers property bool autoAdd: false - function initWebviewProfileHandlers(profile) { - downloadUrl = currentUrl; - if (webViewProfileSetup) return; - webViewProfileSetup = true; - - profile.downloadRequested.connect(function(download){ - adaptedPath = File.convertUrlToPath(downloadUrl); - tempDir = File.getTempDir(); - download.path = tempDir + "/" + adaptedPath; - download.accept(); - if (download.state === WebEngineDownloadItem.DownloadInterrupted) { - console.log("download failed to complete"); - } - }) - - profile.downloadFinished.connect(function(download){ - if (download.state === WebEngineDownloadItem.DownloadCompleted) { - File.runUnzip(download.path, downloadUrl, autoAdd); - } else { - console.log("The download was corrupted, state: " + download.state); - } - autoAdd = false; - }) + DesktopWebEngine { + id: webEngineConfig } function setAutoAdd(auto) { diff --git a/interface/resources/qml/hifi/DesktopWebEngine.qml b/interface/resources/qml/hifi/DesktopWebEngine.qml new file mode 100644 index 0000000000..58c6244e7e --- /dev/null +++ b/interface/resources/qml/hifi/DesktopWebEngine.qml @@ -0,0 +1,17 @@ +import QtQuick 2.7 + +Item { + id: root + + property bool webViewProfileSetup: false + property string currentUrl: "" + property string downloadUrl: "" + property string adaptedPath: "" + property string tempDir: "" + function setupWebEngineSettings() { + } + + + function initWebviewProfileHandlers(profile) { + } +} diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml deleted file mode 100644 index c05de26471..0000000000 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ /dev/null @@ -1,443 +0,0 @@ - -// -// WebBrowser.qml -// -// -// Created by Vlad Stelmahovsky on 06/22/2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 as QQControls -import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.0 - -import QtWebEngine 1.5 -import QtWebChannel 1.0 - -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls -import "../windows" -import "../controls" - -import HifiWeb 1.0 - -Rectangle { - id: root; - - HifiConstants { id: hifi; } - - property string title: ""; - signal sendToScript(var message); - property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false - property bool keyboardRaised: false - property bool punctuationMode: false - property var suggestionsList: [] - readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q="; - - - WebBrowserSuggestionsEngine { - id: searchEngine - - onSuggestions: { - if (suggestions.length > 0) { - suggestionsList = [] - suggestionsList.push(addressBarInput.text); //do not overwrite edit text - for(var i = 0; i < suggestions.length; i++) { - suggestionsList.push(suggestions[i]); - } - addressBar.model = suggestionsList - if (!addressBar.popup.visible) { - addressBar.popup.open(); - } - } - } - } - - Timer { - id: suggestionRequestTimer - interval: 200 - repeat: false - onTriggered: { - if (addressBar.editText !== "") { - searchEngine.querySuggestions(addressBarInput.text); - } - } - } - - color: hifi.colors.baseGray; - - function goTo(url) { - //must be valid attempt to open an site with dot - var urlNew = url - if (url.indexOf(".") > 0) { - if (url.indexOf("http") < 0) { - urlNew = "http://" + url; - } - } else { - urlNew = searchUrlTemplate + url - } - - addressBar.model = [] - //need to rebind if binfing was broken by selecting from suggestions - addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); - webStack.currentItem.webEngineView.url = urlNew - suggestionRequestTimer.stop(); - addressBar.popup.close(); - } - - Column { - spacing: 2 - width: parent.width; - - RowLayout { - id: addressBarRow - width: parent.width; - height: 48 - - HifiControls.WebGlyphButton { - enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1 - glyph: hifi.glyphs.backward; - anchors.verticalCenter: parent.verticalCenter; - size: 38; - onClicked: { - if (webStack.currentItem.webEngineView.canGoBack) { - webStack.currentItem.webEngineView.goBack(); - } else if (webStack.depth > 1) { - webStack.pop(); - } - } - } - - HifiControls.WebGlyphButton { - enabled: webStack.currentItem.webEngineView.canGoForward - glyph: hifi.glyphs.forward; - anchors.verticalCenter: parent.verticalCenter; - size: 38; - onClicked: { - webStack.currentItem.webEngineView.goForward(); - } - } - - QQControls.ComboBox { - id: addressBar - - //selectByMouse: true - focus: true - - editable: true - //flat: true - indicator: Item {} - background: Item {} - onActivated: { - goTo(textAt(index)); - } - - onHighlightedIndexChanged: { - if (highlightedIndex >= 0) { - addressBar.editText = textAt(highlightedIndex) - } - } - - popup.height: webStack.height - - onFocusChanged: { - if (focus) { - addressBarInput.selectAll(); - } - } - - contentItem: QQControls.TextField { - id: addressBarInput - leftPadding: 26 - rightPadding: hifi.dimensions.controlLineHeight + 5 - text: addressBar.editText - placeholderText: qsTr("Enter URL") - font: addressBar.font - selectByMouse: true - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - onFocusChanged: { - if (focus) { - selectAll(); - } - } - - Keys.onDeletePressed: { - addressBarInput.text = "" - } - - Keys.onPressed: { - if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text); - event.accepted = true; - } - } - - Image { - anchors.verticalCenter: parent.verticalCenter; - x: 5 - z: 2 - id: faviconImage - width: 16; height: 16 - sourceSize: Qt.size(width, height) - source: webStack.currentItem.webEngineView.icon - } - - HifiControls.WebGlyphButton { - glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - z: 2 - x: addressBarInput.width - implicitWidth - onClicked: { - if (webStack.currentItem.webEngineView.loading) { - webStack.currentItem.webEngineView.stop(); - } else { - webStack.currentItem.reloadTimer.start(); - } - } - } - } - - Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i"); - - Keys.onPressed: { - if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text); - event.accepted = true; - } - } - - onEditTextChanged: { - if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) { - suggestionRequestTimer.restart(); - } else { - addressBar.model = [] - addressBar.popup.close(); - } - - } - - Layout.fillWidth: true - editText: webStack.currentItem.webEngineView.url - onAccepted: goTo(addressBarInput.text); - } - - HifiControls.WebGlyphButton { - checkable: true - checked: webStack.currentItem.webEngineView.audioMuted - glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - onClicked: { - webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted - } - } - } - - QQControls.ProgressBar { - id: loadProgressBar - background: Rectangle { - implicitHeight: 2 - color: "#6A6A6A" - } - - contentItem: Item { - implicitHeight: 2 - - Rectangle { - width: loadProgressBar.visualPosition * parent.width - height: parent.height - color: "#00B4EF" - } - } - - width: parent.width; - from: 0 - to: 100 - value: webStack.currentItem.webEngineView.loadProgress - height: 2 - } - - Component { - id: webViewComponent - Rectangle { - property alias webEngineView: webEngineView - property alias reloadTimer: reloadTimer - - property WebEngineNewViewRequest request: null - - property bool isDialog: QQControls.StackView.index > 0 - property real margins: isDialog ? 10 : 0 - - color: "#d1d1d1" - - QQControls.StackView.onActivated: { - addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); - } - - onRequestChanged: { - if (isDialog && request !== null && request !== undefined) {//is Dialog ? - request.openIn(webEngineView); - } - } - - HifiControls.BaseWebView { - id: webEngineView - anchors.fill: parent - anchors.margins: parent.margins - - layer.enabled: parent.isDialog - layer.effect: DropShadow { - verticalOffset: 8 - horizontalOffset: 8 - color: "#330066ff" - samples: 10 - spread: 0.5 - } - - focus: true - objectName: "tabletWebEngineView" - - //profile: HFWebEngineProfile; - profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0" - - property string userScriptUrl: "" - - onLoadingChanged: { - if (!loading) { - addressBarInput.cursorPosition = 0 //set input field cursot to beginning - suggestionRequestTimer.stop(); - addressBar.popup.close(); - } - } - - onLinkHovered: { - //TODO: change cursor shape? - } - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.Deferred - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - if (request.destination == WebEngineView.NewViewInDialog) { - webStack.push(webViewComponent, {"request": request}); - } else { - request.openIn(webEngineView); - } - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; - } - - console.error("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onFullScreenRequested: { - if (request.toggleOn) { - webEngineView.state = "FullScreen"; - } else { - webEngineView.state = ""; - } - request.accept(); - } - - onWindowCloseRequested: { - webStack.pop(); - } - } - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } - } - } - - QQControls.StackView { - id: webStack - width: parent.width; - property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 - height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - - Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"}); - } - } - - - HifiControls.Keyboard { - id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } -} diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index fee37ca1c1..39f75a9182 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -16,7 +16,13 @@ import TabletScriptingInterface 1.0 Rectangle { readonly property var level: AudioScriptingInterface.inputLevel; - + + property bool gated: false; + Component.onCompleted: { + AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + } + property bool standalone: false; property var dragTarget: null; @@ -77,6 +83,7 @@ Rectangle { readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; @@ -189,7 +196,7 @@ Rectangle { Rectangle { // mask id: mask; - width: parent.width * level; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; @@ -212,18 +219,42 @@ Rectangle { color: colors.greenStart; } GradientStop { - position: 0.8; + position: 0.5; color: colors.greenEnd; } - GradientStop { - position: 0.81; - color: colors.red; - } GradientStop { position: 1; - color: colors.red; + color: colors.yellow; } } } + + Rectangle { + id: gatedIndicator; + visible: gated && !AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: "#0080FF"; + anchors { + right: parent.left; + verticalCenter: parent.verticalCenter; + } + } + + Rectangle { + id: clippingIndicator; + visible: AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: colors.red; + anchors { + left: parent.right; + verticalCenter: parent.verticalCenter; + } + } } } diff --git a/interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml similarity index 100% rename from interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml rename to interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 39c48646d3..c59fe42608 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -21,7 +21,7 @@ Rectangle { HifiControlsUit.Keyboard { id: keyboard z: 1000 - raised: parent.keyboardEnabled && parent.keyboardRaised + raised: parent.keyboardEnabled && parent.keyboardRaised && HMD.active numeric: parent.punctuationMode anchors { left: parent.left diff --git a/interface/resources/qml/hifi/avatarapp/Spinner.qml b/interface/resources/qml/hifi/avatarapp/Spinner.qml index 3fc331346d..14f8e922d7 100644 --- a/interface/resources/qml/hifi/avatarapp/Spinner.qml +++ b/interface/resources/qml/hifi/avatarapp/Spinner.qml @@ -1,5 +1,4 @@ import QtQuick 2.5 -import QtWebEngine 1.5 AnimatedImage { source: "../../../icons/loader-snake-64-w.gif" diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index c76f5a428a..2d5f77f006 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -662,7 +662,7 @@ Rectangle { anchors.right: parent.right; text: "Cancel" onClicked: { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); + sendToScript({method: 'checkout_cancelClicked', itemId: itemId}); } } } diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 0d0af875d1..759d61b924 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -24,11 +24,8 @@ Item { HifiConstants { id: hifi; } id: root; - property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); - readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; - property alias usernameDropdownVisible: usernameDropdown.visible; - height: mainContainer.height + additionalDropdownHeight; + height: mainContainer.height; Connections { target: Commerce; @@ -93,77 +90,7 @@ Item { MouseArea { anchors.fill: parent; onClicked: { - sendToParent({method: "header_marketplaceImageClicked", referrerURL: root.referrerURL}); - } - } - } - - Item { - id: buttonAndUsernameContainer; - anchors.left: marketplaceHeaderImage.right; - anchors.leftMargin: 8; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 10; - anchors.right: securityImage.left; - anchors.rightMargin: 6; - - TextMetrics { - id: textMetrics; - font.family: "Raleway" - text: usernameText.text; - } - - Rectangle { - id: myUsernameButton; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 40; - width: usernameText.width + 25; - color: "white"; - radius: 4; - border.width: 1; - border.color: hifi.colors.lightGray; - - // Username Text - RalewayRegular { - id: usernameText; - text: Account.username; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - elide: Text.ElideRight; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - width: Math.min(textMetrics.width + 25, 110); - // Anchors - anchors.centerIn: parent; - rightPadding: 10; - } - - HiFiGlyphs { - id: dropdownIcon; - text: hifi.glyphs.caratDn; - // Size - size: 50; - // Anchors - anchors.right: parent.right; - anchors.rightMargin: -14; - anchors.verticalCenter: parent.verticalCenter; - horizontalAlignment: Text.AlignHCenter; - // Style - color: hifi.colors.baseGray; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - usernameDropdown.visible = !usernameDropdown.visible; - } - onEntered: usernameText.color = hifi.colors.baseGrayShadow; - onExited: usernameText.color = hifi.colors.baseGray; + sendToParent({method: "header_marketplaceImageClicked"}); } } } @@ -205,92 +132,6 @@ Item { } } - Item { - id: usernameDropdown; - z: 998; - visible: false; - anchors.top: buttonAndUsernameContainer.bottom; - anchors.topMargin: -buttonAndUsernameContainer.anchors.bottomMargin; - anchors.right: buttonAndUsernameContainer.right; - height: childrenRect.height; - width: 150; - - Rectangle { - id: myItemsButton; - color: hifi.colors.white; - anchors.top: parent.top; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - RalewaySemiBold { - anchors.fill: parent; - text: "My Submissions" - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - myItemsButton.color = hifi.colors.blueHighlight; - } - onExited: { - myItemsButton.color = hifi.colors.white; - } - onClicked: { - sendToParent({method: "header_myItemsClicked"}); - } - } - } - - Rectangle { - id: logOutButton; - color: hifi.colors.white; - anchors.top: myItemsButton.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - RalewaySemiBold { - anchors.fill: parent; - text: "Log Out" - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - logOutButton.color = hifi.colors.blueHighlight; - } - onExited: { - logOutButton.color = hifi.colors.white; - } - onClicked: { - Account.logOut(); - } - } - } - } - - DropShadow { - z: 997; - visible: usernameDropdown.visible; - anchors.fill: usernameDropdown; - horizontalOffset: 3; - verticalOffset: 3; - radius: 8.0; - samples: 17; - color: "#80000000"; - source: usernameDropdown; - } } diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index bc8816e0ea..68d437a346 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -21,11 +21,10 @@ import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. -Rectangle { +Item { HifiConstants { id: hifi; } id: root; - color: hifi.colors.baseGray property int parentAppTitleBarHeight; property int parentAppNavBarHeight; property string currentActiveView: "sendAssetHome"; diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 232e17d851..8ca34af28a 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -22,7 +22,6 @@ Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceUrl: ""; property string entityId: ""; property string certificateId: ""; property string itemName: "--"; @@ -30,6 +29,7 @@ Rectangle { property string itemEdition: "--"; property string dateAcquired: "--"; property string itemCost: "--"; + property string marketplace_item_id: ""; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; property string infoTextColor: hifi.colors.blueAccent; @@ -69,7 +69,7 @@ Rectangle { errorText.text = "Information about this certificate is currently unavailable. Please try again later."; } } else { - root.marketplaceUrl = result.data.marketplace_item_url; + root.marketplace_item_id = result.data.marketplace_item_id; root.isMyCert = result.isMyCert ? result.isMyCert : false; if (root.certInfoReplaceMode > 3) { @@ -352,7 +352,7 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id}); } onEntered: itemName.color = hifi.colors.blueHighlight; onExited: itemName.color = root.certTextColor; @@ -391,7 +391,7 @@ Rectangle { // "Show In Marketplace" button HifiControlsUit.Button { id: showInMarketplaceButton; - enabled: root.marketplaceUrl; + enabled: root.marketplace_item_id && marketplace_item_id !== ""; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; @@ -401,7 +401,7 @@ Rectangle { height: 40; text: "View In Market" onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id}); } } } @@ -620,7 +620,7 @@ Rectangle { root.itemOwner = "--"; root.itemEdition = "--"; root.dateAcquired = "--"; - root.marketplaceUrl = ""; + root.marketplace_item_id = ""; root.itemCost = "--"; root.isMyCert = false; errorText.text = ""; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml new file mode 100644 index 0000000000..0d42cb599e --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -0,0 +1,1216 @@ +// +// Marketplace.qml +// qml/hifi/commerce/marketplace +// +// Marketplace +// +// Created by Roxanne Skelly 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + HifiConstants { + id: hifi + } + + id: root + + property string activeView: "initialize" + property int currentSortIndex: 0 + property string sortString: "recent" + property string categoryString: "" + property string searchString: "" + property bool keyboardEnabled: HMD.active + property bool keyboardRaised: false + property string searchScopeString: "Featured" + property bool isLoggedIn: false + property bool supports3DHTML: true + + anchors.fill: (typeof parent === undefined) ? undefined : parent + + function getMarketplaceItems() { + marketplaceItemView.visible = false; + itemsList.visible = true; + licenseInfo.visible = false; + marketBrowseModel.getFirstPage(); + { + if(root.searchString !== undefined && root.searchString !== "") { + root.searchScopeString = "Search Results: \"" + root.searchString + "\""; + } else if (root.categoryString !== "") { + root.searchScopeString = root.categoryString; + } else { + root.searchScopeString = "Featured"; + } + } + } + + Component.onCompleted: { + Commerce.getLoginStatus(); + + supports3DHTML = PlatformInfo.has3DHTML(); + } + + Component.onDestruction: { + keyboard.raised = false; + } + + Connections { + target: GlobalServices + + onMyUsernameChanged: { + Commerce.getLoginStatus(); + } + } + + Connections { + target: MarketplaceScriptingInterface + + onGetMarketplaceCategoriesResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Categories", result.data.message); + } else { + categoriesModel.clear(); + categoriesModel.append({ + id: -1, + name: "Everything" + }); + result.data.items.forEach(function(category) { + categoriesModel.append({ + id: category.id, + name: category.name + }); + }); + } + getMarketplaceItems(); + } + onGetMarketplaceItemsResult: { + marketBrowseModel.handlePage(result.status !== "success" && result.message, result); + } + + onGetMarketplaceItemResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Item", result.data.message); + } else { + marketplaceItem.supports3DHTML = root.supports3DHTML; + marketplaceItem.item_id = result.data.id; + marketplaceItem.image_url = result.data.thumbnail_url; + marketplaceItem.name = result.data.title; + marketplaceItem.likes = result.data.likes; + if(result.data.has_liked !== undefined) { + marketplaceItem.liked = result.data.has_liked; + } + marketplaceItem.creator = result.data.creator; + marketplaceItem.categories = result.data.categories; + marketplaceItem.price = result.data.cost; + marketplaceItem.description = result.data.description; + marketplaceItem.attributions = result.data.attributions; + marketplaceItem.license = result.data.license; + marketplaceItem.available = result.data.availability === "available"; + marketplaceItem.created_at = result.data.created_at; + marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; + itemsList.visible = false; + marketplaceItemView.visible = true; + } + } + } + + Connections { + target: Commerce + + onLoginStatusResult: { + root.isLoggedIn = isLoggedIn; + } + } + + HifiCommerceCommon.CommerceLightbox { + id: lightboxPopup + visible: false + anchors.fill: parent + } + + // + // HEADER BAR START + // + + Rectangle { + id: headerShadowBase + anchors.fill: header + color: "white" + } + DropShadow { + anchors.fill: headerShadowBase + source: headerShadowBase + verticalOffset: 4 + horizontalOffset: 4 + radius: 6 + samples: 9 + color: hifi.colors.baseGrayShadow + z:5 + } + Rectangle { + id: header; + + visible: true + anchors { + left: parent.left + top: parent.top + right: parent.right + } + + height: childrenRect.height+5 + z: 5 + + Rectangle { + id: titleBarContainer + + anchors.left: parent.left + anchors.top: parent.top + width: parent.width + height: 60 + visible: true + + Image { + id: marketplaceHeaderImage; + source: "../common/images/marketplaceHeaderImage.png"; + anchors.top: parent.top; + anchors.topMargin: 2; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 0; + anchors.left: parent.left; + anchors.leftMargin: 8; + width: 140; + fillMode: Image.PreserveAspectFit; + + MouseArea { + anchors.fill: parent; + onClicked: { + sendToParent({method: "header_marketplaceImageClicked"}); + } + } + } + } + + Rectangle { + id: searchBarContainer + + anchors.top: titleBarContainer.bottom + width: parent.width + height: 50 + + visible: true + clip: false + + // + // TODO: Possibly change this to be a combo box + // + Rectangle { + id: categoriesButton + + anchors { + left: parent.left + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + height: 34 + width: categoriesText.width + 25 + + color: hifi.colors.white + radius: 4 + border.width: 1 + border.color: hifi.colors.lightGrayText + + RalewayRegular { + id: categoriesText + + anchors.centerIn: parent + + text: "Categories" + size: 14 + color: hifi.colors.lightGrayText + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rightPadding: 10 + } + + HiFiGlyphs { + id: categoriesDropdownIcon + + anchors { + right: parent.right + rightMargin: -8 + verticalCenter: parent.verticalCenter + } + + text: hifi.glyphs.caratDn + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.baseGray + } + + MouseArea { + anchors.fill: parent + + hoverEnabled: enabled + onClicked: { + categoriesDropdown.visible = !categoriesDropdown.visible; + categoriesButton.color = categoriesDropdown.visible ? hifi.colors.lightGray : hifi.colors.white; + categoriesDropdown.forceActiveFocus(); + } + onEntered: categoriesText.color = hifi.colors.baseGrayShadow + onExited: categoriesText.color = hifi.colors.baseGray + } + + Component.onCompleted: { + MarketplaceScriptingInterface.getMarketplaceCategories(); + } + } + + // or + RalewayRegular { + id: orText + + anchors.left: categoriesButton.right + anchors.verticalCenter: parent.verticalCenter + width: 25 + + text: "or" + size: 18; + color: hifi.colors.baseGray + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rightPadding: 10 + leftPadding: 10 + } + + HifiControlsUit.TextField { + id: searchField + + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + left: orText.right + rightMargin: 10 + } + height: 34 + + isSearchField: true + colorScheme: hifi.colorSchemes.faintGray + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput + placeholderText: "Search Marketplace" + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + searchField.accepted(); + searchField.forceActiveFocus(); + } + break; + case Qt.Key_Backspace: + if (searchField.text === "") { + primaryFilter_index = -1; + } + break; + } + } + + onAccepted: { + root.searchString = searchField.text; + getMarketplaceItems(); + searchField.forceActiveFocus(); + } + + onActiveFocusChanged: { + root.keyboardRaised = activeFocus; + } + + } + } + } + // + // HEADER BAR END + // + + // + // CATEGORIES LIST START + // + Item { + id: categoriesDropdown + + anchors.fill: parent; + + visible: false + z: 10 + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onClicked: { + categoriesDropdown.visible = false; + categoriesButton.color = hifi.colors.white; + } + } + + Rectangle { + anchors { + left: parent.left; + bottom: parent.bottom; + top: parent.top; + topMargin: 100; + } + width: parent.width/3 + + color: hifi.colors.white + + ListModel { + id: categoriesModel + } + + ListView { + id: categoriesListView; + + anchors.fill: parent + anchors.rightMargin: 10 + width: parent.width + currentIndex: -1; + clip: true + + model: categoriesModel + delegate: ItemDelegate { + height: 34 + width: parent.width + + clip: true + contentItem: Rectangle { + id: categoriesItem + + anchors.fill: parent + + color: hifi.colors.white + visible: true + + RalewayRegular { + id: categoriesItemText + + anchors.leftMargin: 15 + anchors.fill:parent + + text: model.name + color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 14 + } + } + + MouseArea { + anchors.fill: parent + z: 10 + + hoverEnabled: true + propagateComposedEvents: false + + onEntered: { + categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight; + } + + onExited: { + categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white; + } + + onClicked: { + categoriesListView.currentIndex = index; + categoriesText.text = categoriesItemText.text; + categoriesDropdown.visible = false; + categoriesButton.color = hifi.colors.white; + root.categoryString = categoriesItemText.text; + getMarketplaceItems(); + } + } + } + + ScrollBar.vertical: ScrollBar { + parent: categoriesListView.parent + + anchors { + top: categoriesListView.top; + bottom: categoriesListView.bottom; + left: categoriesListView.right; + } + + contentItem.opacity: 1 + } + } + } + } + // + // CATEGORIES LIST END + // + + // + // ITEMS LIST START + // + Item { + id: itemsList + + anchors { + fill: parent + topMargin: 115 + bottomMargin: 50 + } + + visible: true + + HifiModels.PSFListModel { + id: marketBrowseModel + + itemsPerPage: 7 + listModelName: 'marketBrowse' + listView: marketBrowseList + + getPage: function () { + MarketplaceScriptingInterface.getMarketplaceItems( + root.searchString === "" ? undefined : root.searchString, + "", + root.categoryString.toLowerCase(), + "", + "", + root.sortString, + WalletScriptingInterface.limitedCommerce, + marketBrowseModel.currentPageToRetrieve, + marketBrowseModel.itemsPerPage + ); + } + + processPage: function(data) { + return data.items; + } + } + ListView { + id: marketBrowseList + + anchors.fill: parent + anchors.rightMargin: 10 + + model: marketBrowseModel + + orientation: ListView.Vertical + focus: true + clip: true + + delegate: MarketplaceListItem { + item_id: model.id + + anchors.topMargin: 10 + + image_url:model.thumbnail_url + name: model.title + likes: model.likes + liked: model.has_liked ? model.has_liked : false + creator: model.creator + category: model.primary_category + price: model.cost + available: model.availability === "available" + isLoggedIn: root.isLoggedIn; + + onShowItem: { + MarketplaceScriptingInterface.getMarketplaceItem(item_id); + } + + onBuy: { + sendToScript({method: 'marketplace_checkout', itemId: item_id}); + } + + onCategoryClicked: { + root.categoryString = category; + categoriesText.text = category; + getMarketplaceItems(); + } + + onRequestReload: getMarketplaceItems(); + } + + ScrollBar.vertical: ScrollBar { + parent: marketBrowseList.parent + + anchors { + top: marketBrowseList.top + bottom: marketBrowseList.bottom + left: marketBrowseList.right + } + + contentItem.opacity: 1 + } + + headerPositioning: ListView.InlineHeader + + header: Item { + id: itemsHeading + + height: childrenRect.height + width: parent.width + + Rectangle { + id: itemsSpacer; + height: 20 + } + + Rectangle { + id: itemsLoginStatus; + anchors { + top: itemsSpacer.bottom + left: parent.left + right: parent.right + leftMargin: 15 + } + height: root.isLoggedIn ? 0 : 80 + + visible: !root.isLoggedIn + color: hifi.colors.greenHighlight + border.color: hifi.colors.greenShadow + border.width: 1 + radius: 4 + z: 10000 + + HifiControlsUit.Button { + id: loginButton; + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + leftMargin: 15 + topMargin:10 + bottomMargin: 10 + } + width: 80; + + text: "LOG IN" + + onClicked: { + sendToScript({method: 'needsLogIn_loginClicked'}); + } + } + + RalewayRegular { + id: itemsLoginText + + anchors { + leftMargin: 15 + top: parent.top; + bottom: parent.bottom; + right: parent.right; + left: loginButton.right + } + + text: "to get items from the Marketplace." + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + + Item { + id: breadcrumbs + + anchors.top: itemsLoginStatus.bottom; + anchors.left: parent.left + anchors.right: parent.right + height: 34 + visible: categoriesListView.currentIndex >= 0 + + RalewayRegular { + id: categoriesItemText + + anchors.leftMargin: 15 + anchors.fill:parent + + text: "Home /" + color: hifi.colors.blueHighlight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + + MouseArea { + anchors.fill: parent + + onClicked: { + categoriesListView.currentIndex = -1; + categoriesText.text = "Categories"; + root.categoryString = ""; + searchField.text = ""; + getMarketplaceItems(); + } + } + } + + Item { + id: searchScope + + anchors { + top: breadcrumbs.bottom + left: parent.left + right: parent.right + } + height: 50 + + RalewaySemiBold { + id: searchScopeText; + + anchors { + leftMargin: 15 + fill:parent + topMargin: 10 + } + + text: searchScopeString + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 22 + } + } + Item { + id: sort + visible: searchString === undefined || searchString === "" + + anchors { + top: searchScope.bottom; + left: parent.left; + right: parent.right; + topMargin: 10; + leftMargin: 15; + } + height: visible ? childrenRect.height : 0 + + RalewayRegular { + id: sortText + + anchors.leftMargin: 15 + anchors.rightMargin: 20 + height: 34 + + text: "Sort:" + color: hifi.colors.lightGrayText + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 14 + } + + Rectangle { + anchors { + left: sortText.right + top: parent.top + leftMargin: 20 + } + width: root.isLoggedIn ? 322 : 242 + height: 36 + + radius: 4 + border.width: 1 + border.color: hifi.colors.faintGray + + ListModel { + id: sortModel + + ListElement { + name: "Name"; + glyph: ";" + sortString: "alpha" + } + + ListElement { + name: "Date"; + glyph: ";"; + sortString: "recent"; + } + + ListElement { + name: "Popular"; + glyph: ";"; + sortString: "likes"; + } + + ListElement { + name: "My Likes"; + glyph: ";"; + sortString: "my_likes"; + } + } + + ListView { + id: sortListView + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + topMargin:1 + bottomMargin:1 + leftMargin:1 + rightMargin:1 + } + width: childrenRect.width + height: 34 + + orientation: ListView.Horizontal + focus: true + clip: true + highlightFollowsCurrentItem: false + currentIndex: 1; + + delegate: SortButton { + width: 80 + height: parent.height + + glyph: model.glyph + text: model.name + + visible: root.isLoggedIn || model.sortString != "my_likes" + + checked: ListView.isCurrentItem + + onClicked: { + root.currentSortIndex = index; + sortListView.positionViewAtIndex(index, ListView.Beginning); + sortListView.currentIndex = index; + root.sortString = model.sortString; + getMarketplaceItems(); + } + } + highlight: Rectangle { + width: 80 + height: parent.height + + color: hifi.colors.faintGray + x: sortListView.currentItem.x + } + + model: sortModel + } + } + } + } + } + } + + // + // ITEMS LIST END + // + + // + // ITEM START + // + Item { + id: marketplaceItemView + + anchors.fill: parent + anchors.topMargin: 115 + anchors.bottomMargin: 50 + width: parent.width + + visible: false + + ScrollView { + id: marketplaceItemScrollView + + anchors.fill: parent + + clip: true + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + contentWidth: parent.width + contentHeight: childrenRect.height + + function resize() { + contentHeight = (marketplaceItemContent.y - itemSpacer.y + marketplaceItemContent.height); + } + + Item { + id: itemSpacer + anchors.top: parent.top + height: 15 + } + + Rectangle { + id: itemLoginStatus; + anchors { + left: parent.left + right: parent.right + top: itemSpacer.bottom + topMargin: 10 + leftMargin: 15 + rightMargin: 15 + } + height: root.isLoggedIn ? 0 : 80 + + visible: !root.isLoggedIn + color: hifi.colors.greenHighlight + border.color: hifi.colors.greenShadow + border.width: 1 + radius: 4 + z: 10000 + + HifiControlsUit.Button { + id: loginButton; + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + leftMargin: 15 + topMargin:10 + bottomMargin: 10 + } + width: 80; + + text: "LOG IN" + + onClicked: { + sendToScript({method: 'needsLogIn_loginClicked'}); + } + } + + RalewayRegular { + id: itemsLoginText + + anchors { + leftMargin: 15 + top: parent.top; + bottom: parent.bottom; + right: parent.right; + left: loginButton.right + } + + text: "to get items from the Marketplace." + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + + + Rectangle { + id: marketplaceItemContent + anchors.top: itemLoginStatus.bottom + width: parent.width + height: childrenRect.height; + + RalewaySemiBold { + id: backText + + anchors { + top: parent.top + left: parent.left + topMargin: 10 + leftMargin: 15 + bottomMargin: 10 + } + width: paintedWidth + height: paintedHeight + + text: "Back" + size: hifi.fontSizes.overlayTitle + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors.fill: backText + + onClicked: { + getMarketplaceItems(); + } + } + + MarketplaceItem { + id: marketplaceItem + + + anchors.topMargin: 15 + anchors.top: backText.bottom + width: parent.width + height: childrenRect.height + + isLoggedIn: root.isLoggedIn; + + onBuy: { + sendToScript({method: 'marketplace_checkout', itemId: item_id, itemEdition: edition}); + } + + onShowLicense: { + var xhr = new XMLHttpRequest; + xhr.open("GET", url); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) { + console.log(xhr.responseText); + licenseText.text = xhr.responseText; + licenseInfo.visible = true; + } + }; + xhr.send(); + } + onCategoryClicked: { + root.categoryString = category; + categoriesText.text = category; + getMarketplaceItems(); + } + + onResized: { + marketplaceItemScrollView.resize(); + } + } + } + } + } + // + // ITEM END + // + + // + // FOOTER START + // + + Rectangle { + id: footer + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + height: 50 + + color: hifi.colors.blueHighlight + + Item { + anchors { + fill: parent + rightMargin: 15 + leftMargin: 15 + } + + + + Item { + id: footerText + + anchors.fill: parent + visible: root.supports3DHTML && itemsList.visible + + HiFiGlyphs { + id: footerGlyph + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + rightMargin: 10 + } + + text: hifi.glyphs.info + size: 34 + color: hifi.colors.white + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: footerInfo + + anchors { + left: footerGlyph.right + top: parent.top + bottom: parent.bottom + } + + text: "Get items from Clara.io!" + color: hifi.colors.white + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + + HifiControlsUit.Button { + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + topMargin: 10 + bottomMargin: 10 + leftMargin: 10 + rightMargin: 10 + } + + visible: marketplaceItemView.visible + text: "< BACK" + width: 100 + + onClicked: { + marketplaceItemView.visible = false; + itemsList.visible = true; + licenseInfo.visible = false; + } + } + + HifiControlsUit.Button { + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + topMargin: 10 + bottomMargin: 10 + leftMargin: 10 + rightMargin: 10 + } + + visible: root.supports3DHTML + + text: "SEE ALL MARKETS" + width: 180 + + onClicked: { + sendToScript({method: 'marketplace_marketplaces', itemId: marketplaceItemView.visible ? marketplaceItem.item_id : undefined}); + } + } + } + } + // + // FOOTER END + // + + + // + // LICENSE START + // + Rectangle { + id: licenseInfo + + anchors { + fill: root + topMargin: 120 + bottomMargin: 0 + } + + visible: false; + + ScrollView { + anchors { + bottomMargin: 1 + topMargin: 60 + leftMargin: 15 + fill: parent + } + + RalewayRegular { + id: licenseText + + width:440 + wrapMode: Text.Wrap + + text: "" + size: 18; + color: hifi.colors.baseGray + } + } + + Item { + id: licenseClose + + anchors { + top: parent.top + right: parent.right + topMargin: 10 + rightMargin: 10 + } + width: 30 + height: 30 + + HiFiGlyphs { + anchors { + fill: parent + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + height: 30 + + text: hifi.glyphs.close + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.baseGray + } + + MouseArea { + anchors.fill: licenseClose + + onClicked: licenseInfo.visible = false; + } + } + } + // + // LICENSE END + // + + HifiControlsUit.Keyboard { + id: keyboard + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: false + } + + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript, in this case the Marketplaces JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + + function fromScript(message) { + switch (message.method) { + case 'updateMarketplaceQMLItem': + if (!message.params.itemId) { + console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!"); + return; + } + marketplaceItem.edition = message.params.edition ? message.params.edition : -1; + MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId); + break; + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml new file mode 100644 index 0000000000..0a57e56099 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -0,0 +1,648 @@ +// +// MarketplaceListItem.qml +// qml/hifi/commerce/marketplace +// +// MarketplaceListItem +// +// Created by Roxanne Skelly 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + id: root; + + property string item_id: "" + property string image_url: "" + property string name: "" + property int likes: 0 + property bool liked: false + property string creator: "" + property var categories: [] + property int price: 0 + property var attributions: [] + property string description: "" + property string license: "" + property string posted: "" + property bool available: false + property string created_at: "" + property bool isLoggedIn: false; + property int edition: -1; + property bool supports3DHTML: false; + + + onCategoriesChanged: { + categoriesListModel.clear(); + categories.forEach(function(category) { + categoriesListModel.append({"category":category}); + }); + } + + onDescriptionChanged: { + + if(root.supports3DHTML) { + descriptionTextModel.clear(); + descriptionTextModel.append({text: description}); + } else { + descriptionText.text = description; + } + } + + onAttributionsChanged: { + attributionsModel.clear(); + if(root.attributions) { + root.attributions.forEach(function(attribution) { + attributionsModel.append(attribution); + }); + } + footer.evalHeight(); + } + + signal buy() + signal categoryClicked(string category) + signal showLicense(string url) + + HifiConstants { + id: hifi + } + + Connections { + target: MarketplaceScriptingInterface + + onMarketplaceItemLikeResult: { + if (result.status !== 'success') { + console.log("Like/Unlike item", result.data.message); + } else { + root.liked = !root.liked; + root.likes = root.liked ? root.likes + 1 : root.likes - 1; + } + } + } + function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + + var a = new Date(timestamp); + + var year = a.getFullYear(); + var month = addLeadingZero(a.getMonth() + 1); + var day = addLeadingZero(a.getDate()); + var hour = a.getHours(); + var drawnHour = hour; + if (hour === 0) { + drawnHour = 12; + } else if (hour > 12) { + drawnHour -= 12; + } + drawnHour = addLeadingZero(drawnHour); + + var amOrPm = "AM"; + if (hour >= 12) { + amOrPm = "PM"; + } + + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); + return a.toDateString() + " " + drawnHour + ':' + min + amOrPm; + } + function evalHeight() { + height = footer.y - header.y + footer.height; + } + + signal resized() + + onHeightChanged: { + resized(); + } + + anchors { + left: parent.left + right: parent.right + leftMargin: 15 + rightMargin: 15 + } + height: footer.y - header.y + footer.height + + Rectangle { + id: header + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 50 + + RalewaySemiBold { + id: nameText + + anchors { + top: parent.top + left: parent.left + bottom: parent.bottom + } + width: paintedWidth + + text: root.name + size: 24 + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + } + + Item { + id: likes + + anchors { + top: parent.top + right: parent.right + bottom: parent.bottom + rightMargin: 5 + } + + RalewaySemiBold { + id: heart + + anchors { + top: parent.top + right: parent.right + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + + text: "\u2665" + size: 20 + horizontalAlignment: Text.AlignHCenter + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText + } + + RalewaySemiBold { + id: likesText + + anchors { + top: parent.top + right: heart.left + rightMargin: 5 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.likes + size: hifi.fontSizes.overlayTitle + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors { + left: likesText.left + right: heart.right + top: likesText.top + bottom: likesText.bottom + } + + onClicked: { + if (isLoggedIn) { + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked); + } + } + } + } + } + + Image { + id: itemImage + + anchors { + top: header.bottom + left: parent.left + right: parent.right + } + height: width*0.5625 + + source: root.image_url + fillMode: Image.PreserveAspectCrop; + } + + Item { + id: footer + + anchors { + left: parent.left; + right: parent.right; + top: itemImage.bottom; + } + height: categoriesList.y - buyButton.y + categoriesList.height + + function evalHeight() { + height = categoriesList.y - buyButton.y + categoriesList.height; + console.log("HEIGHT: " + height); + } + + HifiControlsUit.Button { + id: buyButton + + anchors { + right: parent.right + top: parent.top + left: parent.left + topMargin: 15 + } + height: 50 + + text: root.edition >= 0 ? "UPGRADE FOR FREE" : (root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)") + enabled: root.edition >= 0 || root.available + buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : "" + color: hifi.buttons.blue + + onClicked: root.buy(); + } + + Item { + id: creatorItem + + anchors { + top: buyButton.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: childrenRect.height + + RalewaySemiBold { + id: creatorLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "CREATOR:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: creatorText + + anchors { + top: creatorLabel.bottom + left: parent.left + topMargin: 10 + } + width: paintedWidth + text: root.creator + + size: 18 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + } + } + + Item { + id: posted + + anchors { + top: creatorItem.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: childrenRect.height + + RalewaySemiBold { + id: postedLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "POSTED:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: created_at + + anchors { + top: postedLabel.bottom + left: parent.left + right: parent.right + topMargin: 5 + } + + text: { getFormattedDate(root.created_at); } + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + } + } + + Rectangle { + + anchors { + top: posted.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: 1 + + color: hifi.colors.lightGrayText + } + + Item { + id: attributions + + anchors { + top: posted.bottom + topMargin: 30 + left: parent.left + right: parent.right + } + width: parent.width + height: attributionsModel.count > 0 ? childrenRect.height : 0 + visible: attributionsModel.count > 0 + + RalewaySemiBold { + id: attributionsLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + height: paintedHeight + + text: "ATTRIBUTIONS:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + ListModel { + id: attributionsModel + } + + ListView { + anchors { + left: parent.left + right: parent.right + top: attributionsLabel.bottom + bottomMargin: 15 + } + + height: 24*model.count+10 + + model: attributionsModel + delegate: Item { + height: 24 + width: parent.width + RalewaySemiBold { + id: attributionName + + anchors.leftMargin: 15 + height: 24 + width: parent.width + + text: model.name + elide: Text.ElideRight + size: 14 + + color: (model.link && root.supports3DHTML)? hifi.colors.blueHighlight : hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + MouseArea { + anchors.fill: parent + + onClicked: { + if (model.link && root.supports3DHTML) { + sendToScript({method: 'marketplace_open_link', link: model.link}); + } + } + } + } + } + } + Rectangle { + + anchors { + bottom: attributions.bottom + leftMargin: 15 + } + width: parent.width + height: 1 + + color: hifi.colors.lightGrayText + } + } + Item { + id: licenseItem; + + anchors { + top: attributions.bottom + left: parent.left + topMargin: 15 + } + width: parent.width + height: childrenRect.height + + RalewaySemiBold { + id: licenseLabel + + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + + text: "SHARED UNDER:"; + size: 14; + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: licenseText + + anchors.top: licenseLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth + + text: root.license + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: licenseHelp + + anchors.top: licenseText.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth + + text: "More about this license" + size: 14 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: parent + + onClicked: { + licenseInfo.visible = true; + var url; + if (root.license === "No Rights Reserved (CC0)") { + url = "https://creativecommons.org/publicdomain/zero/1.0/" + } else if (root.license === "Attribution (CC BY)") { + url = "https://creativecommons.org/licenses/by/4.0/legalcode.txt" + } else if (root.license === "Attribution-ShareAlike (CC BY-SA)") { + url = "https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt" + } else if (root.license === "Attribution-NoDerivs (CC BY-ND)") { + url = "https://creativecommons.org/licenses/by-nd/4.0/legalcode.txt" + } else if (root.license === "Attribution-NonCommercial (CC BY-NC)") { + url = "https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt" + } else if (root.license === "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") { + url = "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt" + } else if (root.license === "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { + url = "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.txt" + } else if (root.license === "Proof of Provenance License (PoP License)") { + url = "https://digitalassetregistry.com/PoP-License/v1/" + } + if(url) { + showLicense(url) + } + } + } + } + } + + Item { + id: descriptionItem + property string text: "" + + anchors { + top: licenseItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + height: childrenRect.height + onHeightChanged: { + footer.evalHeight(); + } + RalewaySemiBold { + id: descriptionLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + height: 20 + + text: "DESCRIPTION:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: descriptionText + + anchors.top: descriptionLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: parent.width + + text: root.description + size: 14 + color: hifi.colors.lightGray + linkColor: root.supports3DHTML ? hifi.colors.blueHighlight : hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + textFormat: Text.RichText + wrapMode: Text.Wrap + onLinkActivated: { + if (root.supports3DHTML) { + sendToScript({method: 'marketplace_open_link', link: link}); + } + } + + onHeightChanged: { footer.evalHeight(); } + } + } + + Item { + id: categoriesList + + anchors { + top: descriptionItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + width: parent.width + height: categoriesListModel.count*24 + categoryLabel.height + (isLoggedIn ? 50 : 150) + + onHeightChanged: { footer.evalHeight(); } + + RalewaySemiBold { + id: categoryLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "IN:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + ListModel { + id: categoriesListModel + } + + ListView { + anchors { + left: parent.left + right: parent.right + top: categoryLabel.bottom + bottomMargin: 15 + } + + height: 24*model.count+10 + + model: categoriesListModel + delegate: RalewaySemiBold { + id: categoryText + + anchors.leftMargin: 15 + width: paintedWidth + + text: model.category + size: 14 + height: 24 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: categoryText + + onClicked: root.categoryClicked(model.category); + } + } + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml new file mode 100644 index 0000000000..2f37637e40 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -0,0 +1,321 @@ +// +// MarketplaceListItem.qml +// qml/hifi/commerce/marketplace +// +// MarketplaceListItem +// +// Created by Roxanne Skelly 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + id: root + + property string item_id: "" + property string image_url: "" + property string name: "" + property int likes: 0 + property bool liked: false + property string creator: "" + property string category: "" + property int price: 0 + property bool available: false + property bool isLoggedIn: false; + + signal buy() + signal showItem() + signal categoryClicked(string category) + signal requestReload() + + HifiConstants { id: hifi } + + width: parent.width + height: childrenRect.height+50 + + DropShadow { + anchors.fill: shadowBase + + source: shadowBase + verticalOffset: 4 + horizontalOffset: 4 + radius: 6 + samples: 9 + color: hifi.colors.baseGrayShadow + } + + Rectangle { + id: shadowBase + + anchors.fill: itemRect + + color: "white" + } + + Rectangle { + id: itemRect + + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 20 + bottomMargin: 10 + leftMargin: 15 + rightMargin: 15 + } + height: childrenRect.height + + MouseArea { + anchors.fill: parent + + hoverEnabled: true + + onClicked: root.showItem(); + onEntered: hoverRect.visible = true; + onExited: hoverRect.visible = false; + + } + + Rectangle { + id: header + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 50 + + RalewaySemiBold { + id: nameText + + anchors { + top: parent.top + left: parent.left + right: parent.right + rightMargin: 50 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.name + size: hifi.fontSizes.overlayTitle + elide: Text.ElideRight + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + } + + Item { + id: likes + + anchors { + top: parent.top + right: parent.right + rightMargin: 15 + bottom: parent.bottom + } + width: heart.width + likesText.width + + Connections { + target: MarketplaceScriptingInterface + + onMarketplaceItemLikeResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Categories", result.data.message); + root.requestReload(); + } + } + } + + RalewaySemiBold { + id: heart + + anchors { + top: parent.top + right: parent.right + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + + text: "\u2665" + size: 20 + horizontalAlignment: Text.AlignHCenter; + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText + } + + RalewaySemiBold { + id: likesText + + anchors { + top: parent.top + right: heart.left + rightMargin: 5 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.likes + size: hifi.fontSizes.overlayTitle + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors { + left: likesText.left + right: heart.right + top: parent.top + bottom: parent.bottom + } + onClicked: { + if (isLoggedIn) { + root.liked = !root.liked; + root.likes = root.liked ? root.likes + 1 : root.likes - 1; + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); + } + } + } + } + } + + Image { + id: itemImage + + anchors { + top: header.bottom + left: parent.left + right: parent.right + } + height: width * 0.5625 + + source: root.image_url + fillMode: Image.PreserveAspectCrop + } + + Item { + id: footer + + anchors { + left: parent.left + right: parent.right + top: itemImage.bottom + } + height: 60 + + RalewaySemiBold { + id: creatorLabel + + anchors { + top: parent.top + left: parent.left + leftMargin: 15 + } + width: paintedWidth + + text: "CREATOR:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: creatorText + + anchors { + top: creatorLabel.top; + left: creatorLabel.right; + leftMargin: 15; + } + width: paintedWidth; + + text: root.creator; + size: 14; + color: hifi.colors.lightGray; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: categoryLabel + + anchors { + top: creatorLabel.bottom + left: parent.left + leftMargin: 15 + } + width: paintedWidth; + + text: "IN:"; + size: 14; + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: categoryText + + anchors { + top: categoryLabel.top + left: categoryLabel.right + leftMargin: 15 + } + width: paintedWidth + + text: root.category + size: 14 + color: hifi.colors.blueHighlight; + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent + + onClicked: root.categoryClicked(root.category); + } + } + + HifiControlsUit.Button { + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + rightMargin: 15 + topMargin:10 + bottomMargin: 10 + } + + text: root.price ? root.price : "FREE" + buttonGlyph: root.price ? hifi.glyphs.hfc : "" + color: hifi.buttons.blue; + + onClicked: root.buy(); + } + } + + Rectangle { + id: hoverRect + + anchors.fill: parent + + border.color: hifi.colors.blueHighlight + border.width: 2 + color: "#00000000" + visible: false + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml new file mode 100644 index 0000000000..37ad2735ce --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml @@ -0,0 +1,87 @@ +// +// SortButton.qml +// qml/hifi/commerce/marketplace +// +// SortButton +// +// Created by Roxanne Skelly 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Item { + HifiConstants { id: hifi; } + + id: root; + + + property string glyph: ""; + property string text: ""; + property bool checked: false; + signal clicked(); + + width: childrenRect.width; + height: parent.height; + + Rectangle { + anchors.top: parent.top; + anchors.left: parent.left; + height: parent.height; + width: 2; + color: hifi.colors.faintGray; + visible: index > 0; + } + + HiFiGlyphs { + id: buttonGlyph; + text: root.glyph; + // Size + size: 14; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 0; + anchors.top: parent.top; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + horizontalAlignment: Text.AlignHCenter; + // Style + color: hifi.colors.lightGray; + } + RalewayRegular { + id: buttonText; + text: root.text; + // Text size + size: 14; + // Style + color: hifi.colors.lightGray; + elide: Text.ElideRight; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.top: parent.top; + height: parent.height; + } + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.clicked(); + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index f7dc26df5f..df6e216b32 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -28,6 +28,7 @@ Item { property string purchaseStatus; property string itemName; property string itemId; + property string updateItemId; property string itemPreviewImageUrl; property string itemHref; property string certificateId; @@ -45,9 +46,9 @@ Item { property bool cardBackVisible; property bool isInstalled; property string wornEntityID; - property string upgradeUrl; + property string updatedItemId; property string upgradeTitle; - property bool updateAvailable: root.upgradeUrl !== ""; + property bool updateAvailable: root.updateItemId && root.updateItemId !== ""; property bool valid; property string originalStatusText; @@ -175,7 +176,7 @@ Item { Item { property alias buttonGlyphText: buttonGlyph.text; - property alias buttonText: buttonText.text; + property alias itemButtonText: buttonText.text; property alias glyphSize: buttonGlyph.size; property string buttonColor: hifi.colors.black; property string buttonColor_hover: hifi.colors.blueHighlight; @@ -243,7 +244,7 @@ Item { onLoaded: { item.enabled = root.valid; item.buttonGlyphText = hifi.glyphs.gift; - item.buttonText = "Gift"; + item.itemButtonText = "Gift"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({ @@ -270,7 +271,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.market; - item.buttonText = "View in Marketplace"; + item.itemButtonText = "View in Marketplace"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); @@ -288,7 +289,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.certificate; - item.buttonText = "View Certificate"; + item.itemButtonText = "View Certificate"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); @@ -307,7 +308,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.uninstall; - item.buttonText = "Uninstall"; + item.itemButtonText = "Uninstall"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); Commerce.uninstallApp(root.itemHref); @@ -330,15 +331,14 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.update; - item.buttonText = "Update"; + item.itemButtonText = "Update"; item.buttonColor = "#E2334D"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({ method: 'updateItemClicked', - itemId: root.itemId, + itemId: root.updateAvailable ? root.updateItemId : root.itemId, itemEdition: root.itemEdition, - upgradeUrl: root.upgradeUrl, itemHref: root.itemHref, itemType: root.itemType, isInstalled: root.isInstalled, @@ -378,10 +378,10 @@ Item { function updateProperties() { if (updateButton.visible && uninstallButton.visible) { - item.buttonText = ""; + item.itemButtonText = ""; item.glyphSize = 20; } else { - item.buttonText = "Send to Trash"; + item.itemButtonText = "Send to Trash"; item.glyphSize = 30; } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 9433618b6b..dc892e6640 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -29,7 +29,6 @@ Rectangle { id: root; property string activeView: "initialize"; - property string referrerURL: ""; property bool securityImageResultReceived: false; property bool purchasesReceived: false; property bool punctuationMode: false; @@ -154,55 +153,10 @@ Rectangle { } } - // - // TITLE BAR START - // - HifiCommerceCommon.EmulatedMarketplaceHeader { - id: titleBarContainer; - z: 997; - visible: false; - height: 100; - // Size - width: parent.width; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - Connections { - onSendToParent: { - if (msg.method === 'needsLogIn' && root.activeView !== "needsLogIn") { - root.activeView = "needsLogIn"; - } else if (msg.method === 'showSecurityPicLightbox') { - lightboxPopup.titleText = "Your Security Pic"; - lightboxPopup.bodyImageSource = msg.securityImageSource; - lightboxPopup.bodyText = lightboxPopup.securityPicBodyText; - lightboxPopup.button1text = "CLOSE"; - lightboxPopup.button1method = function() { - lightboxPopup.visible = false; - } - lightboxPopup.visible = true; - } else { - sendToScript(msg); - } - } - } - } - MouseArea { - enabled: titleBarContainer.usernameDropdownVisible; - anchors.fill: parent; - onClicked: { - titleBarContainer.usernameDropdownVisible = false; - } - } - // - // TITLE BAR END - // - Rectangle { id: initialize; visible: root.activeView === "initialize"; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: -titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; @@ -219,8 +173,7 @@ Rectangle { id: installedAppsContainer; z: 998; visible: false; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: -titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; anchors.left: parent.left; anchors.bottom: parent.bottom; width: parent.width; @@ -422,8 +375,8 @@ Rectangle { // Anchors anchors.left: parent.left; anchors.right: parent.right; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 8 - titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; + anchors.topMargin: 8; anchors.bottom: parent.bottom; // @@ -585,6 +538,7 @@ Rectangle { delegate: PurchasedItem { itemName: title; itemId: id; + updateItemId: model.upgrade_id ? model.upgrade_id : ""; itemPreviewImageUrl: preview; itemHref: download_url; certificateId: certificate_id; @@ -596,7 +550,6 @@ Rectangle { cardBackVisible: model.cardBackVisible || false; isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; - upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; itemType: model.item_type; valid: model.valid; @@ -654,7 +607,7 @@ Rectangle { } else if (msg.method === "showTrashLightbox") { lightboxPopup.titleText = "Send \"" + msg.itemName + "\" to Trash"; lightboxPopup.bodyText = "Sending this item to the Trash means you will no longer own this item " + - "and it will be inaccessible to you from Purchases.\n\nThis action cannot be undone."; + "and it will be inaccessible to you from Inventory.\n\nThis action cannot be undone."; lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = function() { lightboxPopup.visible = false; @@ -1083,8 +1036,6 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updatePurchases': - referrerURL = message.referrerURL || ""; - titleBarContainer.referrerURL = message.referrerURL || ""; filterBar.text = message.filterText ? message.filterText : ""; break; case 'purchases_showMyItems': diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index cf293a06df..eb8aa0f809 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -335,7 +335,7 @@ Item { if (link.indexOf("users/") !== -1) { sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link}); } else { - sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link}); + sendSignalToWallet({method: 'transactionHistory_linkClicked', itemId: model.marketplace_item}); } } } diff --git a/interface/resources/qml/hifi/tablet/+webengine/BlocksWebView.qml b/interface/resources/qml/hifi/tablet/+webengine/BlocksWebView.qml new file mode 100644 index 0000000000..050515da37 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/+webengine/BlocksWebView.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 +import QtWebEngine 1.5 +import "../../controls" as Controls + +Controls.TabletWebView { + profile: WebEngineProfile { httpUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"} +} diff --git a/interface/resources/qml/hifi/tablet/BlocksWebView.qml b/interface/resources/qml/hifi/tablet/BlocksWebView.qml index 1e9eb3beb4..eaed88ba01 100644 --- a/interface/resources/qml/hifi/tablet/BlocksWebView.qml +++ b/interface/resources/qml/hifi/tablet/BlocksWebView.qml @@ -1,10 +1,7 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.TabletWebView { - profile: WebEngineProfile { httpUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"} } diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 267fb9f0cf..5f06e4fbab 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -2,8 +2,6 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQml 2.2 -import QtWebChannel 1.0 -import QtWebEngine 1.1 import "." diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index b19dcbb919..93a23f1b9d 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -3,10 +3,13 @@ import Hifi 1.0 import "../../dialogs" import "../../controls" +import stylesUit 1.0 -Item { +Rectangle { + HifiConstants { id: hifi; } id: tabletRoot objectName: "tabletRoot" + color: hifi.colors.baseGray property string username: "Unknown user" property string usernameShort: "Unknown user" property var rootMenu; diff --git a/interface/resources/qml/hifi/tablet/TabletWebView.qml b/interface/resources/qml/hifi/tablet/TabletWebView.qml index ff6be0480f..9eba7824e0 100644 --- a/interface/resources/qml/hifi/tablet/TabletWebView.qml +++ b/interface/resources/qml/hifi/tablet/TabletWebView.qml @@ -1,6 +1,4 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.TabletWebScreen { diff --git a/interface/resources/qml/hifi/tablet/WindowWebView.qml b/interface/resources/qml/hifi/tablet/WindowWebView.qml index 0f697d634e..632ab712cb 100644 --- a/interface/resources/qml/hifi/tablet/WindowWebView.qml +++ b/interface/resources/qml/hifi/tablet/WindowWebView.qml @@ -1,6 +1,4 @@ import QtQuick 2.0 -import QtWebEngine 1.2 - import "../../controls" as Controls Controls.WebView { diff --git a/interface/resources/qml/stylesUit/+android/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml similarity index 100% rename from interface/resources/qml/stylesUit/+android/HifiConstants.qml rename to interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index 767e69842d..1495045b85 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -20,7 +20,8 @@ * * @hifi-interface * @hifi-client-entity - * + * @hifi-avatar + * * @property {string} buildDate * @property {string} buildVersion * @property {string} qtVersion diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index daf2dd6363..bbe4d70ab6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -152,6 +152,7 @@ #include #include #include +#include #include #include #include "recording/ClipCache.h" @@ -198,15 +199,12 @@ #include "ui/AvatarInputs.h" #include "ui/DialogsManager.h" #include "ui/LoginDialog.h" -#include "ui/overlays/Cube3DOverlay.h" -#include "ui/overlays/Web3DOverlay.h" #include "ui/Snapshot.h" #include "ui/SnapshotAnimated.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" #include "ui/AnimStats.h" #include "ui/UpdateDialog.h" -#include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" #include "Util.h" @@ -235,6 +233,7 @@ #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "commerce/QmlMarketplace.h" #include "ResourceRequestObserver.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" @@ -350,6 +349,8 @@ static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn"; +static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f; + #if defined(Q_OS_ANDROID) static const QString TESTER_FILE = "/sdcard/_hifi_test_device.txt"; #endif @@ -602,8 +603,9 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt __android_log_write(ANDROID_LOG_FATAL,"Interface",local); abort(); } -#endif +#else qApp->getLogger()->addMessage(qPrintable(logMessage)); +#endif } } @@ -621,7 +623,7 @@ public: case NestableType::Entity: return getEntityModelProvider(static_cast(uuid)); case NestableType::Overlay: - return getOverlayModelProvider(static_cast(uuid)); + return nullptr; case NestableType::Avatar: return getAvatarModelProvider(uuid); } @@ -645,22 +647,6 @@ private: return provider; } - scriptable::ModelProviderPointer getOverlayModelProvider(OverlayID overlayID) { - scriptable::ModelProviderPointer provider; - auto &overlays = qApp->getOverlays(); - if (auto overlay = overlays.getOverlay(overlayID)) { - if (auto base3d = std::dynamic_pointer_cast(overlay)) { - provider = std::dynamic_pointer_cast(base3d); - provider->modelProviderType = NestableType::Overlay; - } else { - qCWarning(interfaceapp) << "no renderer for overlay ID" << overlayID.toString(); - } - } else { - qCWarning(interfaceapp) << "overlay not found" << overlayID.toString(); - } - return provider; - } - scriptable::ModelProviderPointer getAvatarModelProvider(QUuid sessionUUID) { scriptable::ModelProviderPointer provider; auto avatarManager = DependencyManager::get(); @@ -761,6 +747,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET); + // set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store + static const auto OCULUS_STORE_ARG = "--oculus-store"; + bool isStore = cmdOptionExists(argc, const_cast(argv), OCULUS_STORE_ARG); + qApp->setProperty(hifi::properties::OCULUS_STORE, isStore); + // Ignore any previous crashes if running from command line with a test script. bool inTestMode { false }; for (int i = 0; i < argc; ++i) { @@ -940,9 +931,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { // FIXME move to header, or better yet, design some kind of UI manager // to take care of highlighting keyboard focused items, rather than // continuing to overburden Application.cpp -std::shared_ptr _keyboardFocusHighlight{ nullptr }; -OverlayID _keyboardFocusHighlightID{ UNKNOWN_OVERLAY_ID }; - +QUuid _keyboardFocusHighlightID; OffscreenGLCanvas* _qmlShareContext { nullptr }; @@ -983,7 +972,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QApplication(argc, argv), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), +#ifndef Q_OS_ANDROID _logger(new FileLogger(this)), +#endif _previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)), _entitySimulation(new PhysicalEntitySimulation()), _physicsEngine(new PhysicsEngine(Vectors::ZERO)), @@ -1117,7 +1108,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } auto accountManager = DependencyManager::get(); +#ifndef Q_OS_ANDROID _logger->setSessionID(accountManager->getSessionID()); +#endif setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString()); setCrashAnnotation("main_thread_id", std::to_string((size_t)QThread::currentThreadId())); @@ -1138,10 +1131,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services."; #endif - // set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store - static const QString OCULUS_STORE_ARG = "--oculus-store"; - bool isStore = arguments().indexOf(OCULUS_STORE_ARG) != -1; - setProperty(hifi::properties::OCULUS_STORE, isStore); + bool isStore = property(hifi::properties::OCULUS_STORE).toBool(); + DependencyManager::get()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties? updateHeartbeat(); @@ -1211,9 +1202,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (tabletScriptingInterface) { tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr); } - getOverlays().deleteOverlay(getTabletScreenID()); - getOverlays().deleteOverlay(getTabletHomeButtonID()); - getOverlays().deleteOverlay(getTabletFrameID()); + auto entityScriptingInterface = DependencyManager::get(); + entityScriptingInterface->deleteEntity(getTabletScreenID()); + entityScriptingInterface->deleteEntity(getTabletHomeButtonID()); + entityScriptingInterface->deleteEntity(getTabletFrameID()); _failedToConnectToEntityServer = false; }); @@ -1303,10 +1295,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (isHMDMode()) { emit loginDialogFocusDisabled(); dialogsManager->hideLoginDialog(); - createLoginDialogOverlay(); + createLoginDialog(); } else { - getOverlays().deleteOverlay(_loginDialogOverlayID); - _loginDialogOverlayID = OverlayID(); + DependencyManager::get()->deleteEntity(_loginDialogID); + _loginDialogID = QUuid(); _loginStateManager.tearDown(); dialogsManager->showLoginDialog(); emit loginDialogFocusEnabled(); @@ -1897,25 +1889,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo SpacemouseManager::getInstance().init(); #endif - // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it - auto entityScriptingInterface = DependencyManager::get(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, - [this](const EntityItemID& entityItemID, const PointerEvent& event) { + // If the user clicks on an object, we will check that it's a web surface, and if so, set the focus to it + auto pointerManager = DependencyManager::get(); + auto keyboardFocusOperator = [this](const QUuid& id, const PointerEvent& event) { if (event.shouldFocus()) { - if (getEntities()->wantsKeyboardFocus(entityItemID)) { - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); - setKeyboardFocusEntity(entityItemID); - } else { + auto keyboard = DependencyManager::get(); + if (getEntities()->wantsKeyboardFocus(id)) { + setKeyboardFocusEntity(id); + } else if (!keyboard->containsID(id)) { // FIXME: this is a hack to make the keyboard work for now, since the keys would otherwise steal focus setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); } } - }); + }; + connect(pointerManager.data(), &PointerManager::triggerBeginEntity, keyboardFocusOperator); + connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, keyboardFocusOperator); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [this](const EntityItemID& entityItemID) { + auto entityScriptingInterface = DependencyManager::get(); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityItemID) { if (entityItemID == _keyboardFocusedEntity.get()) { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); } - }); + }, Qt::QueuedConnection); EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { if (_aboutToQuit) { @@ -1975,33 +1969,25 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return false; }); - EntityTree::setAddMaterialToOverlayOperator([this](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { - auto overlay = _overlays.getOverlay(overlayID); - if (overlay) { - overlay->addMaterial(material, parentMaterialName); - return true; + EntityTree::setGetEntityObjectOperator([this](const QUuid& id) -> QObject* { + auto entities = getEntities(); + if (auto entity = entities->renderableForEntityId(id)) { + return qobject_cast(entity.get()); } - return false; - }); - EntityTree::setRemoveMaterialFromOverlayOperator([this](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { - auto overlay = _overlays.getOverlay(overlayID); - if (overlay) { - overlay->removeMaterial(material, parentMaterialName); - return true; - } - return false; + return nullptr; }); - // Keyboard focus handling for Web overlays. - auto overlays = &(qApp->getOverlays()); - connect(overlays, &Overlays::overlayDeleted, [this](const OverlayID& overlayID) { - if (overlayID == _keyboardFocusedOverlay.get()) { - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); + EntityTree::setTextSizeOperator([this](const QUuid& id, const QString& text) { + auto entities = getEntities(); + if (auto entity = entities->renderableForEntityId(id)) { + if (auto renderable = std::dynamic_pointer_cast(entity)) { + return renderable->textSize(text); + } } + return QSizeF(0.0f, 0.0f); }); connect(this, &Application::aboutToQuit, [this]() { - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); @@ -2322,8 +2308,30 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); - EntityTreeRenderer::setGetAvatarUpOperator([] { - return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) { + if (billboardMode == BillboardMode::YAW) { + //rotate about vertical to face the camera + ViewFrustum frustum; + copyViewFrustum(frustum); + glm::vec3 dPosition = frustum.getPosition() - position; + // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees + float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); + return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); + } else if (billboardMode == BillboardMode::FULL) { + ViewFrustum frustum; + copyViewFrustum(frustum); + glm::vec3 cameraPos = frustum.getPosition(); + // use the referencial from the avatar, y isn't always up + glm::vec3 avatarUP = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(position - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if (glm::length2(s) > 0.0f) { + return glm::conjugate(glm::toQuat(glm::lookAt(cameraPos, position, avatarUP))); + } + } + return rotation; }); render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { @@ -2349,7 +2357,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); }); auto rootItemLoadedFunctor = [webSurface, url, isTablet] { - Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == OVERLAY_LOGIN_DIALOG.toString()); + Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString()); }; if (webSurface->getRootItem()) { rootItemLoadedFunctor(); @@ -2368,7 +2376,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { + if (rootItem && !cachedWebSurface) { // stop loading QMetaObject::invokeMethod(rootItem, "stop"); } @@ -2397,6 +2405,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); // Preload Tablet sounds + DependencyManager::get()->setEntityTree(qApp->getEntities()->getTree()); DependencyManager::get()->preloadSounds(); DependencyManager::get()->createKeyboard(); @@ -2604,11 +2613,10 @@ void Application::cleanupBeforeQuit() { _applicationStateDevice.reset(); { - if (_keyboardFocusHighlightID != UNKNOWN_OVERLAY_ID) { - getOverlays().deleteOverlay(_keyboardFocusHighlightID); - _keyboardFocusHighlightID = UNKNOWN_OVERLAY_ID; + if (_keyboardFocusHighlightID != UNKNOWN_ENTITY_ID) { + DependencyManager::get()->deleteEntity(_keyboardFocusHighlightID); + _keyboardFocusHighlightID = UNKNOWN_ENTITY_ID; } - _keyboardFocusHighlight = nullptr; } { @@ -3005,7 +3013,23 @@ void Application::initializeUi() { QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, QUrl{ "hifi/tablet/TabletMenu.qml" }, + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, }, commerceCallback); + + QmlContextCallback marketplaceCallback = [](QQmlContext* context) { + context->setContextProperty("MarketplaceScriptingInterface", new QmlMarketplace()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + }, marketplaceCallback); + + QmlContextCallback platformInfoCallback = [](QQmlContext* context) { + context->setContextProperty("PlatformInfo", new PlatformInfoScriptingInterface()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + }, platformInfoCallback); + QmlContextCallback ttsCallback = [](QQmlContext* context) { context->setContextProperty("TextToSpeech", DependencyManager::get().data()); }; @@ -3096,7 +3120,7 @@ void Application::initializeUi() { }); #if !defined(DISABLE_QML) - // Pre-create a couple of Web3D overlays to speed up tablet UI + // Pre-create a couple of offscreen surfaces to speed up tablet UI auto offscreenSurfaceCache = DependencyManager::get(); offscreenSurfaceCache->setOnRootContextCreated([&](const QString& rootObject, QQmlContext* surfaceContext) { if (rootObject == TabletScriptingInterface::QML) { @@ -3817,8 +3841,8 @@ static inline bool isKeyEvent(QEvent::Type type) { return type == QEvent::KeyPress || type == QEvent::KeyRelease; } -bool Application::handleKeyEventForFocusedEntityOrOverlay(QEvent* event) { - if (!_keyboardFocusedEntity.get().isInvalidID()) { +bool Application::handleKeyEventForFocusedEntity(QEvent* event) { + if (_keyboardFocusedEntity.get() != UNKNOWN_ENTITY_ID) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: @@ -3839,28 +3863,6 @@ bool Application::handleKeyEventForFocusedEntityOrOverlay(QEvent* event) { } } - if (_keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) { - switch (event->type()) { - case QEvent::KeyPress: - case QEvent::KeyRelease: { - // Only Web overlays can have focus. - auto overlay = std::dynamic_pointer_cast(getOverlays().getOverlay(_keyboardFocusedOverlay.get())); - if (overlay && overlay->getEventHandler()) { - event->setAccepted(false); - QCoreApplication::sendEvent(overlay->getEventHandler(), event); - if (event->isAccepted()) { - _lastAcceptedKeyPress = usecTimestampNow(); - return true; - } - } - } - break; - - default: - break; - } - } - return false; } @@ -3903,8 +3905,8 @@ bool Application::event(QEvent* event) { return false; } - // Allow focused Entities and Overlays to handle keyboard input - if (isKeyEvent(event->type()) && handleKeyEventForFocusedEntityOrOverlay(event)) { + // Allow focused Entities to handle keyboard input + if (isKeyEvent(event->type()) && handleKeyEventForFocusedEntity(event)) { return true; } @@ -4385,9 +4387,9 @@ void Application::mouseMoveEvent(QMouseEvent* event) { buttons, event->modifiers()); if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || - getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_OVERLAY_ID) { - getOverlays().mouseMoveEvent(&mappedEvent); + getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) { getEntities()->mouseMoveEvent(&mappedEvent); + getOverlays().mouseMoveEvent(&mappedEvent); } _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts @@ -4419,15 +4421,16 @@ void Application::mousePressEvent(QMouseEvent* event) { #else QPointF transformedPos; #endif - QMouseEvent mappedEvent(event->type(), - transformedPos, - event->screenPos(), event->button(), - event->buttons(), event->modifiers()); - getOverlays().mousePressEvent(&mappedEvent); + QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); + std::pair entityResult; if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - getEntities()->mousePressEvent(&mappedEvent); + entityResult = getEntities()->mousePressEvent(&mappedEvent); } + std::pair overlayResult = getOverlays().mousePressEvent(&mappedEvent); + + QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second; + setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID); _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts @@ -4467,10 +4470,10 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { event->screenPos(), event->button(), event->buttons(), event->modifiers()); - getOverlays().mouseDoublePressEvent(&mappedEvent); if (!_controllerScriptingInterface->areEntityClicksCaptured()) { getEntities()->mouseDoublePressEvent(&mappedEvent); } + getOverlays().mouseDoublePressEvent(&mappedEvent); // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { @@ -4494,8 +4497,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { event->screenPos(), event->button(), event->buttons(), event->modifiers()); - getOverlays().mouseReleaseEvent(&mappedEvent); getEntities()->mouseReleaseEvent(&mappedEvent); + getOverlays().mouseReleaseEvent(&mappedEvent); _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts @@ -4943,7 +4946,11 @@ void Application::idle() { // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing // details normally. +#ifdef Q_OS_ANDROID + bool showWarnings = false; +#else bool showWarnings = getLogger()->extraDebugging(); +#endif PerformanceWarning warn(showWarnings, "idle()"); { @@ -4957,31 +4964,19 @@ void Application::idle() { update(glm::clamp(secondsSinceLastUpdate, 0.0f, BIGGEST_DELTA_TIME_SECS)); } - - // Update focus highlight for entity or overlay. - { - if (!_keyboardFocusedEntity.get().isInvalidID() || _keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) { + { // Update keyboard focus highlight + if (!_keyboardFocusedEntity.get().isInvalidID()) { const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress; if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); } else { - // update position of highlight overlay - if (!_keyboardFocusedEntity.get().isInvalidID()) { - auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get()); - if (entity && _keyboardFocusHighlight) { - _keyboardFocusHighlight->setWorldOrientation(entity->getWorldOrientation()); - _keyboardFocusHighlight->setWorldPosition(entity->getWorldPosition()); - } - } else { - // Only Web overlays can have focus. - auto overlay = - std::dynamic_pointer_cast(getOverlays().getOverlay(_keyboardFocusedOverlay.get())); - if (overlay && _keyboardFocusHighlight) { - _keyboardFocusHighlight->setWorldOrientation(overlay->getWorldOrientation()); - _keyboardFocusHighlight->setWorldPosition(overlay->getWorldPosition()); - } + if (auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get())) { + EntityItemProperties properties; + properties.setPosition(entity->getWorldPosition()); + properties.setRotation(entity->getWorldOrientation()); + properties.setDimensions(entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + DependencyManager::get()->editEntity(_keyboardFocusHighlightID, properties); } } } @@ -5779,118 +5774,80 @@ void Application::rotationModeChanged() const { } void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions) { - // Create focus if (qApp->getLoginDialogPoppedUp()) { return; } - if (_keyboardFocusHighlightID == UNKNOWN_OVERLAY_ID || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) { - _keyboardFocusHighlight = std::make_shared(); - _keyboardFocusHighlight->setAlpha(1.0f); - _keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 }); - _keyboardFocusHighlight->setIsSolid(false); - _keyboardFocusHighlight->setPulseMin(0.5); - _keyboardFocusHighlight->setPulseMax(1.0); - _keyboardFocusHighlight->setColorPulse(1.0); - _keyboardFocusHighlight->setIgnorePickIntersection(true); - _keyboardFocusHighlight->setDrawInFront(false); - _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); + + auto entityScriptingInterface = DependencyManager::get(); + if (_keyboardFocusHighlightID == UNKNOWN_ENTITY_ID || !entityScriptingInterface->isAddedEntity(_keyboardFocusHighlightID)) { + EntityItemProperties properties; + properties.setType(EntityTypes::Box); + properties.setAlpha(1.0f); + properties.setColor({ 0xFF, 0xEF, 0x00 }); + properties.setPrimitiveMode(PrimitiveMode::LINES); + properties.getPulse().setMin(0.5); + properties.getPulse().setMax(1.0f); + properties.getPulse().setColorMode(PulseMode::IN_PHASE); + properties.setIgnorePickIntersection(true); + _keyboardFocusHighlightID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); } // Position focus - _keyboardFocusHighlight->setWorldOrientation(rotation); - _keyboardFocusHighlight->setWorldPosition(position); - _keyboardFocusHighlight->setDimensions(dimensions); - _keyboardFocusHighlight->setVisible(true); + EntityItemProperties properties; + properties.setPosition(position); + properties.setRotation(rotation); + properties.setDimensions(dimensions); + properties.setVisible(true); + entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties); } QUuid Application::getKeyboardFocusEntity() const { return _keyboardFocusedEntity.get(); } -static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f; - -void Application::setKeyboardFocusEntity(const EntityItemID& entityItemID) { - if (qApp->getLoginDialogPoppedUp()) { - return; - } - if (_keyboardFocusedEntity.get() != entityItemID) { - _keyboardFocusedEntity.set(entityItemID); - - if (_keyboardFocusHighlight && _keyboardFocusedOverlay.get() == UNKNOWN_OVERLAY_ID) { - _keyboardFocusHighlight->setVisible(false); - } - - if (entityItemID == UNKNOWN_ENTITY_ID) { - return; - } - - auto entityScriptingInterface = DependencyManager::get(); - auto properties = entityScriptingInterface->getEntityProperties(entityItemID); - if (!properties.getLocked() && properties.getVisible()) { - - auto entities = getEntities(); - auto entityId = _keyboardFocusedEntity.get(); - if (entities->wantsKeyboardFocus(entityId)) { - entities->setProxyWindow(entityId, _window->windowHandle()); - if (_keyboardMouseDevice->isActive()) { - _keyboardMouseDevice->pluginFocusOutEvent(); - } - _lastAcceptedKeyPress = usecTimestampNow(); - - auto entity = getEntities()->getEntity(entityId); - if (entity) { - setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), - entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); - } - } - } - } -} - -OverlayID Application::getKeyboardFocusOverlay() { - return _keyboardFocusedOverlay.get(); -} - -void Application::setKeyboardFocusOverlay(const OverlayID& overlayID) { - if (overlayID != _keyboardFocusedOverlay.get()) { - if (qApp->getLoginDialogPoppedUp() && !_loginDialogOverlayID.isNull()) { - if (overlayID == _loginDialogOverlayID) { +void Application::setKeyboardFocusEntity(const QUuid& id) { + if (_keyboardFocusedEntity.get() != id) { + if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) { + if (id == _loginDialogID) { emit loginDialogFocusEnabled(); } else { - // that's the only overlay we want in focus; + // that's the only entity we want in focus; return; } } - _keyboardFocusedOverlay.set(overlayID); + _keyboardFocusedEntity.set(id); - if (_keyboardFocusHighlight && _keyboardFocusedEntity.get() == UNKNOWN_ENTITY_ID) { - _keyboardFocusHighlight->setVisible(false); - } + auto entityScriptingInterface = DependencyManager::get(); + if (id != UNKNOWN_ENTITY_ID) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_VISIBLE; + desiredProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; + auto properties = entityScriptingInterface->getEntityProperties(id); + if (properties.getVisible()) { + auto entities = getEntities(); + auto entityId = _keyboardFocusedEntity.get(); + if (entities->wantsKeyboardFocus(entityId)) { + entities->setProxyWindow(entityId, _window->windowHandle()); + if (_keyboardMouseDevice->isActive()) { + _keyboardMouseDevice->pluginFocusOutEvent(); + } + _lastAcceptedKeyPress = usecTimestampNow(); - if (overlayID == UNKNOWN_OVERLAY_ID) { - return; - } - - auto overlayType = getOverlays().getOverlayType(overlayID); - auto isVisible = getOverlays().getProperty(overlayID, "visible").value.toBool(); - if (overlayType == Web3DOverlay::TYPE && isVisible) { - auto overlay = std::dynamic_pointer_cast(getOverlays().getOverlay(overlayID)); - overlay->setProxyWindow(_window->windowHandle()); - - if (_keyboardMouseDevice->isActive()) { - _keyboardMouseDevice->pluginFocusOutEvent(); - } - _lastAcceptedKeyPress = usecTimestampNow(); - - if (overlay->getProperty("showKeyboardFocusHighlight").toBool()) { - auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR; - const float OVERLAY_DEPTH = 0.0105f; - setKeyboardFocusHighlight(overlay->getWorldPosition(), overlay->getWorldOrientation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH)); - } else if (_keyboardFocusHighlight) { - _keyboardFocusHighlight->setVisible(false); + if (properties.getShowKeyboardFocusHighlight()) { + if (auto entity = entities->getEntity(entityId)) { + setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), + entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + return; + } + } + } } } + + EntityItemProperties properties; + properties.setVisible(false); + entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties); } } @@ -6267,6 +6224,19 @@ void Application::update(float deltaTime) { auto grabManager = DependencyManager::get(); grabManager->simulateGrabs(); + // TODO: break these out into distinct perfTimers when they prove interesting + { + PROFILE_RANGE(app, "PickManager"); + PerformanceTimer perfTimer("pickManager"); + DependencyManager::get()->update(); + } + + { + PROFILE_RANGE(app, "PointerManager"); + PerformanceTimer perfTimer("pointerManager"); + DependencyManager::get()->update(); + } + QSharedPointer avatarManager = DependencyManager::get(); { @@ -6280,38 +6250,57 @@ void Application::update(float deltaTime) { PROFILE_RANGE(simulation_physics, "PrePhysics"); PerformanceTimer perfTimer("prePhysics)"); { + PROFILE_RANGE(simulation_physics, "RemoveEntities"); const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics(); - _physicsEngine->removeObjects(motionStates); + { + PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size()); + _physicsEngine->removeObjects(motionStates); + } _entitySimulation->deleteObjectsRemovedFromPhysics(); } - VectorOfMotionStates motionStates; - getEntities()->getTree()->withReadLock([&] { - _entitySimulation->getObjectsToAddToPhysics(motionStates); - _physicsEngine->addObjects(motionStates); - - }); - getEntities()->getTree()->withReadLock([&] { - _entitySimulation->getObjectsToChange(motionStates); - VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); - _entitySimulation->setObjectsToChange(stillNeedChange); - }); + { + PROFILE_RANGE(simulation_physics, "AddEntities"); + VectorOfMotionStates motionStates; + getEntities()->getTree()->withReadLock([&] { + _entitySimulation->getObjectsToAddToPhysics(motionStates); + PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size()); + _physicsEngine->addObjects(motionStates); + }); + } + { + VectorOfMotionStates motionStates; + PROFILE_RANGE(simulation_physics, "ChangeEntities"); + getEntities()->getTree()->withReadLock([&] { + _entitySimulation->getObjectsToChange(motionStates); + VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); + _entitySimulation->setObjectsToChange(stillNeedChange); + }); + } _entitySimulation->applyDynamicChanges(); t1 = std::chrono::high_resolution_clock::now(); - PhysicsEngine::Transaction transaction; - avatarManager->buildPhysicsTransaction(transaction); - _physicsEngine->processTransaction(transaction); - avatarManager->handleProcessedPhysicsTransaction(transaction); - myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); - _physicsEngine->processTransaction(transaction); - myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction); - myAvatar->prepareForPhysicsSimulation(); - _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) { - dynamic->prepareForPhysicsSimulation(); - }); + { + PROFILE_RANGE(simulation_physics, "Avatars"); + PhysicsEngine::Transaction transaction; + avatarManager->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + avatarManager->handleProcessedPhysicsTransaction(transaction); + myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction); + myAvatar->prepareForPhysicsSimulation(); + _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying()); + } + + { + PROFILE_RANGE(simulation_physics, "PrepareActions"); + _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) { + dynamic->prepareForPhysicsSimulation(); + }); + } } auto t2 = std::chrono::high_resolution_clock::now(); { @@ -6414,22 +6403,9 @@ void Application::update(float deltaTime) { updateLOD(deltaTime); - if (!_loginDialogOverlayID.isNull()) { - _loginStateManager.update(getMyAvatar()->getDominantHand(), _loginDialogOverlayID); - updateLoginDialogOverlayPosition(); - } - - // TODO: break these out into distinct perfTimers when they prove interesting - { - PROFILE_RANGE(app, "PickManager"); - PerformanceTimer perfTimer("pickManager"); - DependencyManager::get()->update(); - } - - { - PROFILE_RANGE(app, "PointerManager"); - PerformanceTimer perfTimer("pointerManager"); - DependencyManager::get()->update(); + if (!_loginDialogID.isNull()) { + _loginStateManager.update(getMyAvatar()->getDominantHand(), _loginDialogID); + updateLoginDialogPosition(); } { @@ -7195,7 +7171,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe connect(scriptEngine.data(), &ScriptEngine::finished, clipboardScriptable, &ClipboardScriptingInterface::deleteLater); scriptEngine->registerGlobalObject("Overlays", &_overlays); - qScriptRegisterMetaType(scriptEngine.data(), OverlayPropertyResultToScriptValue, OverlayPropertyResultFromScriptValue); qScriptRegisterMetaType(scriptEngine.data(), RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); @@ -7312,8 +7287,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get().data()); - qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); - registerInteractiveWindowMetaType(scriptEngine.data()); auto pickScriptingInterface = DependencyManager::get(); @@ -8284,7 +8257,7 @@ void Application::toggleLogDialog() { bool keepOnTop =_keepLogWindowOnTop.get(); #ifdef Q_OS_WIN _logDialog = new LogDialog(keepOnTop ? qApp->getWindow() : nullptr, getLogger()); -#else +#elif !defined(Q_OS_ANDROID) _logDialog = new LogDialog(nullptr, getLogger()); if (keepOnTop) { @@ -8835,45 +8808,47 @@ void Application::setShowBulletConstraintLimits(bool value) { _physicsEngine->setShowBulletConstraintLimits(value); } -void Application::createLoginDialogOverlay() { - const glm::vec2 LOGIN_OVERLAY_DIMENSIONS{ 0.89f, 0.5f }; - const auto OVERLAY_OFFSET = glm::vec2(0.7f, -0.1f); +void Application::createLoginDialog() { + const glm::vec3 LOGIN_DIMENSIONS { 0.89f, 0.5f, 0.01f }; + const auto OFFSET = glm::vec2(0.7f, -0.1f); auto cameraPosition = _myCamera.getPosition(); auto cameraOrientation = _myCamera.getOrientation(); auto upVec = getMyAvatar()->getWorldOrientation() * Vectors::UNIT_Y; auto headLookVec = (cameraOrientation * Vectors::FRONT); // DEFAULT_DPI / tablet scale percentage - const float OVERLAY_DPI = 31.0f / (75.0f / 100.0f); - auto offset = headLookVec * OVERLAY_OFFSET.x; - auto overlayPosition = (cameraPosition + offset) + (upVec * OVERLAY_OFFSET.y); - QVariantMap overlayProperties = { - { "name", "LoginDialogOverlay" }, - { "url", OVERLAY_LOGIN_DIALOG }, - { "position", vec3toVariant(overlayPosition) }, - { "orientation", quatToVariant(cameraOrientation) }, - { "isSolid", true }, - { "grabbable", false }, - { "ignorePickIntersection", false }, - { "alpha", 1.0 }, - { "dimensions", vec2ToVariant(LOGIN_OVERLAY_DIMENSIONS)}, - { "dpi", OVERLAY_DPI }, - { "visible", true } - }; - auto& overlays = getOverlays(); - _loginDialogOverlayID = overlays.addOverlay("web3d", overlayProperties); - auto loginOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_loginDialogOverlayID)); + const float DPI = 31.0f / (75.0f / 100.0f); + auto offset = headLookVec * OFFSET.x; + auto position = (cameraPosition + offset) + (upVec * OFFSET.y); + + EntityItemProperties properties; + properties.setType(EntityTypes::Web); + properties.setName("LoginDialogEntity"); + properties.setSourceUrl(LOGIN_DIALOG.toString()); + properties.setPosition(position); + properties.setRotation(cameraOrientation); + properties.setDimensions(LOGIN_DIMENSIONS); + properties.setPrimitiveMode(PrimitiveMode::SOLID); + properties.getGrab().setGrabbable(false); + properties.setIgnorePickIntersection(false); + properties.setAlpha(1.0f); + properties.setDPI(DPI); + properties.setVisible(true); + + auto entityScriptingInterface = DependencyManager::get(); + _loginDialogID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); + auto keyboard = DependencyManager::get().data(); - if (!keyboard->getAnchorID().isNull() && !_loginDialogOverlayID.isNull()) { - const auto KEYBOARD_LOCAL_ORIENTATION = glm::quat(0.0f, 0.0, 1.0f, 0.25f); + if (!keyboard->getAnchorID().isNull() && !_loginDialogID.isNull()) { auto keyboardLocalOffset = cameraOrientation * glm::vec3(-0.4f * getMyAvatar()->getSensorToWorldScale(), -0.3f, 0.2f); - QVariantMap properties { - { "position", vec3toVariant(overlayPosition + keyboardLocalOffset) }, - { "orientation", quatToVariant(cameraOrientation * KEYBOARD_LOCAL_ORIENTATION) }, - }; - overlays.editOverlay(keyboard->getAnchorID(), properties); + + EntityItemProperties properties; + properties.setPosition(position + keyboardLocalOffset); + properties.setRotation(cameraOrientation * Quaternions::Y_180); + + entityScriptingInterface->editEntity(keyboard->getAnchorID(), properties); keyboard->setResetKeyboardPositionOnRaise(false); } - setKeyboardFocusOverlay(_loginDialogOverlayID); + setKeyboardFocusEntity(_loginDialogID); emit loginDialogFocusEnabled(); getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(false); getApplicationCompositor().getReticleInterface()->setVisible(false); @@ -8882,38 +8857,43 @@ void Application::createLoginDialogOverlay() { } } -void Application::updateLoginDialogOverlayPosition() { +void Application::updateLoginDialogPosition() { const float LOOK_AWAY_THRESHOLD_ANGLE = 70.0f; - const auto OVERLAY_OFFSET = glm::vec2(0.7f, -0.1f); - auto& overlays = getOverlays(); - auto loginOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_loginDialogOverlayID)); - auto overlayPositionVec = loginOverlay->getWorldPosition(); + const auto OFFSET = glm::vec2(0.7f, -0.1f); + + auto entityScriptingInterface = DependencyManager::get(); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + auto properties = entityScriptingInterface->getEntityProperties(_loginDialogID, desiredProperties); + auto positionVec = properties.getPosition(); auto cameraPositionVec = _myCamera.getPosition(); auto cameraOrientation = cancelOutRollAndPitch(_myCamera.getOrientation()); auto headLookVec = (cameraOrientation * Vectors::FRONT); - auto overlayToHeadVec = overlayPositionVec - cameraPositionVec; - auto pointAngle = (glm::acos(glm::dot(glm::normalize(overlayToHeadVec), glm::normalize(headLookVec))) * 180.0f / PI); + auto entityToHeadVec = positionVec - cameraPositionVec; + auto pointAngle = (glm::acos(glm::dot(glm::normalize(entityToHeadVec), glm::normalize(headLookVec))) * 180.0f / PI); auto upVec = getMyAvatar()->getWorldOrientation() * Vectors::UNIT_Y; - auto offset = headLookVec * OVERLAY_OFFSET.x; - auto newOverlayPositionVec = (cameraPositionVec + offset) + (upVec * OVERLAY_OFFSET.y); - auto newOverlayOrientation = glm::inverse(glm::quat_cast(glm::lookAt(newOverlayPositionVec, cameraPositionVec, upVec))) * Quaternions::Y_180; + auto offset = headLookVec * OFFSET.x; + auto newPositionVec = (cameraPositionVec + offset) + (upVec * OFFSET.y); - bool overlayOutOfBounds = glm::distance(overlayPositionVec, cameraPositionVec) > 1.0f; + bool outOfBounds = glm::distance(positionVec, cameraPositionVec) > 1.0f; - if (pointAngle > LOOK_AWAY_THRESHOLD_ANGLE || overlayOutOfBounds) { - QVariantMap properties { - {"position", vec3toVariant(newOverlayPositionVec)}, - {"orientation", quatToVariant(newOverlayOrientation)} - }; - overlays.editOverlay(_loginDialogOverlayID, properties); - const auto KEYBOARD_LOCAL_ORIENTATION = glm::quat(0.0f, 0.0, 1.0f, 0.25f); - auto keyboardLocalOffset = newOverlayOrientation * glm::vec3(-0.4f * getMyAvatar()->getSensorToWorldScale(), -0.3f, 0.2f); - QVariantMap keyboardProperties { - { "position", vec3toVariant(newOverlayPositionVec + keyboardLocalOffset) }, - { "orientation", quatToVariant(newOverlayOrientation * KEYBOARD_LOCAL_ORIENTATION) }, - }; - auto keyboard = DependencyManager::get().data(); - overlays.editOverlay(keyboard->getAnchorID(), keyboardProperties); + if (pointAngle > LOOK_AWAY_THRESHOLD_ANGLE || outOfBounds) { + { + EntityItemProperties properties; + properties.setPosition(newPositionVec); + properties.setRotation(cameraOrientation); + entityScriptingInterface->editEntity(_loginDialogID, properties); + } + + { + glm::vec3 keyboardLocalOffset = cameraOrientation * glm::vec3(-0.4f * getMyAvatar()->getSensorToWorldScale(), -0.3f, 0.2f); + glm::quat keyboardOrientation = cameraOrientation * glm::quat(glm::radians(glm::vec3(-30.0f, 180.0f, 0.0f))); + + EntityItemProperties properties; + properties.setPosition(newPositionVec + keyboardLocalOffset); + properties.setRotation(keyboardOrientation); + entityScriptingInterface->editEntity(DependencyManager::get()->getAnchorID(), properties); + } } } @@ -8930,10 +8910,9 @@ void Application::onDismissedLoginDialog() { loginDialogPoppedUp.set(false); auto keyboard = DependencyManager::get().data(); keyboard->setResetKeyboardPositionOnRaise(true); - if (!_loginDialogOverlayID.isNull()) { - // deleting overlay. - getOverlays().deleteOverlay(_loginDialogOverlayID); - _loginDialogOverlayID = OverlayID(); + if (!_loginDialogID.isNull()) { + DependencyManager::get()->deleteEntity(_loginDialogID); + _loginDialogID = QUuid(); _loginStateManager.tearDown(); } resumeAfterLoginDialogActionTaken(); @@ -9099,12 +9078,12 @@ void Application::updateSystemTabletMode() { } } -OverlayID Application::getTabletScreenID() const { +QUuid Application::getTabletScreenID() const { auto HMD = DependencyManager::get(); return HMD->getCurrentTabletScreenID(); } -OverlayID Application::getTabletHomeButtonID() const { +QUuid Application::getTabletHomeButtonID() const { auto HMD = DependencyManager::get(); return HMD->getCurrentHomeButtonID(); } @@ -9115,7 +9094,7 @@ QUuid Application::getTabletFrameID() const { } QVector Application::getTabletIDs() const { - // Most important overlays first. + // Most important first. QVector result; auto HMD = DependencyManager::get(); result << HMD->getCurrentTabletScreenID(); diff --git a/interface/src/Application.h b/interface/src/Application.h index de79e91cb2..afd9f5f12f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -251,7 +251,9 @@ public: void setActiveDisplayPlugin(const QString& pluginName); +#ifndef Q_OS_ANDROID FileLogger* getLogger() const { return _logger; } +#endif float getRenderResolutionScale() const; @@ -299,10 +301,10 @@ public: void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); - OverlayID getTabletScreenID() const; - OverlayID getTabletHomeButtonID() const; - QUuid getTabletFrameID() const; // may be an entity or an overlay - QVector getTabletIDs() const; // In order of most important IDs first. + QUuid getTabletScreenID() const; + QUuid getTabletHomeButtonID() const; + QUuid getTabletFrameID() const; + QVector getTabletIDs() const; void setAvatarOverrideUrl(const QUrl& url, bool save); void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } @@ -325,8 +327,8 @@ public: void setOtherAvatarsReplicaCount(int count) { DependencyManager::get()->setReplicaCount(count); } bool getLoginDialogPoppedUp() const { return _loginDialogPoppedUp; } - void createLoginDialogOverlay(); - void updateLoginDialogOverlayPosition(); + void createLoginDialog(); + void updateLoginDialogPosition(); // Check if a headset is connected bool hasRiftControllers(); @@ -440,10 +442,7 @@ public slots: void setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions); QUuid getKeyboardFocusEntity() const; // thread-safe - void setKeyboardFocusEntity(const EntityItemID& entityItemID); - - OverlayID getKeyboardFocusOverlay(); - void setKeyboardFocusOverlay(const OverlayID& overlayID); + void setKeyboardFocusEntity(const QUuid& id); void addAssetToWorldMessageClose(); @@ -534,7 +533,7 @@ private: void init(); void pauseUntilLoginDetermined(); void resumeAfterLoginDialogActionTaken(); - bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); + bool handleKeyEventForFocusedEntity(QEvent* event); bool handleFileOpenEvent(QFileOpenEvent* event); void cleanupBeforeQuit(); @@ -599,7 +598,9 @@ private: bool _aboutToQuit { false }; +#ifndef Q_OS_ANDROID FileLogger* _logger { nullptr }; +#endif bool _previousSessionCrashed; @@ -706,7 +707,7 @@ private: QString _previousAvatarSkeletonModel; float _previousAvatarTargetScale; CameraMode _previousCameraMode; - OverlayID _loginDialogOverlayID; + QUuid _loginDialogID; LoginStateManager _loginStateManager; quint64 _lastFaceTrackerUpdate; @@ -724,7 +725,6 @@ private: DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface(); ThreadSafeValueCache _keyboardFocusedEntity; - ThreadSafeValueCache _keyboardFocusedOverlay; quint64 _lastAcceptedKeyPress = 0; bool _isForeground = true; // starts out assumed to be in foreground bool _isGLInitialized { false }; diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index f1bc6820eb..4623e7d929 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -21,6 +21,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * */ diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp index b9be58f04b..33328f54cc 100644 --- a/interface/src/InterfaceParentFinder.cpp +++ b/interface/src/InterfaceParentFinder.cpp @@ -50,15 +50,6 @@ SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& s return avatarManager->getMyAvatar(); } - // search overlays - auto& overlays = qApp->getOverlays(); - auto overlay = overlays.getOverlay(parentID); - parent = std::dynamic_pointer_cast(overlay); // this will return nullptr for non-3d overlays - if (!parent.expired()) { - success = true; - return parent; - } - success = false; return parent; } diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 6206fd3539..559bae1779 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -36,6 +36,7 @@ class AABox; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} presentTime Read-only. * @property {number} engineRunTime Read-only. diff --git a/interface/src/LocationBookmarks.h b/interface/src/LocationBookmarks.h index 70ea50e2e7..8cd8e40634 100644 --- a/interface/src/LocationBookmarks.h +++ b/interface/src/LocationBookmarks.h @@ -21,6 +21,7 @@ * * @hifi-client-entity * @hifi-interface + * @hifi-avatar */ class LocationBookmarks : public Bookmarks, public Dependency { diff --git a/interface/src/LoginStateManager.cpp b/interface/src/LoginStateManager.cpp index 8811303f7d..0a09d33775 100644 --- a/interface/src/LoginStateManager.cpp +++ b/interface/src/LoginStateManager.cpp @@ -170,7 +170,7 @@ void LoginStateManager::setUp() { const unsigned int leftHand = 0; QVariantMap leftPointerProperties { { "joint", "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" }, - { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, { "triggers", leftPointerTriggerProperties }, { "posOffset", vec3toVariant(grabPointSphereOffsetLeft + malletOffset) }, { "hover", true }, @@ -197,7 +197,7 @@ void LoginStateManager::setUp() { rightPointerTriggerProperties = QList({rtClick1, rtClick2}); QVariantMap rightPointerProperties{ { "joint", "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" }, - { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, { "triggers", rightPointerTriggerProperties }, { "posOffset", vec3toVariant(grabPointSphereOffsetRight + malletOffset) }, { "hover", true }, @@ -212,7 +212,7 @@ void LoginStateManager::setUp() { pointers->enablePointer(_rightLoginPointerID); } -void LoginStateManager::update(const QString dominantHand, const QUuid loginOverlayID) { +void LoginStateManager::update(const QString& dominantHand, const QUuid& loginEntityID) { if (!isSetUp()) { return; } @@ -224,8 +224,8 @@ void LoginStateManager::update(const QString dominantHand, const QUuid loginOver if (pointers && raypicks) { const auto rightObjectID = raypicks->getPrevRayPickResult(_rightLoginPointerID)["objectID"].toUuid(); const auto leftObjectID = raypicks->getPrevRayPickResult(_leftLoginPointerID)["objectID"].toUuid(); - const QString leftMode = (leftObjectID.isNull() || leftObjectID != loginOverlayID) ? "" : "full"; - const QString rightMode = (rightObjectID.isNull() || rightObjectID != loginOverlayID) ? "" : "full"; + const QString leftMode = (leftObjectID.isNull() || leftObjectID != loginEntityID) ? "" : "full"; + const QString rightMode = (rightObjectID.isNull() || rightObjectID != loginEntityID) ? "" : "full"; pointers->setRenderState(_leftLoginPointerID, leftMode); pointers->setRenderState(_rightLoginPointerID, rightMode); if (_dominantHand == "left" && !leftObjectID.isNull()) { diff --git a/interface/src/LoginStateManager.h b/interface/src/LoginStateManager.h index ad25e87ee6..b898303ba6 100644 --- a/interface/src/LoginStateManager.h +++ b/interface/src/LoginStateManager.h @@ -26,7 +26,7 @@ public: void setUp(); void tearDown(); - void update(const QString dominantHand, const QUuid loginOverlayID); + void update(const QString& dominantHand, const QUuid& loginObjectID); bool isSetUp() const { return (_leftLoginPointerID > PointerEvent::INVALID_POINTER_ID) && (_rightLoginPointerID > PointerEvent::INVALID_POINTER_ID); } diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 84325da473..db74b34d91 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -294,13 +294,6 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename } mapping.insert(JOINT_FIELD, joints); - - if (!mapping.contains(FREE_JOINT_FIELD)) { - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); - } // If there are no blendshape mappings, and we detect that this is likely a mixamo file, // then we can add the default mixamo to "faceshift" mappings diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 1bdb170b60..d67341990d 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -58,11 +58,6 @@ _hfmModel(hfmModel) form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox()); form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox()); - form->addRow("Free Joints:", _freeJoints = new QVBoxLayout()); - QPushButton* newFreeJoint = new QPushButton("New Free Joint"); - _freeJoints->addWidget(newFreeJoint); - connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint())); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Reset); connect(buttons, SIGNAL(accepted()), SLOT(accept())); @@ -102,11 +97,6 @@ QVariantHash ModelPropertiesDialog::getMapping() const { insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText()); insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText()); - mapping.remove(FREE_JOINT_FIELD); - for (int i = 0; i < _freeJoints->count() - 1; i++) { - QComboBox* box = static_cast(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget()); - mapping.insertMulti(FREE_JOINT_FIELD, box->currentText()); - } mapping.insert(JOINT_FIELD, joints); return mapping; @@ -133,16 +123,6 @@ void ModelPropertiesDialog::reset() { setJointText(_headJoint, jointHash.value("jointHead").toString()); setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString()); setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString()); - - while (_freeJoints->count() > 1) { - delete _freeJoints->itemAt(0)->widget(); - } - foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { - QString jointName = joint.toString(); - if (_hfmModel.jointIndices.contains(jointName)) { - createNewFreeJoint(jointName); - } - } } void ModelPropertiesDialog::chooseTextureDirectory() { @@ -176,20 +156,6 @@ void ModelPropertiesDialog::updatePivotJoint() { _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); } -void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { - QWidget* freeJoint = new QWidget(); - QHBoxLayout* freeJointLayout = new QHBoxLayout(); - freeJointLayout->setContentsMargins(QMargins()); - freeJoint->setLayout(freeJointLayout); - QComboBox* jointBox = createJointBox(false); - jointBox->setCurrentText(joint); - freeJointLayout->addWidget(jointBox, 1); - QPushButton* deleteJoint = new QPushButton("Delete"); - freeJointLayout->addWidget(deleteJoint); - freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater())); - _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); -} - QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { QComboBox* box = new QComboBox(); if (withNone) { diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 7019d239ff..8cf9bd5248 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -39,7 +39,6 @@ private slots: void chooseTextureDirectory(); void chooseScriptDirectory(); void updatePivotJoint(); - void createNewFreeJoint(const QString& joint = QString()); private: QComboBox* createJointBox(bool withNone = true) const; @@ -66,7 +65,6 @@ private: QComboBox* _headJoint = nullptr; QComboBox* _leftHandJoint = nullptr; QComboBox* _rightHandJoint = nullptr; - QVBoxLayout* _freeJoints = nullptr; }; #endif // hifi_ModelPropertiesDialog_h diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h index b22ab73837..7e2acdb8ac 100644 --- a/interface/src/SpeechRecognizer.h +++ b/interface/src/SpeechRecognizer.h @@ -27,6 +27,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class SpeechRecognizer : public QObject, public Dependency { Q_OBJECT diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index 41cee8d17d..912e337670 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -31,6 +31,7 @@ class AudioScope : public QObject, public Dependency { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} scopeInput Read-only. * @property {number} scopeOutputLeft Read-only. diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5fb9c9a0ee..a1826076fa 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -569,7 +569,7 @@ void AvatarActionHold::lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, cons } btTransform worldTrans = rigidBody->getWorldTransform(); - AnimPose worldBodyPose(glm::vec3(1), bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin())); + AnimPose worldBodyPose(1.0f, bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin())); // transform the body transform into sensor space with the prePhysics sensor-to-world matrix. // then transform it back into world uisng the postAvatarUpdate sensor-to-world matrix. diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1eb87c16f0..0b33220c01 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -652,28 +652,25 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic PROFILE_RANGE(simulation_physics, __FUNCTION__); - float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results + float bulletDistance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results glm::vec3 rayDirection = glm::normalize(ray.direction); - std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), distance, QVector()); + std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), bulletDistance, QVector()); if (physicsResults.size() > 0) { glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY, rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY, rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY }; - MyCharacterController::RayAvatarResult rayAvatarResult; - AvatarPointer avatar = nullptr; - - BoxFace face = BoxFace::UNKNOWN_FACE; - glm::vec3 surfaceNormal; - QVariantMap extraInfo; - for (auto &hit : physicsResults) { auto avatarID = hit._intersectWithAvatar; if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) || (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID))) { continue; } - + + MyCharacterController::RayAvatarResult rayAvatarResult; + BoxFace face = BoxFace::UNKNOWN_FACE; + QVariantMap extraInfo; + AvatarPointer avatar = nullptr; if (_myAvatar->getSessionUUID() != avatarID) { auto avatarMap = getHashCopy(); AvatarHash::iterator itr = avatarMap.find(avatarID); @@ -683,46 +680,45 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic } else { avatar = _myAvatar; } + if (!hit._isBound) { rayAvatarResult = hit; } else if (avatar) { auto &multiSpheres = avatar->getMultiSphereShapes(); if (multiSpheres.size() > 0) { - std::vector boxHits; + MyCharacterController::RayAvatarResult boxHit; + boxHit._distance = FLT_MAX; + for (size_t i = 0; i < hit._boundJoints.size(); i++) { assert(hit._boundJoints[i] < multiSpheres.size()); auto &mSphere = multiSpheres[hit._boundJoints[i]]; if (mSphere.isValid()) { float boundDistance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; + BoxFace boundFace = BoxFace::UNKNOWN_FACE; + glm::vec3 boundSurfaceNormal; auto &bbox = mSphere.getBoundingBox(); - if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, face, surfaceNormal)) { - MyCharacterController::RayAvatarResult boxHit; - boxHit._distance = boundDistance; - boxHit._intersect = true; - boxHit._intersectionNormal = surfaceNormal; - boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection; - boxHit._intersectWithAvatar = avatarID; - boxHit._intersectWithJoint = mSphere.getJointIndex(); - boxHits.push_back(boxHit); + if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, boundFace, boundSurfaceNormal)) { + if (boundDistance < boxHit._distance) { + boxHit._intersect = true; + boxHit._intersectWithAvatar = avatarID; + boxHit._intersectWithJoint = mSphere.getJointIndex(); + boxHit._distance = boundDistance; + boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection; + boxHit._intersectionNormal = boundSurfaceNormal; + face = boundFace; + } } } } - if (boxHits.size() > 0) { - if (boxHits.size() > 1) { - std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA, - const MyCharacterController::RayAvatarResult& hitB) { - return hitA._distance < hitB._distance; - }); - } - rayAvatarResult = boxHits[0]; + if (boxHit._distance < FLT_MAX) { + rayAvatarResult = boxHit; } } } - if (pickAgainstMesh) { + + if (rayAvatarResult._intersect && pickAgainstMesh) { glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint); - glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayDirection, rayAvatarResult._intersectWithJoint); + glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayAvatarResult._distance * rayDirection, rayAvatarResult._intersectWithJoint); auto avatarOrientation = avatar->getWorldOrientation(); auto avatarPosition = avatar->getWorldPosition(); @@ -732,31 +728,37 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic auto defaultFrameRayOrigin = jointPosition + jointOrientation * localRayOrigin; auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint; - auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin; + auto defaultFrameRayDirection = glm::normalize(defaultFrameRayPoint - defaultFrameRayOrigin); - if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, distance, face, surfaceNormal, extraInfo, true, false)) { - auto newDistance = glm::length(vec3FromVariant(extraInfo["worldIntersectionPoint"]) - defaultFrameRayOrigin); - rayAvatarResult._distance = newDistance; - rayAvatarResult._intersectionPoint = ray.origin + newDistance * rayDirection; - rayAvatarResult._intersectionNormal = surfaceNormal; - extraInfo["worldIntersectionPoint"] = vec3toVariant(rayAvatarResult._intersectionPoint); - break; + float subMeshDistance = FLT_MAX; + BoxFace subMeshFace = BoxFace::UNKNOWN_FACE; + glm::vec3 subMeshSurfaceNormal; + QVariantMap subMeshExtraInfo; + if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, subMeshDistance, subMeshFace, subMeshSurfaceNormal, subMeshExtraInfo, true, false)) { + rayAvatarResult._distance = subMeshDistance; + rayAvatarResult._intersectionPoint = ray.origin + subMeshDistance * rayDirection; + rayAvatarResult._intersectionNormal = subMeshSurfaceNormal; + face = subMeshFace; + extraInfo = subMeshExtraInfo; + } else { + rayAvatarResult._intersect = false; } - } else if (rayAvatarResult._intersect){ + } + + if (rayAvatarResult._intersect) { + result.intersects = true; + result.avatarID = rayAvatarResult._intersectWithAvatar; + result.distance = rayAvatarResult._distance; + result.face = face; + result.intersection = ray.origin + rayAvatarResult._distance * rayDirection; + result.surfaceNormal = rayAvatarResult._intersectionNormal; + result.jointIndex = rayAvatarResult._intersectWithJoint; + result.extraInfo = extraInfo; break; } } - if (rayAvatarResult._intersect) { - result.intersects = true; - result.avatarID = rayAvatarResult._intersectWithAvatar; - result.distance = rayAvatarResult._distance; - result.surfaceNormal = rayAvatarResult._intersectionNormal; - result.jointIndex = rayAvatarResult._intersectWithJoint; - result.intersection = ray.origin + rayAvatarResult._distance * rayDirection; - result.extraInfo = extraInfo; - result.face = face; - } } + return result; } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 50d9e80e8b..51352ec861 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -46,6 +46,7 @@ using SortedAvatar = std::pair>; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @borrows AvatarList.getAvatarIdentifiers as getAvatarIdentifiers * @borrows AvatarList.getAvatarsInRange as getAvatarsInRange diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..b5a938faba 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -75,7 +75,6 @@ const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed const float MIN_AVATAR_SPEED = 0.05f; -const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; @@ -847,6 +846,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { updateOrientation(deltaTime); updatePosition(deltaTime); + updateViewBoom(); } // update sensorToWorldMatrix for camera and hand controllers @@ -915,14 +915,9 @@ void MyAvatar::simulate(float deltaTime, bool inView) { auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { - bool zoneAllowsFlying = true; - bool collisionlessAllowed = true; + std::pair zoneInteractionProperties; entityTree->withWriteLock([&] { - std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); - if (zone) { - zoneAllowsFlying = zone->getFlyingAllowed(); - collisionlessAllowed = zone->getGhostingAllowed(); - } + zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); forEachDescendant([&](SpatiallyNestablePointer object) { // we need to update attached queryAACubes in our own local tree so point-select always works @@ -935,6 +930,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) { }); }); bool isPhysicsEnabled = qApp->isPhysicsEnabled(); + bool zoneAllowsFlying = zoneInteractionProperties.first; + bool collisionlessAllowed = zoneInteractionProperties.second; _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled); _characterController.setCollisionlessAllowed(collisionlessAllowed); } @@ -2983,7 +2980,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); // the rig is in the skeletonModel frame - AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); + AnimPose xform(1.0f, _skeletonModel->getRotation(), _skeletonModel->getTranslation()); if (_enableDebugDrawDefaultPose && animSkeleton) { glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f); @@ -3028,7 +3025,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose); if (_enableDebugDrawDetailedCollision) { - AnimPose rigToWorldPose(glm::vec3(1.0f), getWorldOrientation() * Quaternions::Y_180, getWorldPosition()); + AnimPose rigToWorldPose(1.0f, getWorldOrientation() * Quaternions::Y_180, getWorldPosition()); const int NUM_DEBUG_COLORS = 8; const glm::vec4 DEBUG_COLORS[NUM_DEBUG_COLORS] = { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), @@ -3323,21 +3320,22 @@ void MyAvatar::updateActionMotor(float deltaTime) { direction = Vectors::ZERO; } + float sensorToWorldScale = getSensorToWorldScale(); if (state == CharacterController::State::Hover) { // we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed float motorSpeed = glm::length(_actionMotorVelocity); - float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar; + float finalMaxMotorSpeed = sensorToWorldScale * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar; float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; - const float maxBoostSpeed = getSensorToWorldScale() * MAX_BOOST_SPEED; + const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { if (motorSpeed < maxBoostSpeed) { // an active action motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; - motorSpeed += getSensorToWorldScale() * MIN_AVATAR_SPEED * boostCoefficient; + motorSpeed += sensorToWorldScale * MIN_AVATAR_SPEED * boostCoefficient; } else if (motorSpeed > finalMaxMotorSpeed) { motorSpeed = finalMaxMotorSpeed; } @@ -3348,45 +3346,21 @@ void MyAvatar::updateActionMotor(float deltaTime) { const glm::vec2 currentVel = { direction.x, direction.z }; float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get()); // _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0 - _actionMotorVelocity = getSensorToWorldScale() * (scaledSpeed * _walkSpeedScalar) * direction; - } - - float previousBoomLength = _boomLength; - float boomChange = getDriveKey(ZOOM); - _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; - _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); - - // May need to change view if boom length has changed - if (previousBoomLength != _boomLength) { - qApp->changeViewAsNeeded(_boomLength); + _actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction; } } void MyAvatar::updatePosition(float deltaTime) { - if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { - updateActionMotor(deltaTime); - } - - vec3 velocity = getWorldVelocity(); - float sensorToWorldScale = getSensorToWorldScale(); - float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale; - const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s - if (!_characterController.isEnabledAndReady()) { - // _characterController is not in physics simulation but it can still compute its target velocity - updateMotors(); - _characterController.computeNewVelocity(deltaTime, velocity); - - float speed2 = glm::length(velocity); - if (speed2 > sensorToWorldScale2 * MIN_AVATAR_SPEED_SQUARED) { - // update position ourselves - applyPositionDelta(deltaTime * velocity); + if (_characterController.isEnabledAndReady()) { + if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + updateActionMotor(deltaTime); } - measureMotionDerivatives(deltaTime); - _moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED; - } else { + float sensorToWorldScale = getSensorToWorldScale(); + float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale; + vec3 velocity = getWorldVelocity(); float speed2 = glm::length2(velocity); + const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s _moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED; - if (_moving) { // scan for walkability glm::vec3 position = getWorldPosition(); @@ -3398,6 +3372,18 @@ void MyAvatar::updatePosition(float deltaTime) { } } +void MyAvatar::updateViewBoom() { + float previousBoomLength = _boomLength; + float boomChange = getDriveKey(ZOOM); + _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; + _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); + + // May need to change view if boom length has changed + if (previousBoomLength != _boomLength) { + qApp->changeViewAsNeeded(_boomLength); + } +} + void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { // COLLISION SOUND API in Audio has been removed } @@ -4835,7 +4821,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat swingTwistDecomposition(hipsinWorldSpace, avatarUpWorld, resultingSwingInWorld, resultingTwistInWorld); // remove scale present from sensorToWorldMatrix - followWorldPose.scale() = glm::vec3(1.0f); + followWorldPose.scale() = 1.0f; if (isActive(Rotation)) { //use the hmd reading for the hips follow @@ -5300,6 +5286,21 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { bool tellHandler { false }; _avatarGrabsLock.withWriteLock([&] { + + std::map::iterator itr; + itr = _avatarGrabs.find(grabID); + if (itr != _avatarGrabs.end()) { + GrabPointer grab = itr->second; + if (grab) { + grab->setReleased(true); + bool success; + SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); + if (target && success) { + target->disableGrab(grab); + } + } + } + if (_avatarGrabData.remove(grabID)) { _grabsToDelete.push_back(grabID); tellHandler = true; @@ -5312,3 +5313,15 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { } } +void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + // force an update packet + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityID, properties); + }); + } +} + diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0d27988543..984d7b297b 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -66,6 +66,7 @@ class MyAvatar : public Avatar { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {Vec3} qmlPosition - A synonym for position for use by QML. * @property {boolean} shouldRenderLocally=true - If true then your avatar is rendered for you in Interface, @@ -137,7 +138,7 @@ class MyAvatar : public Avatar { * your avatar when rolling your HMD in degrees per second. * @property {number} userHeight=1.75 - The height of the user in sensor space. * @property {number} userEyeHeight=1.65 - The estimated height of the user's eyes in sensor space. Read-only. - * @property {Uuid} SELF_ID - UUID representing "my avatar". Only use for local-only entities and overlays in situations + * @property {Uuid} SELF_ID - UUID representing "my avatar". Only use for local-only entities in situations * where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). Note: Likely to be deprecated. * Read-only. * @property {number} walkSpeed @@ -1732,6 +1733,7 @@ private: void updateOrientation(float deltaTime); void updateActionMotor(float deltaTime); void updatePosition(float deltaTime); + void updateViewBoom(); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); void initAnimGraph(); @@ -1882,6 +1884,7 @@ private: bool didTeleport(); bool getIsAway() const { return _isAway; } void setAway(bool value); + void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const override; std::mutex _pinnedJointsMutex; std::vector _pinnedJoints; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 51a2c3767b..9b0e2fb7f8 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -55,7 +55,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { if (myAvatar->isJointPinned(hipsIndex)) { Transform avatarTransform = myAvatar->getTransform(); AnimPose result = AnimPose(worldToSensorMat * avatarTransform.getMatrix() * Matrices::Y_180); - result.scale() = glm::vec3(1.0f, 1.0f, 1.0f); + result.scale() = 1.0f; return result; } @@ -122,7 +122,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::ControllerParameters params; - AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); + AnimPose avatarToRigPose(1.0f, Quaternions::Y_180, glm::vec3(0.0f)); glm::mat4 rigToAvatarMatrix = Matrices::Y_180; glm::mat4 avatarToWorldMatrix = createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()); @@ -141,7 +141,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // preMult 180 is necessary to convert from avatar to rig coordinates. // postMult 180 is necessary to convert head from -z forward to z forward. glm::quat headRot = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; - params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = AnimPose(glm::vec3(1.0f), headRot, glm::vec3(0.0f)); + params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = AnimPose(1.0f, headRot, glm::vec3(0.0f)); params.primaryControllerFlags[Rig::PrimaryControllerType_Head] = 0; } diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index a3950c8e96..40c7c01b30 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -55,44 +55,47 @@ OtherAvatar::~OtherAvatar() { void OtherAvatar::removeOrb() { if (!_otherAvatarOrbMeshPlaceholderID.isNull()) { - qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); - _otherAvatarOrbMeshPlaceholderID = UNKNOWN_OVERLAY_ID; + DependencyManager::get()->deleteEntity(_otherAvatarOrbMeshPlaceholderID); + _otherAvatarOrbMeshPlaceholderID = UNKNOWN_ENTITY_ID; } } void OtherAvatar::updateOrbPosition() { - if (_otherAvatarOrbMeshPlaceholder != nullptr) { - _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); - if (_otherAvatarOrbMeshPlaceholderID.isNull()) { - _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); - } + if (_otherAvatarOrbMeshPlaceholderID.isNull()) { + EntityItemProperties properties; + properties.setPosition(getHead()->getPosition()); + DependencyManager::get()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties); } } void OtherAvatar::createOrb() { if (_otherAvatarOrbMeshPlaceholderID.isNull()) { - _otherAvatarOrbMeshPlaceholder = std::make_shared(); - _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); - _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); - _otherAvatarOrbMeshPlaceholder->setIsSolid(false); - _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); - _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); - _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); - _otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true); - _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); - _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); - // Position focus - _otherAvatarOrbMeshPlaceholder->setWorldOrientation(glm::quat(0.0f, 0.0f, 0.0f, 1.0)); - _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); - _otherAvatarOrbMeshPlaceholder->setDimensions(glm::vec3(0.5f, 0.5f, 0.5f)); - _otherAvatarOrbMeshPlaceholder->setVisible(true); + EntityItemProperties properties; + properties.setType(EntityTypes::Sphere); + properties.setAlpha(1.0f); + properties.setColor(getLoadingOrbColor(_loadingStatus)); + properties.setPrimitiveMode(PrimitiveMode::LINES); + properties.getPulse().setMin(0.5f); + properties.getPulse().setMax(1.0f); + properties.getPulse().setColorMode(PulseMode::IN_PHASE); + properties.setIgnorePickIntersection(true); + + properties.setPosition(getHead()->getPosition()); + properties.setRotation(glm::quat(0.0f, 0.0f, 0.0f, 1.0)); + properties.setDimensions(glm::vec3(0.5f, 0.5f, 0.5f)); + properties.setVisible(true); + + _otherAvatarOrbMeshPlaceholderID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); } } void OtherAvatar::indicateLoadingStatus(LoadingStatus loadingStatus) { Avatar::indicateLoadingStatus(loadingStatus); - if (_otherAvatarOrbMeshPlaceholder) { - _otherAvatarOrbMeshPlaceholder->setColor(getLoadingOrbColor(_loadingStatus)); + + if (_otherAvatarOrbMeshPlaceholderID != UNKNOWN_ENTITY_ID) { + EntityItemProperties properties; + properties.setColor(getLoadingOrbColor(_loadingStatus)); + DependencyManager::get()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties); } } @@ -447,6 +450,10 @@ void OtherAvatar::handleChangedAvatarEntityData() { EntityItemProperties properties; int32_t bytesLeftToRead = data.size(); unsigned char* dataAt = (unsigned char*)(data.data()); + // FIXME: This function will cause unintented changes in SpaillyNestable + // E.g overriding the ID index of an exisiting entity to temporary entity + // in the following map QHash _children; + // Andrew Meadows will address this issue if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) { // properties are corrupt continue; @@ -489,6 +496,17 @@ void OtherAvatar::handleChangedAvatarEntityData() { bool success = true; if (entity) { QUuid oldParentID = entity->getParentID(); + + // Since has overwrtiiten the back pointer + // from the parent children map (see comment for function call above), + // we need to for reset the back pointer in the map correctly by setting the parentID, but + // since the parentID of the entity has not changed we first need to set it some ither ID, + // then set the the original ID for the changes to take effect + // TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes + // side effects...remove the following three lines + const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); + entity->setParentID(NULL_ID); + entity->setParentID(oldParentID); if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { @@ -510,6 +528,11 @@ void OtherAvatar::handleChangedAvatarEntityData() { } } stateItr.value().success = success; + if (success) { + stateItr.value().hash = newHash; + } else { + stateItr.value().hash = 0; + } } AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs(); diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 3ecd35413f..696e122b30 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -16,8 +16,6 @@ #include #include "InterfaceLogging.h" -#include "ui/overlays/Overlays.h" -#include "ui/overlays/Sphere3DOverlay.h" class AvatarManager; class AvatarMotionState; @@ -76,9 +74,18 @@ protected: void onAddAttachedAvatarEntity(const QUuid& id); void onRemoveAttachedAvatarEntity(const QUuid& id); + class AvatarEntityDataHash { + public: + AvatarEntityDataHash(uint32_t h) : hash(h) {}; + uint32_t hash { 0 }; + bool success { false }; + }; + + using MapOfAvatarEntityDataHashes = QMap; + MapOfAvatarEntityDataHashes _avatarEntityDataHashes; + std::vector _attachedAvatarEntities; - std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; - OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; + QUuid _otherAvatarOrbMeshPlaceholderID; AvatarMotionState* _motionState { nullptr }; std::vector _detailedMotionStates; int32_t _spaceIndex { -1 }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 5236c5a7fb..046e697b6d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -54,11 +54,10 @@ void QmlCommerce::openSystemApp(const QString& appName) { {"GOTO", "hifi/tablet/TabletAddressDialog.qml"}, {"PEOPLE", "hifi/Pal.qml"}, {"WALLET", "hifi/commerce/wallet/Wallet.qml"}, - {"MARKET", "/marketplace.html"} + {"MARKET", "hifi/commerce/marketplace/Marketplace.qml"} }; static const QMap systemInject{ - {"MARKET", "/scripts/system/html/js/marketplacesInject.js"} }; diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp new file mode 100644 index 0000000000..23ba418a2d --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -0,0 +1,131 @@ +// +// QmlMarketplace.cpp +// interface/src/commerce +// +// Guard for safe use of Marketplace by authorized QML. +// +// Created by Roxanne Skelly on 1/18/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 +// + + +#include "QmlMarketplace.h" +#include "CommerceLogging.h" +#include "Application.h" +#include "DependencyManager.h" +#include +#include +#include +#include +#include "scripting/HMDScriptingInterface.h" + +#define ApiHandler(NAME) void QmlMarketplace::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); } +#define FailHandler(NAME) void QmlMarketplace::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); } +#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME) +Handler(getMarketplaceItems) +Handler(getMarketplaceItem) +Handler(marketplaceItemLike) +Handler(getMarketplaceCategories) + +QmlMarketplace::QmlMarketplace() { +} + +void QmlMarketplace::openMarketplace(const QString& marketplaceItemId) { + auto tablet = dynamic_cast( + DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); + tablet->loadQMLSource("hifi/commerce/marketplace/Marketplace.qml"); + DependencyManager::get()->openTablet(); + if (!marketplaceItemId.isEmpty()) { + tablet->sendToQml(QVariantMap({ { "method", "marketplace_openItem" }, { "itemId", marketplaceItemId } })); + } +} + +void QmlMarketplace::getMarketplaceItems( + const QString& q, + const QString& view, + const QString& category, + const QString& adminFilter, + const QString& adminFilterCost, + const QString& sort, + const bool isFree, + const int& page, + const int& perPage) { + + QString endpoint = "items"; + QUrlQuery request; + request.addQueryItem("q", q); + request.addQueryItem("view", view); + request.addQueryItem("category", category); + request.addQueryItem("adminFilter", adminFilter); + request.addQueryItem("adminFilterCost", adminFilterCost); + request.addQueryItem("sort", sort); + if (isFree) { + request.addQueryItem("isFree", "true"); + } + request.addQueryItem("page", QString::number(page)); + request.addQueryItem("perPage", QString::number(perPage)); + send(endpoint, "getMarketplaceItemsSuccess", "getMarketplaceItemsFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); +} + +void QmlMarketplace::getMarketplaceItem(const QString& marketplaceItemId) { + QString endpoint = QString("items/") + marketplaceItemId; + send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional); +} + +void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) { + QString endpoint = QString("items/") + marketplaceItemId + "/like"; + send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required); +} + +void QmlMarketplace::getMarketplaceCategories() { + QString endpoint = "categories"; + send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None); +} + + +void QmlMarketplace::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request) { + auto accountManager = DependencyManager::get(); + const QString URL = "/api/v1/marketplace/"; + JSONCallbackParameters callbackParams(this, success, fail); + + accountManager->sendRequest(URL + endpoint + "?" + request.toString(), + authType, + method, + callbackParams, + QByteArray(), + NULL, + QVariantMap()); + +} + +QJsonObject QmlMarketplace::apiResponse(const QString& label, QNetworkReply* reply) { + QByteArray response = reply->readAll(); + QJsonObject data = QJsonDocument::fromJson(response).object(); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. + qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); +#endif + return data; +} + +// Non-200 responses are not json: +QJsonObject QmlMarketplace::failResponse(const QString& label, QNetworkReply* reply) { + QString response = reply->readAll(); + qWarning(commerce) << "FAILED" << label << response; + + // tempResult will be NULL if the response isn't valid JSON. + QJsonDocument tempResult = QJsonDocument::fromJson(response.toLocal8Bit()); + if (tempResult.isNull()) { + QJsonObject result + { + { "status", "fail" }, + { "message", response } + }; + return result; + } + else { + return tempResult.object(); + } +} \ No newline at end of file diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h new file mode 100644 index 0000000000..5794d4f53c --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.h @@ -0,0 +1,73 @@ +// +// QmlMarketplace.h +// interface/src/commerce +// +// Guard for safe use of Marketplace by authorized QML. +// +// Created by Roxanne Skelly on 1/18/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 +// + +#pragma once +#ifndef hifi_QmlMarketplace_h +#define hifi_QmlMarketplace_h + +#include + +#include +#include +#include "AccountManager.h" + +class QmlMarketplace : public QObject { + Q_OBJECT + +public: + QmlMarketplace(); + +public slots: + void getMarketplaceItemsSuccess(QNetworkReply* reply); + void getMarketplaceItemsFailure(QNetworkReply* reply); + void getMarketplaceItemSuccess(QNetworkReply* reply); + void getMarketplaceItemFailure(QNetworkReply* reply); + void getMarketplaceCategoriesSuccess(QNetworkReply* reply); + void getMarketplaceCategoriesFailure(QNetworkReply* reply); + void marketplaceItemLikeSuccess(QNetworkReply* reply); + void marketplaceItemLikeFailure(QNetworkReply* reply); + +protected: + Q_INVOKABLE void openMarketplace(const QString& marketplaceItemId = QString()); + Q_INVOKABLE void getMarketplaceItems( + const QString& q = QString(), + const QString& view = QString(), + const QString& category = QString(), + const QString& adminFilter = QString("published"), + const QString& adminFilterCost = QString(), + const QString& sort = QString(), + const bool isFree = false, + const int& page = 1, + const int& perPage = 20); + Q_INVOKABLE void getMarketplaceItem(const QString& marketplaceItemId); + Q_INVOKABLE void marketplaceItemLike(const QString& marketplaceItemId, const bool like = true); + Q_INVOKABLE void getMarketplaceCategories(); + +signals: + void getMarketplaceItemsResult(QJsonObject result); + void getMarketplaceItemResult(QJsonObject result); + void getMarketplaceCategoriesResult(QJsonObject result); + void marketplaceItemLikeResult(QJsonObject result); + +private: + void send(const QString& endpoint, + const QString& success, + const QString& fail, + QNetworkAccessManager::Operation method, + AccountManagerAuth::Type authType, + const QUrlQuery& request = QUrlQuery()); + QJsonObject apiResponse(const QString& label, QNetworkReply* reply); + QJsonObject failResponse(const QString& label, QNetworkReply* reply); +}; + +#endif // hifi_QmlMarketplace_h diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index ed52083d77..b9dc8326e8 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -194,7 +194,6 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui _calibrationCount(0), _calibrationValues(), _calibrationBillboard(NULL), - _calibrationBillboardID(UNKNOWN_OVERLAY_ID), _calibrationMessage(QString()), _isCalibrated(false) { @@ -616,10 +615,6 @@ void DdeFaceTracker::setEyeClosingThreshold(float eyeClosingThreshold) { static const int CALIBRATION_BILLBOARD_WIDTH = 300; static const int CALIBRATION_BILLBOARD_HEIGHT = 120; -static const int CALIBRATION_BILLBOARD_TOP_MARGIN = 30; -static const int CALIBRATION_BILLBOARD_LEFT_MARGIN = 30; -static const int CALIBRATION_BILLBOARD_FONT_SIZE = 16; -static const float CALIBRATION_BILLBOARD_ALPHA = 0.5f; static QString CALIBRATION_INSTRUCTION_MESSAGE = "Hold still to calibrate camera"; void DdeFaceTracker::calibrate() { @@ -634,12 +629,8 @@ void DdeFaceTracker::calibrate() { _calibrationCount = 0; _calibrationMessage = CALIBRATION_INSTRUCTION_MESSAGE + "\n\n"; + // FIXME: this overlay probably doesn't work anymore _calibrationBillboard = new TextOverlay(); - _calibrationBillboard->setTopMargin(CALIBRATION_BILLBOARD_TOP_MARGIN); - _calibrationBillboard->setLeftMargin(CALIBRATION_BILLBOARD_LEFT_MARGIN); - _calibrationBillboard->setFontSize(CALIBRATION_BILLBOARD_FONT_SIZE); - _calibrationBillboard->setText(CALIBRATION_INSTRUCTION_MESSAGE); - _calibrationBillboard->setAlpha(CALIBRATION_BILLBOARD_ALPHA); glm::vec2 viewport = qApp->getCanvasSize(); _calibrationBillboard->setX((viewport.x - CALIBRATION_BILLBOARD_WIDTH) / 2); _calibrationBillboard->setY((viewport.y - CALIBRATION_BILLBOARD_HEIGHT) / 2); @@ -659,10 +650,10 @@ void DdeFaceTracker::addCalibrationDatum() { int samplesLeft = CALIBRATION_SAMPLES - _calibrationCount; if (samplesLeft % LARGE_TICK_INTERVAL == 0) { _calibrationMessage += QString::number(samplesLeft / LARGE_TICK_INTERVAL); - _calibrationBillboard->setText(_calibrationMessage); + // FIXME: set overlay text } else if (samplesLeft % SMALL_TICK_INTERVAL == 0) { _calibrationMessage += "."; - _calibrationBillboard->setText(_calibrationMessage); + // FIXME: set overlay text } for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 4fe36b582e..0ad8d85c62 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -32,6 +32,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class DdeFaceTracker : public FaceTracker, public Dependency { @@ -168,7 +169,7 @@ private: int _calibrationCount; QVector _calibrationValues; TextOverlay* _calibrationBillboard; - OverlayID _calibrationBillboardID; + QUuid _calibrationBillboardID; QString _calibrationMessage; bool _isCalibrated; void addCalibrationDatum(); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 5c8868abdb..6b07e6717c 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -38,11 +38,13 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "OctreePacketProcessor::processPacket()"); +#ifndef Q_OS_ANDROID const int WAY_BEHIND = 300; if (packetsToProcessCount() > WAY_BEHIND && qApp->getLogger()->extraDebugging()) { qDebug("OctreePacketProcessor::processPacket() packets to process=%d", packetsToProcessCount()); } +#endif bool wasStatsPacket = false; diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index d30f98051e..82d75257df 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -409,10 +409,6 @@ PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pi return std::make_shared(pick, entityIntersections, std::vector()); } -PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { - return std::make_shared(pick, std::vector(), std::vector()); -} - PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { if (!pick.loaded) { // Cannot compute result diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 92aa415f9e..c742c089b4 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -54,7 +54,6 @@ public: return std::make_shared(pickVariant, std::vector(), std::vector()); } PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; - PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; PickResultPointer getHUDIntersection(const CollisionRegion& pick) override; Transform getResultTransform() const override; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 236512f2fe..aeed65fbad 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -17,6 +17,8 @@ #include "PickManager.h" #include "RayPick.h" +#include "PolyLineEntityItem.h" + LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithParent, bool enabled) : @@ -28,7 +30,7 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { auto renderState = std::static_pointer_cast(_renderStates[state]); if (renderState) { - updateRenderStateOverlay(renderState->getPathID(), pathProps); + updateRenderState(renderState->getPathID(), pathProps); QVariant lineWidth = pathProps.toMap()["lineWidth"]; if (lineWidth.isValid()) { renderState->setLineWidth(lineWidth.toFloat()); @@ -121,48 +123,62 @@ void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, Int } } -LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : +LaserPointer::RenderState::RenderState(const QUuid& startID, const QUuid& pathID, const QUuid& endID) : StartEndRenderState(startID, endID), _pathID(pathID) { - if (!_pathID.isNull()) { - _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignorePickIntersection").value.toBool(); - _lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat(); + if (!getPathID().isNull()) { + auto entityScriptingInterface = DependencyManager::get(); + { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_IGNORE_PICK_INTERSECTION; + _pathIgnorePicks = entityScriptingInterface->getEntityProperties(getPathID(), desiredProperties).getIgnorePickIntersection(); + } + { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_STROKE_WIDTHS; + auto widths = entityScriptingInterface->getEntityProperties(getPathID(), desiredProperties).getStrokeWidths(); + _lineWidth = widths.length() == 0 ? PolyLineEntityItem::DEFAULT_LINE_WIDTH : widths[0]; + } } } void LaserPointer::RenderState::cleanup() { StartEndRenderState::cleanup(); - if (!_pathID.isNull()) { - qApp->getOverlays().deleteOverlay(_pathID); + if (!getPathID().isNull()) { + DependencyManager::get()->deleteEntity(getPathID()); } } void LaserPointer::RenderState::disable() { StartEndRenderState::disable(); if (!getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("visible", false); - pathProps.insert("ignorePickIntersection", true); - qApp->getOverlays().editOverlay(getPathID(), pathProps); + EntityItemProperties properties; + properties.setVisible(false); + properties.setIgnorePickIntersection(true); + DependencyManager::get()->editEntity(getPathID(), properties); } } void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { StartEndRenderState::update(origin, end, surfaceNormal, parentScale, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); - QVariant endVariant = vec3toVariant(end); if (!getPathID().isNull()) { - QVariantMap pathProps; - pathProps.insert("start", vec3toVariant(origin)); - pathProps.insert("end", endVariant); - pathProps.insert("visible", true); - pathProps.insert("ignorePickIntersection", doesPathIgnoreRays()); - pathProps.insert("lineWidth", getLineWidth() * parentScale); - qApp->getOverlays().editOverlay(getPathID(), pathProps); + EntityItemProperties properties; + QVector points; + points.append(glm::vec3(0.0f)); + points.append(end - origin); + properties.setPosition(origin); + properties.setLinePoints(points); + properties.setVisible(true); + properties.setIgnorePickIntersection(doesPathIgnorePicks()); + QVector widths; + widths.append(getLineWidth() * parentScale); + DependencyManager::get()->editEntity(getPathID(), properties); } } std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap) { + // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers QUuid startID; if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); @@ -232,9 +248,8 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { case ENTITY: + case LOCAL_ENTITY: return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); default: return glm::vec3(NAN); } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index bcd4bb607d..13d108baee 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -19,10 +19,10 @@ public: class RenderState : public StartEndRenderState { public: RenderState() {} - RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + RenderState(const QUuid& startID, const QUuid& pathID, const QUuid& endID); - const OverlayID& getPathID() const { return _pathID; } - const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } + const QUuid& getPathID() const { return _pathID; } + const bool& doesPathIgnorePicks() const { return _pathIgnorePicks; } void setLineWidth(float width) { _lineWidth = width; } float getLineWidth() const { return _lineWidth; } @@ -33,9 +33,9 @@ public: bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; private: - OverlayID _pathID; - bool _pathIgnoreRays; + QUuid _pathID; + bool _pathIgnorePicks; float _lineWidth; }; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index d85e329e9a..425ffd7de4 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -27,6 +27,7 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ public: @@ -111,10 +112,10 @@ public: * @function LaserPointers.setLockEndUUID * @param {number} id * @param {Uuid} itemID - * @param {boolean} isOverlay + * @param {boolean} isAvatar * @param {Mat4} [offsetMat] */ - Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } + Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } /**jsdoc diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index b93ced17c6..7a0eed96a8 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -10,7 +10,6 @@ #include "Application.h" #include "EntityScriptingInterface.h" #include "PickScriptingInterface.h" -#include "ui/overlays/Overlays.h" #include "avatar/AvatarManager.h" #include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" @@ -68,20 +67,15 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) DependencyManager::get()->evalParabolaIntersectionVector(pick, searchFilter, getIncludeItemsAs(), getIgnoreItemsAs()); if (entityRes.intersects) { - return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); - } - } - return std::make_shared(pick.toVariantMap()); -} - -PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { - if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { - bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get()->getForceCoarsePicking()); - ParabolaToOverlayIntersectionResult overlayRes = - qApp->getOverlays().findParabolaIntersectionVector(pick, precisionPicking, - getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); - if (overlayRes.intersects) { - return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); + IntersectionType type = IntersectionType::ENTITY; + if (getFilter().doesPickLocalEntities()) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_ENTITY_HOST_TYPE; + if (DependencyManager::get()->getEntityProperties(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { + type = IntersectionType::LOCAL_ENTITY; + } + } + return std::make_shared(type, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); } } return std::make_shared(pick.toVariantMap()); diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h index 8cbbc79bf5..0adbb01954 100644 --- a/interface/src/raypick/ParabolaPick.h +++ b/interface/src/raypick/ParabolaPick.h @@ -12,7 +12,6 @@ #include class EntityItemID; -class OverlayID; class ParabolaPickResult : public PickResult { public: @@ -80,7 +79,6 @@ public: PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const PickParabola& pick) override; - PickResultPointer getOverlayIntersection(const PickParabola& pick) override; PickResultPointer getAvatarIntersection(const PickParabola& pick) override; PickResultPointer getHUDIntersection(const PickParabola& pick) override; Transform getResultTransform() const override; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index e45b11b479..389f6ed286 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -149,7 +149,7 @@ void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, } } -ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, +ParabolaPointer::RenderState::RenderState(const QUuid& startID, const QUuid& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, bool isVisibleInSecondaryCamera, bool drawInFront, bool pathEnabled) : StartEndRenderState(startID, endID) { @@ -230,6 +230,7 @@ void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::ve } std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap) { + // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers QUuid startID; if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); @@ -321,9 +322,8 @@ glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, co // TODO: implement switch (pickedObject.type) { case ENTITY: + case LOCAL_ENTITY: //return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration); - case OVERLAY: - //return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration); default: return glm::vec3(NAN); } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 3e9a22db4f..d4b705a7d2 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -10,6 +10,8 @@ #include "PathPointer.h" +#include + class ParabolaPointer : public PathPointer { using Parent = PathPointer; public: @@ -79,7 +81,7 @@ public: }; RenderState() {} - RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float parentScale, + RenderState(const QUuid& startID, const QUuid& endID, const glm::vec3& pathColor, float pathAlpha, float parentScale, bool isVisibleInSecondaryCamera, bool drawInFront, bool pathEnabled); void setPathWidth(float width) { _pathWidth = width; } diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index ae0ce4671b..8a1675cfe1 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -73,10 +73,10 @@ void PathPointer::setLength(float length) { }); } -void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { +void PathPointer::setLockEndUUID(const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat) { withWriteLock([&] { _lockEndObject.id = objectID; - _lockEndObject.isOverlay = isOverlay; + _lockEndObject.isAvatar = isAvatar; _lockEndObject.offsetMat = offsetMat; }); } @@ -97,12 +97,8 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick glm::quat rot; glm::vec3 dim; glm::vec3 registrationPoint; - if (_lockEndObject.isOverlay) { - pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); - rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); - dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); - registrationPoint = glm::vec3(0.5f); - } else { + // TODO: use isAvatar + { EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; @@ -117,7 +113,7 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick distance = glm::distance(origin, endVec); glm::vec3 normalizedDirection = glm::normalize(direction); - type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + type = IntersectionType::ENTITY; id = _lockEndObject.id; intersection = endVec; surfaceNormal = -normalizedDirection; @@ -126,8 +122,6 @@ PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pick id = getPickedObjectID(pickResult); if (type == IntersectionType::ENTITY) { endVec = DependencyManager::get()->getEntityTransform(id)[3]; - } else if (type == IntersectionType::OVERLAY) { - endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value); } else if (type == IntersectionType::AVATAR) { endVec = DependencyManager::get()->getAvatar(id)->getPosition(); } @@ -184,8 +178,8 @@ void PathPointer::editRenderState(const std::string& state, const QVariant& star withWriteLock([&] { auto renderState = _renderStates.find(state); if (renderState != _renderStates.end()) { - updateRenderStateOverlay(renderState->second->getStartID(), startProps); - updateRenderStateOverlay(renderState->second->getEndID(), endProps); + updateRenderState(renderState->second->getStartID(), startProps); + updateRenderState(renderState->second->getEndID(), endProps); QVariant startDim = startProps.toMap()["dimensions"]; if (startDim.isValid()) { renderState->second->setStartDim(vec3FromVariant(startDim)); @@ -204,7 +198,8 @@ void PathPointer::editRenderState(const std::string& state, const QVariant& star }); } -void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { +void PathPointer::updateRenderState(const QUuid& id, const QVariant& props) { + // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers if (!id.isNull() && props.isValid()) { QVariantMap propMap = props.toMap(); propMap.remove("visible"); @@ -249,65 +244,79 @@ Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickRes return toReturn; } -StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) : +StartEndRenderState::StartEndRenderState(const QUuid& startID, const QUuid& endID) : _startID(startID), _endID(endID) { + auto entityScriptingInterface = DependencyManager::get(); if (!_startID.isNull()) { - _startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value); - _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignorePickIntersection").value.toBool(); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_DIMENSIONS; + desiredProperties += PROP_IGNORE_PICK_INTERSECTION; + auto properties = entityScriptingInterface->getEntityProperties(_startID, desiredProperties); + _startDim = properties.getDimensions(); + _startIgnorePicks = properties.getIgnorePickIntersection(); } if (!_endID.isNull()) { - _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); - _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); - _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignorePickIntersection").value.toBool(); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_DIMENSIONS; + desiredProperties += PROP_ROTATION; + desiredProperties += PROP_IGNORE_PICK_INTERSECTION; + auto properties = entityScriptingInterface->getEntityProperties(_endID, desiredProperties); + _endDim = properties.getDimensions(); + _endRot = properties.getRotation(); + _endIgnorePicks = properties.getIgnorePickIntersection(); } } void StartEndRenderState::cleanup() { + auto entityScriptingInterface = DependencyManager::get(); if (!_startID.isNull()) { - qApp->getOverlays().deleteOverlay(_startID); + entityScriptingInterface->deleteEntity(_startID); } if (!_endID.isNull()) { - qApp->getOverlays().deleteOverlay(_endID); + entityScriptingInterface->deleteEntity(_endID); } } void StartEndRenderState::disable() { + auto entityScriptingInterface = DependencyManager::get(); if (!getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("visible", false); - startProps.insert("ignorePickIntersection", true); - qApp->getOverlays().editOverlay(getStartID(), startProps); + EntityItemProperties properties; + properties.setVisible(false); + properties.setIgnorePickIntersection(true); + entityScriptingInterface->editEntity(getStartID(), properties); } if (!getEndID().isNull()) { - QVariantMap endProps; - endProps.insert("visible", false); - endProps.insert("ignorePickIntersection", true); - qApp->getOverlays().editOverlay(getEndID(), endProps); + EntityItemProperties properties; + properties.setVisible(false); + properties.setIgnorePickIntersection(true); + entityScriptingInterface->editEntity(getEndID(), properties); } _enabled = false; } void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, float parentScale, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + auto entityScriptingInterface = DependencyManager::get(); if (!getStartID().isNull()) { - QVariantMap startProps; - startProps.insert("position", vec3toVariant(origin)); - startProps.insert("visible", true); - startProps.insert("dimensions", vec3toVariant(getStartDim() * parentScale)); - startProps.insert("ignorePickIntersection", doesStartIgnoreRays()); - qApp->getOverlays().editOverlay(getStartID(), startProps); + EntityItemProperties properties; + properties.setPosition(origin); + properties.setVisible(true); + properties.setDimensions(getStartDim() * parentScale); + properties.setIgnorePickIntersection(doesStartIgnorePicks()); + entityScriptingInterface->editEntity(getStartID(), properties); } if (!getEndID().isNull()) { - QVariantMap endProps; - glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value); + EntityItemProperties properties; + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_DIMENSIONS; + glm::vec3 dim; if (distanceScaleEnd) { dim = getEndDim() * glm::distance(origin, end); - endProps.insert("dimensions", vec3toVariant(dim)); } else { dim = getEndDim() * parentScale; - endProps.insert("dimensions", vec3toVariant(dim)); } + properties.setDimensions(dim); glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal); normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0)); @@ -343,11 +352,11 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, _avgEndRot = rotation; } } - endProps.insert("position", vec3toVariant(position)); - endProps.insert("rotation", quatToVariant(rotation)); - endProps.insert("visible", true); - endProps.insert("ignorePickIntersection", doesEndIgnoreRays()); - qApp->getOverlays().editOverlay(getEndID(), endProps); + properties.setPosition(position); + properties.setRotation(rotation); + properties.setVisible(true); + properties.setIgnorePickIntersection(doesEndIgnorePicks()); + entityScriptingInterface->editEntity(getEndID(), properties); } _enabled = true; } @@ -355,9 +364,8 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { switch (pickedObject.type) { case ENTITY: + case LOCAL_ENTITY: return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); case HUD: return DependencyManager::get()->calculatePos2DFromHUD(origin); default: diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h index 1aa4165c87..4b0625ef0f 100644 --- a/interface/src/raypick/PathPointer.h +++ b/interface/src/raypick/PathPointer.h @@ -12,27 +12,25 @@ #include #include -#include "ui/overlays/Overlay.h" - #include #include struct LockEndObject { QUuid id { QUuid() }; - bool isOverlay { false }; + bool isAvatar { false }; glm::mat4 offsetMat { glm::mat4() }; }; class StartEndRenderState { public: StartEndRenderState() {} - StartEndRenderState(const OverlayID& startID, const OverlayID& endID); + StartEndRenderState(const QUuid& startID, const QUuid& endID); virtual ~StartEndRenderState() = default; - const OverlayID& getStartID() const { return _startID; } - const OverlayID& getEndID() const { return _endID; } - const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } - const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + const QUuid& getStartID() const { return _startID; } + const QUuid& getEndID() const { return _endID; } + const bool& doesStartIgnorePicks() const { return _startIgnorePicks; } + const bool& doesEndIgnorePicks() const { return _endIgnorePicks; } void setStartDim(const glm::vec3& startDim) { _startDim = startDim; } const glm::vec3& getStartDim() const { return _startDim; } @@ -51,10 +49,10 @@ public: bool isEnabled() const { return _enabled; } protected: - OverlayID _startID; - OverlayID _endID; - bool _startIgnoreRays; - bool _endIgnoreRays; + QUuid _startID; + QUuid _endID; + bool _startIgnorePicks; + bool _endIgnorePicks; glm::vec3 _startDim; glm::vec3 _endDim; @@ -78,11 +76,11 @@ public: virtual ~PathPointer(); void setRenderState(const std::string& state) override; - // You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing overlays. + // You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing parts. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override; void setLength(float length) override; - void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; + void setLockEndUUID(const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) override; void updateVisuals(const PickResultPointer& prevRayPickResult) override; @@ -119,7 +117,7 @@ protected: bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + void updateRenderState(const QUuid& id, const QVariant& props); virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0; PickedObject getHoveredObject(const PickResultPointer& pickResult) override; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index e8f84e63fe..ce01db3a56 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -26,7 +26,6 @@ #include "avatar/AvatarManager.h" #include "NestableTransformNode.h" #include "avatars-renderer/AvatarTransformNode.h" -#include "ui/overlays/OverlayTransformNode.h" #include "EntityTransformNode.h" #include @@ -61,7 +60,7 @@ PickFilter getPickFilter(unsigned int filter) { * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. * @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. - * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, an overlay, or a pick. + * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick. * @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral @@ -161,7 +160,7 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. * @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. - * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, an overlay, or a pick. + * @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick. * @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral @@ -264,7 +263,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti * The depth is measured in world space, but will scale with the parent if defined. * @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group * will be considered colliding with the pick. -* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, an overlay, or a pick. +* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick. * @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. * @property {boolean} [scaleWithParent=true] If true, the collision pick's dimensions and threshold will adjust according to the scale of the parent. @@ -415,8 +414,6 @@ void PickScriptingInterface::setParentTransform(std::shared_ptr pick, NestableType nestableType = sharedNestablePointer->getNestableType(); if (nestableType == NestableType::Avatar) { pick->parentTransform = std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); - } else if (nestableType == NestableType::Overlay) { - pick->parentTransform = std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); } else if (nestableType == NestableType::Entity) { pick->parentTransform = std::make_shared(std::static_pointer_cast(sharedNestablePointer), parentJointIndex); } else { diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index e795068cd3..6264a3e258 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -23,6 +23,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} PICK_ENTITIES A filter flag. Include domain and avatar entities when intersecting. Read-only.. Deprecated. * @property {number} PICK_OVERLAYS A filter flag. Include local entities when intersecting. Read-only.. Deprecated. @@ -46,7 +47,8 @@ * * @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags. Read-only. * @property {number} INTERSECTED_ENTITY An intersection type. Intersected an entity. Read-only. - * @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an overlay. Read-only. + * @property {number} INTERSECTED_LOCAL_ENTITY An intersection type. Intersected a local entity. + * @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an entity (3D Overlays no longer exist). Read-only. * @property {number} INTERSECTED_AVATAR An intersection type. Intersected an avatar. Read-only. * @property {number} INTERSECTED_HUD An intersection type. Intersected the HUD sphere. Read-only. * @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results. @@ -76,6 +78,7 @@ class PickScriptingInterface : public QObject, public Dependency { Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ INTERSECTED_LOCAL_ENTITY CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) @@ -211,7 +214,7 @@ public: Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking); /**jsdoc - * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks. + * Sets a list of Entity IDs and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks. * @function Picks.setIgnoreItems * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. * @param {Uuid[]} ignoreItems A list of IDs to ignore. @@ -219,7 +222,7 @@ public: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems); /**jsdoc - * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus + * Sets a list of Entity IDs and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus * Picks only intersect with objects in their include list. * @function Picks.setIncludeItems * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. @@ -348,7 +351,13 @@ public slots: * @function Picks.INTERSECTED_OVERLAY * @returns {number} */ - static constexpr unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; } + static constexpr unsigned int INTERSECTED_LOCAL_ENTITY() { return IntersectionType::LOCAL_ENTITY; } + + /**jsdoc + * @function Picks.INTERSECTED_OVERLAY + * @returns {number} + */ + static constexpr unsigned int INTERSECTED_OVERLAY() { return INTERSECTED_LOCAL_ENTITY(); } /**jsdoc * @function Picks.INTERSECTED_AVATAR diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 0009536479..19c20f0c06 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -99,7 +99,7 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) } } - return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled, modelPositionOffset, + return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylus(propertyMap), hover, enabled, modelPositionOffset, modelRotationOffset, modelDimensions)); } @@ -116,15 +116,15 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * * @typedef {object} Pointers.RayPointerRenderState * @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} - * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Ray Pointer, + * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined object to represent the beginning of the Ray Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. - * @property {Overlays.OverlayProperties|QUuid} [path] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the path of the Ray Pointer, + * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [path] When using {@link Pointers.createPointer}, an optionally defined object to represent the path of the Ray Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". - * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. - * @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Ray Pointer, + * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. + * @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined object to represent the end of the Ray Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. + * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. @@ -271,14 +271,14 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * * @typedef {object} Pointers.ParabolaPointerRenderState * @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} -* @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the beginning of the Parabola Pointer, +* @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined object to represent the beginning of the Parabola Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. +* When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. * @property {Pointers.ParabolaProperties} [path] When using {@link Pointers.createPointer}, the optionally defined rendering properties of the parabolic path defined by the Parabola Pointer. * Not defined in {@link Pointers.getPointerProperties}. -* @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined overlay to represent the end of the Parabola Pointer, +* @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined object to represent the end of the Parabola Pointer, * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* When returned from {@link Pointers.getPointerProperties}, the ID of the created overlay if it exists, or a null ID otherwise. +* When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index a21c1f2470..b04d15d888 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -16,12 +16,13 @@ /**jsdoc * The Pointers API lets you create and manage objects for repeatedly calculating intersections in different ways, as well as the visual representation of those objects. - * Pointers can also be configured to automatically generate {@link PointerEvent}s on {@link Entities} and {@link Overlays}. + * Pointers can also be configured to automatically generate {@link PointerEvent}s on {@link Entities}. * * @namespace Pointers * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class PointerScriptingInterface : public QObject, public Dependency { @@ -39,7 +40,7 @@ public: * @typedef {object} Pointers.Trigger * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, - * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". + * it will try to set the entity focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". */ /**jsdoc @@ -153,7 +154,7 @@ public: Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get()->setLength(uid, length); } /**jsdoc - * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers. + * Sets a list of Entity IDs and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers. * @function Pointers.setIgnoreItems * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. * @param {Uuid[]} ignoreItems A list of IDs to ignore. @@ -161,7 +162,7 @@ public: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; /**jsdoc - * Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus + * Sets a list of Entity IDs and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus * Pointers only intersect with objects in their include list. * @function Pointers.setIncludeItems * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. @@ -171,15 +172,15 @@ public: /**jsdoc - * Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object. + * Lock a Pointer onto a specific object (entity or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object. * Not used by Stylus Pointers. * @function Pointers.setLockEndUUID * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. * @param {Uuid} objectID The ID of the object to which to lock on. - * @param {boolean} isOverlay False for entities or avatars, true for overlays + * @param {boolean} isAvatar False for entities, true for avatars * @param {Mat4} [offsetMat] The offset matrix to use if you do not want to lock on to the center of the object. */ - Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } + Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } /**jsdoc @@ -211,7 +212,7 @@ public: * @function Pointers.getPointerProperties * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. * @returns {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} The information about the Pointer. - * Currently only includes renderStates and defaultRenderStates with associated overlay IDs. + * Currently only includes renderStates and defaultRenderStates with associated entity IDs. */ Q_INVOKABLE QVariantMap getPointerProperties(unsigned int uid) const; }; diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 24ba4435e2..b030a67e17 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -9,7 +9,6 @@ #include "Application.h" #include "EntityScriptingInterface.h" -#include "ui/overlays/Overlays.h" #include "avatar/AvatarManager.h" #include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" @@ -37,26 +36,23 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) { DependencyManager::get()->evalRayIntersectionVector(pick, searchFilter, getIncludeItemsAs(), getIgnoreItemsAs()); if (entityRes.intersects) { - return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); - } else { - return std::make_shared(pick.toVariantMap()); - } -} - -PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { - bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get()->getForceCoarsePicking()); - RayToOverlayIntersectionResult overlayRes = - qApp->getOverlays().findRayIntersectionVector(pick, precisionPicking, - getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); - if (overlayRes.intersects) { - return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); + IntersectionType type = IntersectionType::ENTITY; + if (getFilter().doesPickLocalEntities()) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_ENTITY_HOST_TYPE; + if (DependencyManager::get()->getEntityProperties(entityRes.entityID, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { + type = IntersectionType::LOCAL_ENTITY; + } + } + return std::make_shared(type, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } } PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { - RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), true); + bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get()->getForceCoarsePicking()); + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), precisionPicking); if (avatarRes.intersects) { return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { @@ -88,12 +84,6 @@ glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::v return origin + t * direction; } -glm::vec3 RayPick::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) { - glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); - glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); - return intersectRayWithXYPlane(origin, direction, position, rotation, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT); -} - glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) { auto props = DependencyManager::get()->getEntityProperties(entityID); return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint()); @@ -112,15 +102,12 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3 return pos2D; } -glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) { - glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); - glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); - glm::vec3 dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); - - return projectOntoXYPlane(worldPos, position, rotation, dimensions, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT, unNormalized); -} - glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) { - auto props = DependencyManager::get()->getEntityProperties(entityID); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + desiredProperties += PROP_ROTATION; + desiredProperties += PROP_DIMENSIONS; + desiredProperties += PROP_REGISTRATION_POINT; + auto props = DependencyManager::get()->getEntityProperties(entityID, desiredProperties); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index ba67ceebb1..a781795e55 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -12,7 +12,6 @@ #include class EntityItemID; -class OverlayID; class RayPickResult : public PickResult { public: @@ -78,16 +77,13 @@ public: PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } PickResultPointer getEntityIntersection(const PickRay& pick) override; - PickResultPointer getOverlayIntersection(const PickRay& pick) override; PickResultPointer getAvatarIntersection(const PickRay& pick) override; PickResultPointer getHUDIntersection(const PickRay& pick) override; Transform getResultTransform() const override; // These are helper functions for projecting and intersecting rays static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction); - static glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction); static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true); - static glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized = true); private: static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration); diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index 3ad0efd439..64b21ea055 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -25,6 +25,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} PICK_ENTITIES Read-only. * @property {number} PICK_OVERLAYS Read-only. @@ -53,6 +54,7 @@ class RayPickScriptingInterface : public QObject, public Dependency { Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ INTERSECTED_LOCAL_ENTITY CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) @@ -202,7 +204,13 @@ public slots: * @function RayPick.INTERSECTED_OVERLAY * @returns {number} */ - static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_OVERLAY(); } + static unsigned int INTERSECTED_LOCAL_ENTITY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); } + + /**jsdoc + * @function RayPick.INTERSECTED_OVERLAY + * @returns {number} + */ + static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); } /**jsdoc * @function RayPick.INTERSECTED_AVATAR diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index 0a76180be8..0e95959566 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -11,8 +11,6 @@ #include -#include "ui/overlays/Base3DOverlay.h" - #include "Application.h" #include #include "avatar/AvatarManager.h" @@ -20,6 +18,8 @@ #include #include +#include + using namespace bilateral; float StylusPick::WEB_STYLUS_LENGTH = 0.2f; @@ -148,7 +148,7 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { continue; } - if (!entity->getVisible() && !getFilter().doesPickInvisible()) { + if (!EntityTreeElement::checkFilterSettings(entity, getFilter())) { continue; } @@ -161,47 +161,15 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(target, intersection, false); if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) { - results.push_back(StylusPickResult(IntersectionType::ENTITY, target, distance, intersection, pick, normal)); - } - } - - StylusPickResult nearestTarget(pick.toVariantMap()); - for (const auto& result : results) { - if (result.distance < nearestTarget.distance) { - nearestTarget = result; - } - } - return std::make_shared(nearestTarget); -} - -PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) { - std::vector results; - for (const auto& target : getIncludeItems()) { - if (target.isNull()) { - continue; - } - - auto overlay = qApp->getOverlays().getOverlay(target); - // Don't interact with non-3D or invalid overlays - if (!overlay || !overlay->is3D()) { - continue; - } - - if (!overlay->getVisible() && !getFilter().doesPickInvisible()) { - continue; - } - - auto overlay3D = std::static_pointer_cast(overlay); - const auto overlayRotation = overlay3D->getWorldOrientation(); - const auto overlayPosition = overlay3D->getWorldPosition(); - - glm::vec3 normal = overlayRotation * Vectors::UNIT_Z; - float distance = glm::dot(pick.position - overlayPosition, normal); - glm::vec3 intersection = pick.position - (normal * distance); - - glm::vec2 pos2D = RayPick::projectOntoOverlayXYPlane(target, intersection, false); - if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) { - results.push_back(StylusPickResult(IntersectionType::OVERLAY, target, distance, intersection, pick, normal)); + IntersectionType type = IntersectionType::ENTITY; + if (getFilter().doesPickLocalEntities()) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_ENTITY_HOST_TYPE; + if (DependencyManager::get()->getEntityProperties(target, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) { + type = IntersectionType::LOCAL_ENTITY; + } + } + results.push_back(StylusPickResult(type, target, distance, intersection, pick, normal)); } } diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h index 14821c0ce5..9d5dc10b67 100644 --- a/interface/src/raypick/StylusPick.h +++ b/interface/src/raypick/StylusPick.h @@ -63,7 +63,6 @@ public: StylusTip getMathematicalPick() const override; PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override; PickResultPointer getEntityIntersection(const StylusTip& pick) override; - PickResultPointer getOverlayIntersection(const StylusTip& pick) override; PickResultPointer getAvatarIntersection(const StylusTip& pick) override; PickResultPointer getHUDIntersection(const StylusTip& pick) override; Transform getResultTransform() const override; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 867f896763..3cdcb9c3a5 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -27,10 +27,10 @@ static const float TOUCH_HYSTERESIS = 0.001f; static const QString DEFAULT_STYLUS_MODEL_URL = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx"; -StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled, +StylusPointer::StylusPointer(const QVariant& props, const QUuid& stylus, bool hover, bool enabled, const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions) : Pointer(DependencyManager::get()->createStylusPick(props), enabled, hover), - _stylusOverlay(stylusOverlay), + _stylus(stylus), _modelPositionOffset(modelPositionOffset), _modelDimensions(modelDimensions), _modelRotationOffset(modelRotationOffset) @@ -38,13 +38,14 @@ StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverl } StylusPointer::~StylusPointer() { - if (!_stylusOverlay.isNull()) { - qApp->getOverlays().deleteOverlay(_stylusOverlay); + if (!_stylus.isNull()) { + DependencyManager::get()->deleteEntity(_stylus); } } -OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) { - QVariantMap overlayProperties; +QUuid StylusPointer::buildStylus(const QVariantMap& properties) { + // FIXME: we have to keep using the Overlays interface here, because existing scripts use overlay properties to define pointers + QVariantMap propertiesMap; QString modelUrl = DEFAULT_STYLUS_MODEL_URL; @@ -56,15 +57,15 @@ OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) { } } // TODO: make these configurable per pointer - overlayProperties["name"] = "stylus"; - overlayProperties["url"] = modelUrl; - overlayProperties["loadPriority"] = 10.0f; - overlayProperties["solid"] = true; - overlayProperties["visible"] = false; - overlayProperties["ignorePickIntersection"] = true; - overlayProperties["drawInFront"] = false; + propertiesMap["name"] = "stylus"; + propertiesMap["url"] = modelUrl; + propertiesMap["loadPriority"] = 10.0f; + propertiesMap["solid"] = true; + propertiesMap["visible"] = false; + propertiesMap["ignorePickIntersection"] = true; + propertiesMap["drawInFront"] = false; - return qApp->getOverlays().addOverlay("model", overlayProperties); + return qApp->getOverlays().addOverlay("model", propertiesMap); } void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { @@ -83,25 +84,25 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { } void StylusPointer::show(const StylusTip& tip) { - if (!_stylusOverlay.isNull()) { - QVariantMap props; + if (!_stylus.isNull()) { auto modelOrientation = tip.orientation * _modelRotationOffset; auto sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); auto modelPositionOffset = modelOrientation * (_modelPositionOffset * sensorToWorldScale); - props["position"] = vec3toVariant(tip.position + modelPositionOffset); - props["rotation"] = quatToVariant(modelOrientation); - props["dimensions"] = vec3toVariant(sensorToWorldScale * _modelDimensions); - props["visible"] = true; - qApp->getOverlays().editOverlay(_stylusOverlay, props); + EntityItemProperties properties; + properties.setPosition(tip.position + modelPositionOffset); + properties.setRotation(modelOrientation); + properties.setDimensions(sensorToWorldScale * _modelDimensions); + properties.setVisible(true); + DependencyManager::get()->editEntity(_stylus, properties); } _showing = true; } void StylusPointer::hide() { - if (!_stylusOverlay.isNull()) { - QVariantMap props; - props.insert("visible", false); - qApp->getOverlays().editOverlay(_stylusOverlay, props); + if (!_stylus.isNull()) { + EntityItemProperties properties; + properties.setVisible(false); + DependencyManager::get()->editEntity(_stylus, properties); } _showing = false; } @@ -234,9 +235,8 @@ QVariantMap StylusPointer::toVariantMap() const { glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { case ENTITY: + case LOCAL_ENTITY: return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); default: return glm::vec3(NAN); } @@ -245,9 +245,8 @@ glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, cons glm::vec2 StylusPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { switch (pickedObject.type) { case ENTITY: + case LOCAL_ENTITY: return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); case HUD: return DependencyManager::get()->calculatePos2DFromHUD(origin); default: diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 64e2a38bed..7d43df2379 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -12,8 +12,6 @@ #include #include -#include "ui/overlays/Overlay.h" - #include "StylusPick.h" class StylusPointer : public Pointer { @@ -21,7 +19,7 @@ class StylusPointer : public Pointer { using Ptr = std::shared_ptr; public: - StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled, + StylusPointer(const QVariant& props, const QUuid& stylus, bool hover, bool enabled, const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions); ~StylusPointer(); @@ -36,7 +34,7 @@ public: QVariantMap toVariantMap() const override; - static OverlayID buildStylusOverlay(const QVariantMap& properties); + static QUuid buildStylus(const QVariantMap& properties); protected: PickedObject getHoveredObject(const PickResultPointer& pickResult) override; @@ -74,7 +72,7 @@ private: RenderState _renderState { EVENTS_ON }; - const OverlayID _stylusOverlay; + QUuid _stylus; static bool isWithinBounds(float distance, float min, float max, float hysteresis); static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index fb3c329def..55445a864d 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -42,6 +42,7 @@ class AccountServicesScriptingInterface : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @namespace AccountServices * @property {string} username Read-only. diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 524170c7a5..fb64dbe098 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "AudioClient.h" +#include "AudioHelpers.h" #include "ui/AvatarInputs.h" using namespace scripting; @@ -26,26 +27,9 @@ QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; float Audio::loudnessToLevel(float loudness) { - const float LOG2 = log(2.0f); - const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; - const float LOG2_LOUDNESS_FLOOR = 11.0f; - - float level = 0.0f; - - loudness += 1.0f; - float log2loudness = logf(loudness) / LOG2; - - if (log2loudness <= LOG2_LOUDNESS_FLOOR) { - level = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE; - } else { - level = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE; - } - - if (level > 1.0f) { - level = 1.0; - } - - return level; + float level = 6.02059991f * fastLog2f(loudness); // level in dBFS + level = (level + 48.0f) * (1/39.0f); // map [-48, -9] dBFS to [0, 1] + return glm::clamp(level, 0.0f, 1.0f); } Audio::Audio() : _devices(_contextIsHMD) { @@ -150,18 +134,33 @@ float Audio::getInputLevel() const { }); } -void Audio::onInputLoudnessChanged(float loudness) { +bool Audio::isClipping() const { + return resultWithReadLock([&] { + return _isClipping; + }); +} + +void Audio::onInputLoudnessChanged(float loudness, bool isClipping) { float level = loudnessToLevel(loudness); - bool changed = false; + bool levelChanged = false; + bool isClippingChanged = false; + withWriteLock([&] { if (_inputLevel != level) { _inputLevel = level; - changed = true; + levelChanged = true; + } + if (_isClipping != isClipping) { + _isClipping = isClipping; + isClippingChanged = true; } }); - if (changed) { + if (levelChanged) { emit inputLevelChanged(level); } + if (isClippingChanged) { + emit clippingChanged(isClipping); + } } QString Audio::getContext() const { diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 4c4bf6dd60..e4dcba9130 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -32,6 +32,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * @@ -41,6 +42,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * above the noise floor. * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – * 1.0 (the onset of clipping). Read-only. + * @property {boolean} clipping - true if the audio input is clipping, otherwise false. * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some * devices, and others might only support values of 0.0 and 1.0. @@ -58,6 +60,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) + Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) @@ -74,6 +77,7 @@ public: bool noiseReductionEnabled() const; float getInputVolume() const; float getInputLevel() const; + bool isClipping() const; QString getContext() const; void showMicMeter(bool show); @@ -217,6 +221,14 @@ signals: */ void inputLevelChanged(float level); + /**jsdoc + * Triggered when the clipping state of the input audio changes. + * @function Audio.clippingChanged + * @param {boolean} isClipping - true if the audio input is clipping, otherwise false. + * @returns {Signal} + */ + void clippingChanged(bool isClipping); + /**jsdoc * Triggered when the current context of the audio changes. * @function Audio.contextChanged @@ -237,7 +249,7 @@ private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); void setInputVolume(float volume); - void onInputLoudnessChanged(float loudness); + void onInputLoudnessChanged(float loudness, bool isClipping); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -247,6 +259,7 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; + bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index f6a0b29779..42f2205861 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -24,6 +24,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ClipboardScriptingInterface : public QObject { Q_OBJECT diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index b063e98992..f8adbd5c12 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -199,6 +199,7 @@ class ScriptEngine; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end * points in a {@link RouteObject} mapping. A synonym for Controller.Hardware.Actions. diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index db42b5ca54..c8e251eb3e 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -24,7 +24,8 @@ * * @hifi-interface * @hifi-client-entity - * + * @hifi-avatar + * * @property {number} width * @property {number} height * @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top diff --git a/interface/src/scripting/GooglePolyScriptingInterface.h b/interface/src/scripting/GooglePolyScriptingInterface.h index fb5aed9759..ad44ad6223 100644 --- a/interface/src/scripting/GooglePolyScriptingInterface.h +++ b/interface/src/scripting/GooglePolyScriptingInterface.h @@ -21,6 +21,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class GooglePolyScriptingInterface : public QObject, public Dependency { diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 81f85409cc..d41e8951a2 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -31,6 +31,7 @@ class QScriptEngine; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {Vec3} position - The position of the HMD if currently in VR display mode, otherwise * {@link Vec3(0)|Vec3.ZERO}. Read-only. @@ -53,12 +54,12 @@ class QScriptEngine; * @property {boolean} tabletContextualMode - true if the tablet has been opened in contextual mode, otherwise * false. In contextual mode, the tablet has been opened at a specific world position and orientation rather * than at a position and orientation relative to the user. Read-only. - * @property {Uuid} tabletID - The UUID of the tablet body model overlay. - * @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay. - * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay. - * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay. - * @property {Uuid} miniTabletID - The UUID of the mini tablet's body model overlay. null if not in HMD mode. - * @property {Uuid} miniTabletScreenID - The UUID of the mini tablet's screen overlay. null if not in HMD mode. + * @property {Uuid} tabletID - The UUID of the tablet body model entity. + * @property {Uuid} tabletScreenID - The UUID of the tablet's screen entity. + * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button entity. + * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight entity. + * @property {Uuid} miniTabletID - The UUID of the mini tablet's body model entity. null if not in HMD mode. + * @property {Uuid} miniTabletScreenID - The UUID of the mini tablet's screen entity. null if not in HMD mode. * @property {number} miniTabletHand - The hand that the mini tablet is displayed on: 0 for left hand, * 1 for right hand, -1 if not in HMD mode. * @property {bool} miniTabletEnabled=true - true if the mini tablet is enabled to be displayed, otherwise diff --git a/interface/src/scripting/KeyboardScriptingInterface.cpp b/interface/src/scripting/KeyboardScriptingInterface.cpp index ccf123efed..b031f2d749 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.cpp +++ b/interface/src/scripting/KeyboardScriptingInterface.cpp @@ -64,6 +64,6 @@ bool KeyboardScriptingInterface::getPreferMalletsOverLasers() const { return DependencyManager::get()->getPreferMalletsOverLasers(); } -bool KeyboardScriptingInterface::containsID(OverlayID overlayID) const { - return DependencyManager::get()->containsID(overlayID); +bool KeyboardScriptingInterface::containsID(const QUuid& id) const { + return DependencyManager::get()->containsID(id); } diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h index acee10669e..41c5ab7644 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.h +++ b/interface/src/scripting/KeyboardScriptingInterface.h @@ -16,7 +16,6 @@ #include #include "DependencyManager.h" -#include "ui/overlays/Overlay.h" /**jsdoc * The Keyboard API provides facilities to use 3D Physical keyboard. @@ -24,6 +23,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {bool} raised - true If the keyboard is visible false otherwise * @property {bool} password - true Will show * instead of characters in the text display false otherwise @@ -46,7 +46,7 @@ public: Q_INVOKABLE void disableRightMallet(); Q_INVOKABLE void setLeftHandLaser(unsigned int leftHandLaser); Q_INVOKABLE void setRightHandLaser(unsigned int rightHandLaser); - Q_INVOKABLE bool containsID(OverlayID overlayID) const; + Q_INVOKABLE bool containsID(const QUuid& overlayID) const; private: bool getPreferMalletsOverLasers() const; bool isRaised() const; diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index 81cf775de8..02d5c84138 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -35,6 +35,7 @@ class MenuItemProperties; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ /** diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b6e4df0d40..b390ab7119 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -133,3 +133,11 @@ bool PlatformInfoScriptingInterface::hasRiftControllers() { bool PlatformInfoScriptingInterface::hasViveControllers() { return qApp->hasViveControllers(); } + +bool PlatformInfoScriptingInterface::has3DHTML() { +#if defined(Q_OS_ANDROID) + return false; +#else + return true; +#endif +} diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 3ed57965c9..aece09b008 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -65,6 +65,12 @@ public slots: * @function Window.hasRift * @returns {boolean} true if running on Windows, otherwise false.*/ bool hasViveControllers(); + + /**jsdoc + * Returns true if device supports 3d HTML + * @function Window.hasRift + * @returns {boolean} true if device supports 3d HTML, otherwise false.*/ + bool has3DHTML(); }; #endif // hifi_PlatformInfoScriptingInterface_h diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 4a8a72b16d..c15b5cde11 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -40,19 +40,6 @@ bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) { return true; } -bool GameplayObjects::addToGameplayObjects(const OverlayID& overlayID) { - containsData = true; - if (std::find(_overlayIDs.begin(), _overlayIDs.end(), overlayID) == _overlayIDs.end()) { - _overlayIDs.push_back(overlayID); - } - return true; -} -bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) { - _overlayIDs.erase(std::remove(_overlayIDs.begin(), _overlayIDs.end(), overlayID), _overlayIDs.end()); - return true; -} - - SelectionScriptingInterface::SelectionScriptingInterface() { } @@ -64,7 +51,6 @@ SelectionScriptingInterface::SelectionScriptingInterface() { * * "avatar" * "entity" - * "overlay" * * * @typedef {string} Selection.ItemType @@ -72,20 +58,16 @@ SelectionScriptingInterface::SelectionScriptingInterface() { bool SelectionScriptingInterface::addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id) { if (itemType == "avatar") { return addToGameplayObjects(listName, (QUuid)id); - } else if (itemType == "entity") { + } else if (itemType == "entity" || itemType == "overlay") { return addToGameplayObjects(listName, (EntityItemID)id); - } else if (itemType == "overlay") { - return addToGameplayObjects(listName, (OverlayID)id); } return false; } bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id) { if (itemType == "avatar") { return removeFromGameplayObjects(listName, (QUuid)id); - } else if (itemType == "entity") { + } else if (itemType == "entity" || itemType == "overlay") { return removeFromGameplayObjects(listName, (EntityItemID)id); - } else if (itemType == "overlay") { - return removeFromGameplayObjects(listName, (OverlayID)id); } return false; } @@ -253,12 +235,6 @@ void SelectionScriptingInterface::printList(const QString& listName) { qDebug() << j << ';'; } qDebug() << ""; - - qDebug() << "Overlay IDs:"; - for (auto k : (*currentList).getOverlayIDs()) { - qDebug() << k << ';'; - } - qDebug() << ""; } else { qDebug() << "List named " << listName << " empty"; @@ -272,7 +248,6 @@ void SelectionScriptingInterface::printList(const QString& listName) { * @typedef {object} Selection.SelectedItemsList * @property {Uuid[]} avatars - The IDs of the avatars in the selection. * @property {Uuid[]} entities - The IDs of the entities in the selection. - * @property {Uuid[]} overlays - The IDs of the overlays in the selection. */ QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const { QReadLocker lock(&_selectionListsLock); @@ -281,7 +256,6 @@ QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& lis if (currentList != _selectedItemsListMap.end()) { QList avatarIDs; QList entityIDs; - QList overlayIDs; if ((*currentList).getContainsData()) { if (!(*currentList).getAvatarIDs().empty()) { @@ -294,15 +268,9 @@ QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& lis entityIDs.push_back((QUuid)j ); } } - if (!(*currentList).getOverlayIDs().empty()) { - for (auto j : (*currentList).getOverlayIDs()) { - overlayIDs.push_back((QUuid)j); - } - } } list["avatars"] = (avatarIDs); list["entities"] = (entityIDs); - list["overlays"] = (overlayIDs); return list; } @@ -379,7 +347,6 @@ void SelectionToSceneHandler::updateSceneFromSelectedList() { render::ItemIDs finalList; render::ItemID currentID; auto entityTreeRenderer = DependencyManager::get(); - auto& overlays = qApp->getOverlays(); for (QUuid& currentAvatarID : thisList.getAvatarIDs()) { auto avatar = std::static_pointer_cast(DependencyManager::get()->getAvatarBySessionID(currentAvatarID)); @@ -398,16 +365,6 @@ void SelectionToSceneHandler::updateSceneFromSelectedList() { } } - for (OverlayID& currentOverlayID : thisList.getOverlayIDs()) { - auto overlay = overlays.getOverlay(currentOverlayID); - if (overlay != NULL) { - currentID = overlay->getRenderItemID(); - if (currentID != render::Item::INVALID_ITEM_ID) { - finalList.push_back(currentID); - } - } - } - render::Selection selection(_listName.toStdString(), finalList); transaction.resetSelection(selection); diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 83aec63ee2..fcb4090184 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -19,7 +19,6 @@ #include #include "RenderableEntityItem.h" -#include "ui/overlays/Overlay.h" #include #include @@ -37,15 +36,10 @@ public: bool addToGameplayObjects(const EntityItemID& entityID); bool removeFromGameplayObjects(const EntityItemID& entityID); - std::vector getOverlayIDs() const { return _overlayIDs; } - bool addToGameplayObjects(const OverlayID& overlayID); - bool removeFromGameplayObjects(const OverlayID& overlayID); - private: bool containsData { false }; std::vector _avatarIDs; std::vector _entityIDs; - std::vector _overlayIDs; }; @@ -83,11 +77,12 @@ protected: }; /**jsdoc - * The Selection API provides a means of grouping together avatars, entities, and overlays in named lists. + * The Selection API provides a means of grouping together avatars and entities in named lists. * @namespace Selection * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @example Outline an entity when it is grabbed by a controller. * // Create a box and copy the following text into the entity's "Script URL" field. @@ -174,14 +169,14 @@ public: Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); /**jsdoc - * Print out the list of avatars, entities, and overlays in a selection to the debug log (not the script log). + * Print out the list of avatars and entities in a selection to the debug log (not the script log). * @function Selection.printList * @param {string} listName - The name of the selection list. */ Q_INVOKABLE void printList(const QString& listName); /**jsdoc - * Get the list of avatars, entities, and overlays stored in a selection list. + * Get the list of avatars and entities stored in a selection list. * @function Selection.getSelectedItemsList * @param {string} listName - The name of the selection list. * @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 32d868bb24..e907e550f3 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -21,6 +21,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class SettingsScriptingInterface : public QObject { diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 36ee021b29..005af7f558 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -34,6 +34,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} walletStatus * @property {bool} limitedCommerce diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index c5e558eb3a..baff6444e1 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -31,6 +31,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} innerWidth - The width of the drawable area of the Interface window (i.e., without borders or other * chrome), in pixels. Read-only. diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 789a2a2bdf..799d7ea182 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -59,7 +59,7 @@ void AddressBarDialog::loadHome() { auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); if (homeLocation == "") { - homeLocation = DEFAULT_HIFI_ADDRESS; + homeLocation = DEFAULT_HOME_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 3579776213..e91b1d725c 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -90,7 +90,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { } void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { - PROFILE_RANGE(app, __FUNCTION__); + PROFILE_RANGE(render, __FUNCTION__); if (!_uiTexture) { _uiTexture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); @@ -119,7 +119,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { } void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { - PROFILE_RANGE(app, __FUNCTION__); + PROFILE_RANGE(render, __FUNCTION__); gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get(); @@ -134,9 +134,7 @@ void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { batch.resetViewTransform(); // Render all of the Script based "HUD" aka 2D overlays. - // note: we call them HUD, as opposed to 2D, only because there are some cases of 3D HUD overlays, like the - // cameral controls for the edit.js - qApp->getOverlays().renderHUD(renderArgs); + qApp->getOverlays().render(renderArgs); } void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderArgs) { @@ -178,7 +176,7 @@ static const auto DEFAULT_SAMPLER = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LI static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); void ApplicationOverlay::buildFramebufferObject() { - PROFILE_RANGE(app, __FUNCTION__); + PROFILE_RANGE(render, __FUNCTION__); auto uiSize = glm::uvec2(qApp->getUiSize()); if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) { diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index e67d35e59f..6569792807 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -29,6 +29,7 @@ class AvatarInputs : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {boolean} cameraEnabled Read-only. * @property {boolean} cameraMuted Read-only. diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 24083956ff..a3a875ac40 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -57,7 +57,7 @@ void DialogsManager::showAddressBar() { if (!hmd->getShouldShowTablet()) { hmd->openTablet(); } - qApp->setKeyboardFocusOverlay(hmd->getCurrentTabletScreenID()); + qApp->setKeyboardFocusEntity(hmd->getCurrentTabletScreenID()); setAddressBarVisible(true); } @@ -70,7 +70,7 @@ void DialogsManager::hideAddressBar() { tablet->gotoHomeScreen(); hmd->closeTablet(); } - qApp->setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); + qApp->setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); setAddressBarVisible(false); } diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 9e9a319802..8102df6dc6 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -33,11 +33,6 @@ #include #include -#include "ui/overlays/Overlays.h" -#include "ui/overlays/Overlay.h" -#include "ui/overlays/ModelOverlay.h" -#include "ui/overlays/Cube3DOverlay.h" -#include "ui/overlays/Text3DOverlay.h" #include "avatar/AvatarManager.h" #include "avatar/MyAvatar.h" #include "avatar/AvatarManager.h" @@ -120,21 +115,21 @@ std::pair calculateKeyboardPositionAndOrientation() { float sensorToWorldScale = myAvatar->getSensorToWorldScale(); QUuid tabletID = hmd->getCurrentTabletFrameID(); if (!tabletID.isNull() && hmd->getShouldShowTablet()) { - Overlays& overlays = qApp->getOverlays(); - auto tabletOverlay = std::dynamic_pointer_cast(overlays.getOverlay(tabletID)); - if (tabletOverlay) { - auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); - bool landscapeMode = tablet->getLandscape(); - glm::vec3 keyboardOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_OFFSET : KEYBOARD_TABLET_OFFSET; - glm::vec3 keyboardDegreesOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET : KEYBOARD_TABLET_DEGREES_OFFSET; - glm::vec3 tabletWorldPosition = tabletOverlay->getWorldPosition(); - glm::quat tabletWorldOrientation = tabletOverlay->getWorldOrientation(); - glm::vec3 scaledKeyboardTabletOffset = keyboardOffset * sensorToWorldScale; + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + desiredProperties += PROP_ROTATION; + auto properties = DependencyManager::get()->getEntityProperties(tabletID, desiredProperties); - keyboardLocation.first = tabletWorldPosition + (tabletWorldOrientation * scaledKeyboardTabletOffset); - keyboardLocation.second = tabletWorldOrientation * glm::quat(glm::radians(keyboardDegreesOffset)); - } + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + bool landscapeMode = tablet->getLandscape(); + glm::vec3 keyboardOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_OFFSET : KEYBOARD_TABLET_OFFSET; + glm::vec3 keyboardDegreesOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET : KEYBOARD_TABLET_DEGREES_OFFSET; + glm::vec3 tabletWorldPosition = properties.getPosition(); + glm::quat tabletWorldOrientation = properties.getRotation(); + glm::vec3 scaledKeyboardTabletOffset = keyboardOffset * sensorToWorldScale; + keyboardLocation.first = tabletWorldPosition + (tabletWorldOrientation * scaledKeyboardTabletOffset); + keyboardLocation.second = tabletWorldOrientation * glm::quat(glm::radians(keyboardDegreesOffset)); } else { glm::vec3 avatarWorldPosition = myAvatar->getWorldPosition(); glm::quat avatarWorldOrientation = myAvatar->getWorldOrientation(); @@ -148,31 +143,27 @@ std::pair calculateKeyboardPositionAndOrientation() { } void Key::saveDimensionsAndLocalPosition() { - Overlays& overlays = qApp->getOverlays(); - auto model3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_keyID)); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_DIMENSIONS; + auto properties = DependencyManager::get()->getEntityProperties(_keyID, desiredProperties); - if (model3DOverlay) { - _originalLocalPosition = model3DOverlay->getLocalPosition(); - _originalDimensions = model3DOverlay->getDimensions(); - _currentLocalPosition = _originalLocalPosition; - } + _originalLocalPosition = properties.getLocalPosition(); + _originalDimensions = properties.getDimensions(); + _currentLocalPosition = _originalLocalPosition; + _originalDimensionsAndLocalPositionSaved = true; } void Key::scaleKey(float sensorToWorldScale) { - Overlays& overlays = qApp->getOverlays(); - auto model3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_keyID)); - - if (model3DOverlay) { + if (_originalDimensionsAndLocalPositionSaved) { glm::vec3 scaledLocalPosition = _originalLocalPosition * sensorToWorldScale; glm::vec3 scaledDimensions = _originalDimensions * sensorToWorldScale; _currentLocalPosition = scaledLocalPosition; - QVariantMap properties { - { "dimensions", vec3toVariant(scaledDimensions) }, - { "localPosition", vec3toVariant(scaledLocalPosition) } - }; - - overlays.editOverlay(_keyID, properties); + EntityItemProperties properties; + properties.setDimensions(scaledDimensions); + properties.setLocalPosition(scaledLocalPosition); + DependencyManager::get()->editEntity(_keyID, properties); } } @@ -241,6 +232,18 @@ void Keyboard::registerKeyboardHighlighting() { selection->enableListToScene(KEY_PRESSED_HIGHLIGHT); } +void Keyboard::disableSelectionLists() { + auto selection = DependencyManager::get(); + selection->disableListHighlight(KEY_HOVER_HIGHLIGHT); + selection->disableListHighlight(KEY_PRESSED_HIGHLIGHT); +} + +void Keyboard::enableSelectionLists() { + auto selection = DependencyManager::get(); + selection->enableListHighlight(KEY_HOVER_HIGHLIGHT, KEY_HOVERING_STYLE); + selection->enableListHighlight(KEY_PRESSED_HIGHLIGHT, KEY_PRESSING_STYLE); +} + bool Keyboard::getUse3DKeyboard() const { return _use3DKeyboardLock.resultWithReadLock([&] { return _use3DKeyboard.get(); @@ -262,21 +265,21 @@ void Keyboard::createKeyboard() { QVariantMap leftStylusProperties { { "hand", LEFT_HAND_CONTROLLER_INDEX }, - { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, { "model", modelProperties }, { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } }; QVariantMap rightStylusProperties { { "hand", RIGHT_HAND_CONTROLLER_INDEX }, - { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() }, { "model", modelProperties }, { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } }; - _leftHandStylus = pointerManager->addPointer(std::make_shared(leftStylusProperties, StylusPointer::buildStylusOverlay(leftStylusProperties), true, true, + _leftHandStylus = pointerManager->addPointer(std::make_shared(leftStylusProperties, StylusPointer::buildStylus(leftStylusProperties), true, true, MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS)); - _rightHandStylus = pointerManager->addPointer(std::make_shared(rightStylusProperties, StylusPointer::buildStylusOverlay(rightStylusProperties), true, true, + _rightHandStylus = pointerManager->addPointer(std::make_shared(rightStylusProperties, StylusPointer::buildStylus(rightStylusProperties), true, true, MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS)); pointerManager->disablePointer(_rightHandStylus); @@ -300,6 +303,7 @@ void Keyboard::setRaised(bool raised) { raiseKeyboardAnchor(raised); raiseKeyboard(raised); raised ? enableStylus() : disableStylus(); + raised ? enableSelectionLists() : disableSelectionLists(); withWriteLock([&] { _raised = raised; _layerIndex = 0; @@ -312,93 +316,65 @@ void Keyboard::setRaised(bool raised) { } void Keyboard::updateTextDisplay() { - Overlays& overlays = qApp->getOverlays(); - auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto entityScriptingInterface = DependencyManager::get(); float sensorToWorldScale = myAvatar->getSensorToWorldScale(); - float textWidth = (float) overlays.textSize(_textDisplay.overlayID, _typedCharacters).width(); + float textWidth = (float)entityScriptingInterface->textSize(_textDisplay.entityID, _typedCharacters).width(); glm::vec3 scaledDimensions = _textDisplay.dimensions; scaledDimensions *= sensorToWorldScale; float leftMargin = (scaledDimensions.x / 2); scaledDimensions.x += textWidth; + scaledDimensions.y *= 1.25f; - - QVariantMap textDisplayProperties { - { "dimensions", vec3toVariant(scaledDimensions) }, - { "leftMargin", leftMargin }, - { "text", _typedCharacters }, - { "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) } - }; - - overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); + EntityItemProperties properties; + properties.setDimensions(scaledDimensions); + properties.setLeftMargin(leftMargin); + properties.setText(_typedCharacters); + properties.setLineHeight(_textDisplay.lineHeight * sensorToWorldScale); + entityScriptingInterface->editEntity(_textDisplay.entityID, properties); } void Keyboard::raiseKeyboardAnchor(bool raise) const { - Overlays& overlays = qApp->getOverlays(); - OverlayID anchorOverlayID = _anchor.overlayID; - auto anchorOverlay = std::dynamic_pointer_cast(overlays.getOverlay(anchorOverlayID)); - if (anchorOverlay) { + auto entityScriptingInterface = DependencyManager::get(); + + EntityItemProperties properties; + properties.setVisible(raise); + + entityScriptingInterface->editEntity(_textDisplay.entityID, properties); + entityScriptingInterface->editEntity(_backPlate.entityID, properties); + + if (_resetKeyboardPositionOnRaise) { std::pair keyboardLocation = calculateKeyboardPositionAndOrientation(); - if (_resetKeyboardPositionOnRaise) { - anchorOverlay->setWorldPosition(keyboardLocation.first); - anchorOverlay->setWorldOrientation(keyboardLocation.second); - } - anchorOverlay->setVisible(raise); - - QVariantMap textDisplayProperties { - { "visible", raise } - }; - - overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); - - auto backPlateOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_backPlate.overlayID)); - - if (backPlateOverlay) { - backPlateOverlay->setVisible(raise); - } + properties.setPosition(keyboardLocation.first); + properties.setRotation(keyboardLocation.second); } + entityScriptingInterface->editEntity(_anchor.entityID, properties); } void Keyboard::scaleKeyboard(float sensorToWorldScale) { - Overlays& overlays = qApp->getOverlays(); + auto entityScriptingInterface = DependencyManager::get(); - glm::vec3 scaledDimensions = _anchor.originalDimensions * sensorToWorldScale; - auto volume3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_anchor.overlayID)); - - if (volume3DOverlay) { - volume3DOverlay->setDimensions(scaledDimensions); + { + EntityItemProperties properties; + properties.setLocalPosition(_backPlate.localPosition * sensorToWorldScale); + properties.setDimensions(_backPlate.dimensions * sensorToWorldScale); + entityScriptingInterface->editEntity(_backPlate.entityID, properties); } - for (auto& keyboardLayer: _keyboardLayers) { + for (auto& keyboardLayer : _keyboardLayers) { for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { iter.value().scaleKey(sensorToWorldScale); } } - - - glm::vec3 scaledLocalPosition = _textDisplay.localPosition * sensorToWorldScale; - glm::vec3 textDisplayScaledDimensions = _textDisplay.dimensions * sensorToWorldScale; - - QVariantMap textDisplayProperties { - { "localPosition", vec3toVariant(scaledLocalPosition) }, - { "dimensions", vec3toVariant(textDisplayScaledDimensions) }, - { "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) } - }; - - overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); - - - glm::vec3 backPlateScaledDimensions = _backPlate.dimensions * sensorToWorldScale; - glm::vec3 backPlateScaledLocalPosition = _backPlate.localPosition * sensorToWorldScale; - - QVariantMap backPlateProperties { - { "localPosition", vec3toVariant(backPlateScaledLocalPosition) }, - { "dimensions", vec3toVariant(backPlateScaledDimensions) } - }; - - overlays.editOverlay(_backPlate.overlayID, backPlateProperties); + { + EntityItemProperties properties; + properties.setLocalPosition(_textDisplay.localPosition * sensorToWorldScale); + properties.setDimensions(_textDisplay.dimensions * sensorToWorldScale); + properties.setLineHeight(_textDisplay.lineHeight * sensorToWorldScale); + entityScriptingInterface->editEntity(_textDisplay.entityID, properties); + } } void Keyboard::startLayerSwitchTimer() { @@ -419,13 +395,12 @@ void Keyboard::raiseKeyboard(bool raise) const { if (_keyboardLayers.empty()) { return; } - Overlays& overlays = qApp->getOverlays(); + const auto& keyboardLayer = _keyboardLayers[_layerIndex]; + EntityItemProperties properties; + properties.setVisible(raise); for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { - auto base3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(iter.key())); - if (base3DOverlay) { - base3DOverlay->setVisible(raise); - } + DependencyManager::get()->editEntity(iter.key(), properties); } } @@ -465,19 +440,15 @@ bool Keyboard::getPreferMalletsOverLasers() const { } void Keyboard::switchToLayer(int layerIndex) { + auto entityScriptingInterface = DependencyManager::get(); if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) { - Overlays& overlays = qApp->getOverlays(); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + desiredProperties += PROP_ROTATION; + auto oldProperties = entityScriptingInterface->getEntityProperties(_anchor.entityID, desiredProperties); - OverlayID currentAnchorOverlayID = _anchor.overlayID; - - glm::vec3 currentOverlayPosition; - glm::quat currentOverlayOrientation; - - auto currentAnchorOverlay = std::dynamic_pointer_cast(overlays.getOverlay(currentAnchorOverlayID)); - if (currentAnchorOverlay) { - currentOverlayPosition = currentAnchorOverlay->getWorldPosition(); - currentOverlayOrientation = currentAnchorOverlay->getWorldOrientation(); - } + glm::vec3 currentPosition = oldProperties.getPosition(); + glm::quat currentOrientation = oldProperties.getRotation(); raiseKeyboardAnchor(false); raiseKeyboard(false); @@ -487,19 +458,17 @@ void Keyboard::switchToLayer(int layerIndex) { raiseKeyboardAnchor(true); raiseKeyboard(true); - OverlayID newAnchorOverlayID = _anchor.overlayID; - auto newAnchorOverlay = std::dynamic_pointer_cast(overlays.getOverlay(newAnchorOverlayID)); - if (newAnchorOverlay) { - newAnchorOverlay->setWorldPosition(currentOverlayPosition); - newAnchorOverlay->setWorldOrientation(currentOverlayOrientation); - } + EntityItemProperties properties; + properties.setPosition(currentPosition); + properties.setRotation(currentOrientation); + entityScriptingInterface->editEntity(_anchor.entityID, properties); startLayerSwitchTimer(); } } -bool Keyboard::shouldProcessOverlayAndPointerEvent(const PointerEvent& event, const OverlayID& overlayID) const { - return (shouldProcessPointerEvent(event) && shouldProcessOverlay(overlayID)); +bool Keyboard::shouldProcessEntityAndPointerEvent(const PointerEvent& event) const { + return (shouldProcessPointerEvent(event) && shouldProcessEntity()); } bool Keyboard::shouldProcessPointerEvent(const PointerEvent& event) const { @@ -510,14 +479,14 @@ bool Keyboard::shouldProcessPointerEvent(const PointerEvent& event) const { return ((isStylusEvent && preferMalletsOverLasers) || (isLaserEvent && !preferMalletsOverLasers)); } -void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event) { +void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) { auto buttonType = event.getButton(); - if (!shouldProcessOverlayAndPointerEvent(event, overlayID) || buttonType != PointerEvent::PrimaryButton) { + if (!shouldProcessEntityAndPointerEvent(event) || buttonType != PointerEvent::PrimaryButton) { return; } auto& keyboardLayer = _keyboardLayers[_layerIndex]; - auto search = keyboardLayer.find(overlayID); + auto search = keyboardLayer.find(id); if (search == keyboardLayer.end()) { return; @@ -533,13 +502,9 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent auto userInputMapper = DependencyManager::get(); userInputMapper->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex); - Overlays& overlays = qApp->getOverlays(); - auto base3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(overlayID)); - - glm::vec3 keyWorldPosition; - if (base3DOverlay) { - keyWorldPosition = base3DOverlay->getWorldPosition(); - } + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + glm::vec3 keyWorldPosition = DependencyManager::get()->getEntityProperties(id, desiredProperties).getPosition(); AudioInjectorOptions audioOptions; audioOptions.localOnly = true; @@ -601,7 +566,7 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent key.startTimer(KEY_PRESS_TIMEOUT_MS); } auto selection = DependencyManager::get(); - selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID); + selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "entity", id); } } @@ -617,25 +582,23 @@ void Keyboard::setRightHandLaser(unsigned int rightHandLaser) { }); } -void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event) { - if (!shouldProcessOverlayAndPointerEvent(event, overlayID)) { +void Keyboard::handleTriggerEnd(const QUuid& id, const PointerEvent& event) { + if (!shouldProcessEntityAndPointerEvent(event)) { return; } auto& keyboardLayer = _keyboardLayers[_layerIndex]; - auto search = keyboardLayer.find(overlayID); + auto search = keyboardLayer.find(id); if (search == keyboardLayer.end()) { return; } - Key& key = search.value();; - Overlays& overlays = qApp->getOverlays(); - auto base3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(overlayID)); + Key& key = search.value(); - if (base3DOverlay) { - base3DOverlay->setLocalPosition(key.getCurrentLocalPosition()); - } + EntityItemProperties properties; + properties.setLocalPosition(key.getCurrentLocalPosition()); + DependencyManager::get()->editEntity(id, properties); key.setIsPressed(false); if (key.timerFinished() && getPreferMalletsOverLasers()) { @@ -643,78 +606,79 @@ void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& } auto selection = DependencyManager::get(); - selection->removeFromSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID); + selection->removeFromSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "entity", id); } -void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event) { - if (!shouldProcessOverlayAndPointerEvent(event, overlayID)) { +void Keyboard::handleTriggerContinue(const QUuid& id, const PointerEvent& event) { + if (!shouldProcessEntityAndPointerEvent(event)) { return; } auto& keyboardLayer = _keyboardLayers[_layerIndex]; - auto search = keyboardLayer.find(overlayID); + auto search = keyboardLayer.find(id); if (search == keyboardLayer.end()) { return; } Key& key = search.value(); - Overlays& overlays = qApp->getOverlays(); - if (!key.isPressed() && getPreferMalletsOverLasers()) { - auto base3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(overlayID)); + unsigned int pointerID = event.getID(); + auto pointerManager = DependencyManager::get(); + auto pickResult = pointerManager->getPrevPickResult(pointerID); + auto stylusPickResult = std::dynamic_pointer_cast(pickResult); + float distance = stylusPickResult->distance; - if (base3DOverlay) { - unsigned int pointerID = event.getID(); - auto pointerManager = DependencyManager::get(); - auto pickResult = pointerManager->getPrevPickResult(pointerID); - auto stylusPickResult = std::dynamic_pointer_cast(pickResult); - float distance = stylusPickResult->distance; + static const float PENETRATION_THRESHOLD = 0.025f; + if (distance < PENETRATION_THRESHOLD) { + static const float Z_OFFSET = 0.002f; - static const float PENATRATION_THRESHOLD = 0.025f; - if (distance < PENATRATION_THRESHOLD) { - static const float Z_OFFSET = 0.002f; - glm::quat overlayOrientation = base3DOverlay->getWorldOrientation(); - glm::vec3 overlayYAxis = overlayOrientation * Z_AXIS; - glm::vec3 overlayYOffset = overlayYAxis * Z_OFFSET; - glm::vec3 localPosition = key.getCurrentLocalPosition() - overlayYOffset; - base3DOverlay->setLocalPosition(localPosition); - key.setIsPressed(true); - } + auto entityScriptingInterface = DependencyManager::get(); + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_ROTATION; + glm::quat orientation = entityScriptingInterface->getEntityProperties(id, desiredProperties).getRotation(); + glm::vec3 yAxis = orientation * Z_AXIS; + glm::vec3 yOffset = yAxis * Z_OFFSET; + glm::vec3 localPosition = key.getCurrentLocalPosition() - yOffset; + + EntityItemProperties properties; + properties.setLocalPosition(localPosition); + entityScriptingInterface->editEntity(id, properties); + key.setIsPressed(true); } } } -void Keyboard::handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event) { - if (!shouldProcessOverlayAndPointerEvent(event, overlayID)) { +void Keyboard::handleHoverBegin(const QUuid& id, const PointerEvent& event) { + if (!shouldProcessEntityAndPointerEvent(event)) { return; } auto& keyboardLayer = _keyboardLayers[_layerIndex]; - auto search = keyboardLayer.find(overlayID); + auto search = keyboardLayer.find(id); if (search == keyboardLayer.end()) { return; } auto selection = DependencyManager::get(); - selection->addToSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID); + selection->addToSelectedItemsList(KEY_HOVER_HIGHLIGHT, "entity", id); } -void Keyboard::handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event) { - if (!shouldProcessOverlayAndPointerEvent(event, overlayID)) { +void Keyboard::handleHoverEnd(const QUuid& id, const PointerEvent& event) { + if (!shouldProcessEntityAndPointerEvent(event)) { return; } auto& keyboardLayer = _keyboardLayers[_layerIndex]; - auto search = keyboardLayer.find(overlayID); + auto search = keyboardLayer.find(id); if (search == keyboardLayer.end()) { return; } auto selection = DependencyManager::get(); - selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID); + selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "entity", id); } void Keyboard::disableStylus() { @@ -752,7 +716,6 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { } clearKeyboardKeys(); - Overlays& overlays = qApp->getOverlays(); auto requestData = request->getData(); QVector includeItems; @@ -776,54 +739,60 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { return; } - QVariantMap anchorProperties { - { "name", "KeyboardAnchor"}, - { "isSolid", true }, - { "visible", false }, - { "grabbable", true }, - { "ignorePickIntersection", false }, - { "dimensions", anchorObject["dimensions"].toVariant() }, - { "position", anchorObject["position"].toVariant() }, - { "orientation", anchorObject["rotation"].toVariant() } - }; + auto entityScriptingInterface = DependencyManager::get(); + { + glm::vec3 dimensions = vec3FromVariant(anchorObject["dimensions"].toVariant()); - glm::vec3 dimensions = vec3FromVariant(anchorObject["dimensions"].toVariant()); + EntityItemProperties properties; + properties.setType(EntityTypes::Box); + properties.setName("KeyboardAnchor"); + properties.setVisible(false); + properties.getGrab().setGrabbable(true); + properties.setIgnorePickIntersection(false); + properties.setDimensions(dimensions); + properties.setPosition(vec3FromVariant(anchorObject["position"].toVariant())); + properties.setRotation(quatFromVariant(anchorObject["rotation"].toVariant())); - Anchor anchor; - anchor.overlayID = overlays.addOverlay("cube", anchorProperties); - anchor.originalDimensions = dimensions; - _anchor = anchor; + Anchor anchor; + anchor.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); + anchor.originalDimensions = dimensions; + _anchor = anchor; + } - QJsonObject backPlateObject = jsonObject["backPlate"].toObject(); + { + QJsonObject backPlateObject = jsonObject["backPlate"].toObject(); + glm::vec3 position = vec3FromVariant(backPlateObject["position"].toVariant()); + glm::vec3 dimensions = vec3FromVariant(backPlateObject["dimensions"].toVariant()); + glm::quat rotation = quatFromVariant(backPlateObject["rotation"].toVariant()); - QVariantMap backPlateProperties { - { "name", "backPlate"}, - { "isSolid", true }, - { "visible", true }, - { "grabbable", false }, - { "alpha", 0.0 }, - { "ignoreRayIntersection", false}, - { "dimensions", backPlateObject["dimensions"].toVariant() }, - { "position", backPlateObject["position"].toVariant() }, - { "orientation", backPlateObject["rotation"].toVariant() }, - { "parentID", _anchor.overlayID } - }; + EntityItemProperties properties; + properties.setType(EntityTypes::Box); + properties.setName("Keyboard-BackPlate"); + properties.setVisible(true); + properties.getGrab().setGrabbable(false); + properties.setAlpha(0.0f); + properties.setIgnorePickIntersection(false); + properties.setDimensions(dimensions); + properties.setPosition(position); + properties.setRotation(rotation); + properties.setParentID(_anchor.entityID); - BackPlate backPlate; - backPlate.overlayID = overlays.addOverlay("cube", backPlateProperties); - backPlate.dimensions = vec3FromVariant(backPlateObject["dimensions"].toVariant()); - backPlate.localPosition = vec3FromVariant(overlays.getProperty(backPlate.overlayID, "localPosition").value); - _backPlate = backPlate; + BackPlate backPlate; + backPlate.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); + backPlate.dimensions = dimensions; + glm::quat anchorEntityInverseWorldOrientation = glm::inverse(rotation); + glm::vec3 anchorEntityLocalTranslation = anchorEntityInverseWorldOrientation * -position; + backPlate.localPosition = (anchorEntityInverseWorldOrientation * position) + anchorEntityLocalTranslation; + _backPlate = backPlate; + } const QJsonArray& keyboardLayers = jsonObject["layers"].toArray(); int keyboardLayerCount = keyboardLayers.size(); _keyboardLayers.reserve(keyboardLayerCount); - - for (int keyboardLayerIndex = 0; keyboardLayerIndex < keyboardLayerCount; keyboardLayerIndex++) { const QJsonValue& keyboardLayer = keyboardLayers[keyboardLayerIndex].toArray(); - QHash keyboardLayerKeys; + QHash keyboardLayerKeys; foreach (const QJsonValue& keyboardKeyValue, keyboardLayer.toArray()) { QVariantMap textureMap; @@ -841,20 +810,18 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { QString modelUrl = keyboardKeyValue["modelURL"].toString(); QString url = (useResourcePath ? (resourcePath + modelUrl) : modelUrl); - QVariantMap properties { - { "dimensions", keyboardKeyValue["dimensions"].toVariant() }, - { "position", keyboardKeyValue["position"].toVariant() }, - { "visible", false }, - { "isSolid", true }, - { "emissive", true }, - { "parentID", _anchor.overlayID }, - { "url", url }, - { "textures", textureMap }, - { "grabbable", false }, - { "localOrientation", keyboardKeyValue["localOrientation"].toVariant() } - }; - - OverlayID overlayID = overlays.addOverlay("model", properties); + EntityItemProperties properties; + properties.setType(EntityTypes::Model); + properties.setDimensions(vec3FromVariant(keyboardKeyValue["dimensions"].toVariant())); + properties.setPosition(vec3FromVariant(keyboardKeyValue["position"].toVariant())); + properties.setVisible(false); + properties.setEmissive(true); + properties.setParentID(_anchor.entityID); + properties.setModelURL(url); + properties.setTextures(QString(QJsonDocument::fromVariant(textureMap).toJson())); + properties.getGrab().setGrabbable(false); + properties.setLocalRotation(quatFromVariant(keyboardKeyValue["localOrientation"].toVariant())); + QUuid id = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); QString keyType = keyboardKeyValue["type"].toString(); QString keyString = keyboardKeyValue["key"].toString(); @@ -869,48 +836,54 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { key.setSwitchToLayerIndex(switchToLayer); } } - key.setID(overlayID); + key.setID(id); key.setKeyString(keyString); key.saveDimensionsAndLocalPosition(); includeItems.append(key.getID()); - _itemsToIgnore.append(key.getID()); - keyboardLayerKeys.insert(overlayID, key); + _itemsToIgnore.insert(key.getID()); + keyboardLayerKeys.insert(id, key); } _keyboardLayers.push_back(keyboardLayerKeys); } - TextDisplay textDisplay; - QJsonObject displayTextObject = jsonObject["textDisplay"].toObject(); + { + QJsonObject displayTextObject = jsonObject["textDisplay"].toObject(); + glm::vec3 dimensions = vec3FromVariant(displayTextObject["dimensions"].toVariant()); + glm::vec3 localPosition = vec3FromVariant(displayTextObject["localPosition"].toVariant()); + float lineHeight = (float)displayTextObject["lineHeight"].toDouble(); - QVariantMap displayTextProperties { - { "dimensions", displayTextObject["dimensions"].toVariant() }, - { "localPosition", displayTextObject["localPosition"].toVariant() }, - { "localOrientation", displayTextObject["localOrientation"].toVariant() }, - { "leftMargin", displayTextObject["leftMargin"].toVariant() }, - { "rightMargin", displayTextObject["rightMargin"].toVariant() }, - { "topMargin", displayTextObject["topMargin"].toVariant() }, - { "bottomMargin", displayTextObject["bottomMargin"].toVariant() }, - { "lineHeight", displayTextObject["lineHeight"].toVariant() }, - { "visible", false }, - { "emissive", true }, - { "grabbable", false }, - { "text", ""}, - { "parentID", _anchor.overlayID } - }; + EntityItemProperties properties; + properties.setType(EntityTypes::Text); + properties.setDimensions(dimensions); + properties.setLocalPosition(localPosition); + properties.setLocalRotation(quatFromVariant(displayTextObject["localOrientation"].toVariant())); + properties.setLeftMargin((float)displayTextObject["leftMargin"].toDouble()); + properties.setRightMargin((float)displayTextObject["rightMargin"].toDouble()); + properties.setTopMargin((float)displayTextObject["topMargin"].toDouble()); + properties.setBottomMargin((float)displayTextObject["bottomMargin"].toDouble()); + properties.setLineHeight((float)displayTextObject["lineHeight"].toDouble()); + properties.setVisible(false); + properties.setEmissive(true); + properties.getGrab().setGrabbable(false); + properties.setText(""); + properties.setTextAlpha(1.0f); + properties.setBackgroundAlpha(0.7f); + properties.setParentID(_anchor.entityID); - textDisplay.overlayID = overlays.addOverlay("text3d", displayTextProperties); - textDisplay.localPosition = vec3FromVariant(displayTextObject["localPosition"].toVariant()); - textDisplay.dimensions = vec3FromVariant(displayTextObject["dimensions"].toVariant()); - textDisplay.lineHeight = (float) displayTextObject["lineHeight"].toDouble(); - - _textDisplay = textDisplay; + TextDisplay textDisplay; + textDisplay.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); + textDisplay.localPosition = localPosition; + textDisplay.dimensions = dimensions; + textDisplay.lineHeight = lineHeight; + _textDisplay = textDisplay; + } _ignoreItemsLock.withWriteLock([&] { - _itemsToIgnore.append(_textDisplay.overlayID); - _itemsToIgnore.append(_anchor.overlayID); + _itemsToIgnore.insert(_textDisplay.entityID); + _itemsToIgnore.insert(_anchor.entityID); }); _layerIndex = 0; auto pointerManager = DependencyManager::get(); @@ -922,34 +895,33 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { } -OverlayID Keyboard::getAnchorID() { - return _ignoreItemsLock.resultWithReadLock([&] { - return _anchor.overlayID; +QUuid Keyboard::getAnchorID() { + return _ignoreItemsLock.resultWithReadLock([&] { + return _anchor.entityID; }); } -bool Keyboard::shouldProcessOverlay(const OverlayID& overlayID) const { - return (!_keyboardLayers.empty() && isLayerSwitchTimerFinished() && overlayID != _backPlate.overlayID); +bool Keyboard::shouldProcessEntity() const { + return (!_keyboardLayers.empty() && isLayerSwitchTimerFinished()); } -QVector Keyboard::getKeysID() { - return _ignoreItemsLock.resultWithReadLock>([&] { +QSet Keyboard::getKeyIDs() { + return _ignoreItemsLock.resultWithReadLock>([&] { return _itemsToIgnore; }); } void Keyboard::clearKeyboardKeys() { - Overlays& overlays = qApp->getOverlays(); - + auto entityScriptingInterface = DependencyManager::get(); for (const auto& keyboardLayer: _keyboardLayers) { for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { - overlays.deleteOverlay(iter.key()); + entityScriptingInterface->deleteEntity(iter.key()); } } - overlays.deleteOverlay(_anchor.overlayID); - overlays.deleteOverlay(_textDisplay.overlayID); - overlays.deleteOverlay(_backPlate.overlayID); + entityScriptingInterface->deleteEntity(_anchor.entityID); + entityScriptingInterface->deleteEntity(_textDisplay.entityID); + entityScriptingInterface->deleteEntity(_backPlate.entityID); _keyboardLayers.clear(); @@ -989,8 +961,8 @@ void Keyboard::disableRightMallet() { pointerManager->disablePointer(_rightHandStylus); } -bool Keyboard::containsID(OverlayID overlayID) const { +bool Keyboard::containsID(const QUuid& id) const { return resultWithReadLock([&] { - return _itemsToIgnore.contains(overlayID) || _backPlate.overlayID == overlayID; + return _itemsToIgnore.contains(id) || _backPlate.entityID == id; }); } diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h index bcb6ef1a22..958c862520 100644 --- a/interface/src/ui/Keyboard.h +++ b/interface/src/ui/Keyboard.h @@ -25,8 +25,6 @@ #include #include -#include "ui/overlays/Overlay.h" - class PointerEvent; @@ -47,8 +45,8 @@ public: static Key::Type getKeyTypeFromString(const QString& keyTypeString); - OverlayID getID() const { return _keyID; } - void setID(OverlayID overlayID) { _keyID = overlayID; } + QUuid getID() const { return _keyID; } + void setID(const QUuid& id) { _keyID = id; } void startTimer(int time); bool timerFinished(); @@ -77,12 +75,13 @@ private: int _switchToLayer { 0 }; bool _pressed { false }; - OverlayID _keyID; + QUuid _keyID; QString _keyString; glm::vec3 _originalLocalPosition; glm::vec3 _originalDimensions; glm::vec3 _currentLocalPosition; + bool _originalDimensionsAndLocalPositionSaved { false }; std::shared_ptr _timer { std::make_shared() }; }; @@ -111,35 +110,35 @@ public: bool getUse3DKeyboard() const; void setUse3DKeyboard(bool use); - bool containsID(OverlayID overlayID) const; + bool containsID(const QUuid& id) const; void loadKeyboardFile(const QString& keyboardFile); - QVector getKeysID(); - OverlayID getAnchorID(); + QSet getKeyIDs(); + QUuid getAnchorID(); public slots: - void handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event); - void handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event); - void handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event); - void handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event); - void handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event); + void handleTriggerBegin(const QUuid& id, const PointerEvent& event); + void handleTriggerEnd(const QUuid& id, const PointerEvent& event); + void handleTriggerContinue(const QUuid& id, const PointerEvent& event); + void handleHoverBegin(const QUuid& id, const PointerEvent& event); + void handleHoverEnd(const QUuid& id, const PointerEvent& event); void scaleKeyboard(float sensorToWorldScale); private: struct Anchor { - OverlayID overlayID; + QUuid entityID; glm::vec3 originalDimensions; }; struct BackPlate { - OverlayID overlayID; + QUuid entityID; glm::vec3 dimensions; glm::vec3 localPosition; }; struct TextDisplay { float lineHeight; - OverlayID overlayID; + QUuid entityID; glm::vec3 localPosition; glm::vec3 dimensions; }; @@ -148,14 +147,16 @@ private: void raiseKeyboardAnchor(bool raise) const; void enableStylus(); void disableStylus(); + void enableSelectionLists(); + void disableSelectionLists(); void setLayerIndex(int layerIndex); void clearKeyboardKeys(); void switchToLayer(int layerIndex); void updateTextDisplay(); - bool shouldProcessOverlayAndPointerEvent(const PointerEvent& event, const OverlayID& overlayID) const; + bool shouldProcessEntityAndPointerEvent(const PointerEvent& event) const; bool shouldProcessPointerEvent(const PointerEvent& event) const; - bool shouldProcessOverlay(const OverlayID& overlayID) const; + bool shouldProcessEntity() const; void startLayerSwitchTimer(); bool isLayerSwitchTimerFinished() const; @@ -184,8 +185,8 @@ private: Anchor _anchor; BackPlate _backPlate; - QVector _itemsToIgnore; - std::vector> _keyboardLayers; + QSet _itemsToIgnore; + std::vector> _keyboardLayers; }; #endif diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 4359318833..b4f504822f 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -34,7 +34,7 @@ HIFI_QML_DEF(LoginDialog) static const QUrl TABLET_LOGIN_DIALOG_URL("dialogs/TabletLoginDialog.qml"); -const QUrl OVERLAY_LOGIN_DIALOG = PathUtils::qmlUrl("OverlayLoginDialog.qml"); +const QUrl LOGIN_DIALOG = PathUtils::qmlUrl("OverlayLoginDialog.qml"); LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { auto accountManager = DependencyManager::get(); @@ -71,7 +71,7 @@ void LoginDialog::showWithSelection() { if (!qApp->getLoginDialogPoppedUp()) { tablet->initialScreen(TABLET_LOGIN_DIALOG_URL); } else { - qApp->createLoginDialogOverlay(); + qApp->createLoginDialog(); } } diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 7c932932cf..e2fa8adc61 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -18,7 +18,7 @@ class QNetworkReply; -extern const QUrl OVERLAY_LOGIN_DIALOG; +extern const QUrl LOGIN_DIALOG; class LoginDialog : public OffscreenQmlDialog { Q_OBJECT diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 8fc05775bd..77bdfd4ac1 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -42,6 +42,7 @@ private: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class Snapshot : public QObject, public Dependency { diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index cb204c9772..e3697ee8ec 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -158,7 +158,7 @@ void Stats::updateStats(bool force) { STAT_UPDATE(rayPicksCount, totalPicks[PickQuery::Ray]); STAT_UPDATE(parabolaPicksCount, totalPicks[PickQuery::Parabola]); STAT_UPDATE(collisionPicksCount, totalPicks[PickQuery::Collision]); - std::vector updatedPicks = pickManager->getUpdatedPickCounts(); + std::vector updatedPicks = pickManager->getUpdatedPickCounts(); STAT_UPDATE(stylusPicksUpdated, updatedPicks[PickQuery::Stylus]); STAT_UPDATE(rayPicksUpdated, updatedPicks[PickQuery::Ray]); STAT_UPDATE(parabolaPicksUpdated, updatedPicks[PickQuery::Parabola]); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index ae608cfddb..36e92b00af 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -27,6 +27,7 @@ private: \ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * @@ -171,10 +172,10 @@ private: \ * @property {number} rayPicksCount - Read-only. * @property {number} parabolaPicksCount - Read-only. * @property {number} collisionPicksCount - Read-only. - * @property {Vec4} stylusPicksUpdated - Read-only. - * @property {Vec4} rayPicksUpdated - Read-only. - * @property {Vec4} parabolaPicksUpdated - Read-only. - * @property {Vec4} collisionPicksUpdated - Read-only. + * @property {Vec3} stylusPicksUpdated - Read-only. + * @property {Vec3} rayPicksUpdated - Read-only. + * @property {Vec3} parabolaPicksUpdated - Read-only. + * @property {Vec3} collisionPicksUpdated - Read-only. */ // Properties from x onwards are QQuickItem properties. @@ -296,10 +297,10 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, rayPicksCount, 0) STATS_PROPERTY(int, parabolaPicksCount, 0) STATS_PROPERTY(int, collisionPicksCount, 0) - STATS_PROPERTY(QVector4D, stylusPicksUpdated, QVector4D(0, 0, 0, 0)) - STATS_PROPERTY(QVector4D, rayPicksUpdated, QVector4D(0, 0, 0, 0)) - STATS_PROPERTY(QVector4D, parabolaPicksUpdated, QVector4D(0, 0, 0, 0)) - STATS_PROPERTY(QVector4D, collisionPicksUpdated, QVector4D(0, 0, 0, 0)) + STATS_PROPERTY(QVector3D, stylusPicksUpdated, QVector3D(0, 0, 0)) + STATS_PROPERTY(QVector3D, rayPicksUpdated, QVector3D(0, 0, 0)) + STATS_PROPERTY(QVector3D, parabolaPicksUpdated, QVector3D(0, 0, 0)) + STATS_PROPERTY(QVector3D, collisionPicksUpdated, QVector3D(0, 0, 0)) public: static Stats* getInstance(); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp deleted file mode 100644 index eb43e8cf45..0000000000 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ /dev/null @@ -1,392 +0,0 @@ -// -// Base3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Base3DOverlay.h" - -#include -#include -#include "Application.h" - - -const bool DEFAULT_IS_SOLID = false; -const bool DEFAULT_IS_DASHED_LINE = false; - -Base3DOverlay::Base3DOverlay() : - SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), - _isSolid(DEFAULT_IS_SOLID), - _isDashedLine(DEFAULT_IS_DASHED_LINE), - _ignorePickIntersection(false), - _drawInFront(false), - _drawHUDLayer(false) -{ - // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here - // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. - _queryAACubeSet = true; -} - -Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : - Overlay(base3DOverlay), - SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), - _isSolid(base3DOverlay->_isSolid), - _isDashedLine(base3DOverlay->_isDashedLine), - _ignorePickIntersection(base3DOverlay->_ignorePickIntersection), - _drawInFront(base3DOverlay->_drawInFront), - _drawHUDLayer(base3DOverlay->_drawHUDLayer), - _isGrabbable(base3DOverlay->_isGrabbable), - _isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera) -{ - setTransform(base3DOverlay->getTransform()); - // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here - // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. - _queryAACubeSet = true; -} - -QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties, bool scalesWithParent) { - // the position and rotation in _transform are relative to the parent (aka local). The versions coming from - // scripts are in world-frame, unless localPosition or localRotation are used. Patch up the properties - // so that "position" and "rotation" are relative-to-parent values. - QVariantMap result = properties; - QUuid parentID = result["parentID"].isValid() ? QUuid(result["parentID"].toString()) : QUuid(); - int parentJointIndex = result["parentJointIndex"].isValid() ? result["parentJointIndex"].toInt() : -1; - bool success; - - // make "position" and "orientation" be relative-to-parent - if (result["localPosition"].isValid()) { - result["position"] = result["localPosition"]; - } else if (result["position"].isValid()) { - glm::vec3 localPosition = SpatiallyNestable::worldToLocal(vec3FromVariant(result["position"]), - parentID, parentJointIndex, scalesWithParent, success); - if (success) { - result["position"] = vec3toVariant(localPosition); - } - } - - if (result["localOrientation"].isValid()) { - result["orientation"] = result["localOrientation"]; - } else if (result["orientation"].isValid()) { - glm::quat localOrientation = SpatiallyNestable::worldToLocal(quatFromVariant(result["orientation"]), - parentID, parentJointIndex, scalesWithParent, success); - if (success) { - result["orientation"] = quatToVariant(localOrientation); - } - } - - return result; -} - -void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { - QVariantMap properties = originalProperties; - - if (properties["name"].isValid()) { - setName(properties["name"].toString()); - } - - // carry over some legacy keys - if (!properties["position"].isValid() && !properties["localPosition"].isValid()) { - if (properties["p1"].isValid()) { - properties["position"] = properties["p1"]; - } else if (properties["point"].isValid()) { - properties["position"] = properties["point"]; - } else if (properties["start"].isValid()) { - properties["position"] = properties["start"]; - } - } - if (!properties["orientation"].isValid() && properties["rotation"].isValid()) { - properties["orientation"] = properties["rotation"]; - } - if (!properties["localOrientation"].isValid() && properties["localRotation"].isValid()) { - properties["localOrientation"] = properties["localRotation"]; - } - - // All of parentID, parentJointIndex, position, orientation are needed to make sense of any of them. - // If any of these changed, pull any missing properties from the overlay. - if (properties["parentID"].isValid() || properties["parentJointIndex"].isValid() || - properties["position"].isValid() || properties["localPosition"].isValid() || - properties["orientation"].isValid() || properties["localOrientation"].isValid()) { - if (!properties["parentID"].isValid()) { - properties["parentID"] = getParentID(); - } - if (!properties["parentJointIndex"].isValid()) { - properties["parentJointIndex"] = getParentJointIndex(); - } - if (!properties["position"].isValid() && !properties["localPosition"].isValid()) { - properties["position"] = vec3toVariant(getWorldPosition()); - } - if (!properties["orientation"].isValid() && !properties["localOrientation"].isValid()) { - properties["orientation"] = quatToVariant(getWorldOrientation()); - } - } - - properties = convertOverlayLocationFromScriptSemantics(properties, getScalesWithParent()); - Overlay::setProperties(properties); - - bool needRenderItemUpdate = false; - - auto drawInFront = properties["drawInFront"]; - if (drawInFront.isValid()) { - bool value = drawInFront.toBool(); - setDrawInFront(value); - needRenderItemUpdate = true; - } - - auto drawHUDLayer = properties["drawHUDLayer"]; - if (drawHUDLayer.isValid()) { - bool value = drawHUDLayer.toBool(); - setDrawHUDLayer(value); - needRenderItemUpdate = true; - } - - auto isGrabbable = properties["grabbable"]; - if (isGrabbable.isValid()) { - setIsGrabbable(isGrabbable.toBool()); - } - - auto isVisibleInSecondaryCamera = properties["isVisibleInSecondaryCamera"]; - if (isVisibleInSecondaryCamera.isValid()) { - bool value = isVisibleInSecondaryCamera.toBool(); - setIsVisibleInSecondaryCamera(value); - needRenderItemUpdate = true; - } - - if (properties["position"].isValid()) { - setLocalPosition(vec3FromVariant(properties["position"])); - needRenderItemUpdate = true; - } - if (properties["orientation"].isValid()) { - setLocalOrientation(quatFromVariant(properties["orientation"])); - needRenderItemUpdate = true; - } - if (properties["isSolid"].isValid()) { - setIsSolid(properties["isSolid"].toBool()); - } - if (properties["isFilled"].isValid()) { - setIsSolid(properties["isSolid"].toBool()); - } - if (properties["isWire"].isValid()) { - setIsSolid(!properties["isWire"].toBool()); - } - if (properties["solid"].isValid()) { - setIsSolid(properties["solid"].toBool()); - } - if (properties["filled"].isValid()) { - setIsSolid(properties["filled"].toBool()); - } - if (properties["wire"].isValid()) { - setIsSolid(!properties["wire"].toBool()); - } - - if (properties["isDashedLine"].isValid()) { - qDebug() << "isDashed is deprecated and will be removed in RC79!"; - setIsDashedLine(properties["isDashedLine"].toBool()); - } - if (properties["dashed"].isValid()) { - qDebug() << "dashed is deprecated and will be removed in RC79!"; - setIsDashedLine(properties["dashed"].toBool()); - } - if (properties["ignorePickIntersection"].isValid()) { - setIgnorePickIntersection(properties["ignorePickIntersection"].toBool()); - } else if (properties["ignoreRayIntersection"].isValid()) { - setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool()); - } - - if (properties["parentID"].isValid()) { - setParentID(QUuid(properties["parentID"].toString())); - needRenderItemUpdate = true; - } - if (properties["parentJointIndex"].isValid()) { - setParentJointIndex(properties["parentJointIndex"].toInt()); - needRenderItemUpdate = true; - } - - // Communicate changes to the renderItem if needed - if (needRenderItemUpdate) { - auto itemID = getRenderItemID(); - if (render::Item::isValidID(itemID)) { - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - transaction.updateItem(itemID); - scene->enqueueTransaction(transaction); - } - _queryAACubeSet = true; // HACK: just in case some SpatiallyNestable code accidentally set it false - } -} - -// JSDoc for copying to @typedefs of overlay types that inherit Base3DOverlay. -/**jsdoc - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {boolean} isVisibleInSecondaryCamera=false - If true, the overlay is rendered in secondary - * camera views. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - */ -QVariant Base3DOverlay::getProperty(const QString& property) { - if (property == "name") { - return _nameLock.resultWithReadLock([&] { - return _name; - }); - } - if (property == "position" || property == "start" || property == "p1" || property == "point") { - return vec3toVariant(getWorldPosition()); - } - if (property == "localPosition") { - return vec3toVariant(getLocalPosition()); - } - if (property == "rotation" || property == "orientation") { - return quatToVariant(getWorldOrientation()); - } - if (property == "localRotation" || property == "localOrientation") { - return quatToVariant(getLocalOrientation()); - } - if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled") { - return _isSolid; - } - if (property == "isWire" || property == "wire") { - return !_isSolid; - } - if (property == "isDashedLine" || property == "dashed") { - qDebug() << "isDashedLine/dashed are deprecated and will be removed in RC79!"; - return _isDashedLine; - } - if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") { - return _ignorePickIntersection; - } - if (property == "drawInFront") { - return _drawInFront; - } - if (property == "grabbable") { - return _isGrabbable; - } - if (property == "isVisibleInSecondaryCamera") { - return _isVisibleInSecondaryCamera; - } - if (property == "parentID") { - return getParentID(); - } - if (property == "parentJointIndex") { - return getParentJointIndex(); - } - - return Overlay::getProperty(property); -} - -void Base3DOverlay::locationChanged(bool tellPhysics) { - SpatiallyNestable::locationChanged(tellPhysics); - - // Force the actual update of the render transform through the notify call - notifyRenderVariableChange(); -} - -// FIXME: Overlays shouldn't be deleted when their parents are -void Base3DOverlay::parentDeleted() { - qApp->getOverlays().deleteOverlay(getOverlayID()); -} - -void Base3DOverlay::update(float duration) { - // In Base3DOverlay, if its location or bound changed, the renderTrasnformDirty flag is true. - // then the correct transform used for rendering is computed in the update transaction and assigned. - if (_renderVariableDirty) { - auto itemID = getRenderItemID(); - if (render::Item::isValidID(itemID)) { - // Capture the render transform value in game loop before - auto latestTransform = evalRenderTransform(); - bool latestVisible = getVisible(); - _renderVariableDirty = false; - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - transaction.updateItem(itemID, [latestTransform, latestVisible](Overlay& data) { - auto overlay3D = dynamic_cast(&data); - if (overlay3D) { - // TODO: overlays need to communicate all relavent render properties through transactions - overlay3D->setRenderTransform(latestTransform); - overlay3D->setRenderVisible(latestVisible); - } - }); - scene->enqueueTransaction(transaction); - } - } -} - -void Base3DOverlay::notifyRenderVariableChange() const { - _renderVariableDirty = true; -} - -Transform Base3DOverlay::evalRenderTransform() { - return getTransform(); -} - -void Base3DOverlay::setRenderTransform(const Transform& transform) { - _renderTransform = transform; -} - -void Base3DOverlay::setRenderVisible(bool visible) { - _renderVisible = visible; -} - -SpatialParentTree* Base3DOverlay::getParentTree() const { - auto entityTreeRenderer = qApp->getEntities(); - EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; - return entityTree.get(); -} - -void Base3DOverlay::setVisible(bool visible) { - Parent::setVisible(visible); - notifyRenderVariableChange(); -} - -QString Base3DOverlay::getName() const { - return _nameLock.resultWithReadLock([&] { - return QString("Overlay:") + _name; - }); -} - -void Base3DOverlay::setName(QString name) { - _nameLock.withWriteLock([&] { - _name = name; - }); -} - - - -render::ItemKey Base3DOverlay::getKey() { - auto builder = render::ItemKey::Builder(Overlay::getKey()); - - if (getDrawInFront()) { - builder.withLayer(render::hifi::LAYER_3D_FRONT); - } else if (getDrawHUDLayer()) { - builder.withLayer(render::hifi::LAYER_3D_HUD); - } else { - builder.withoutLayer(); - } - - builder.withoutViewSpace(); - - if (isTransparent()) { - builder.withTransparent(); - } - - return builder.build(); -} diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h deleted file mode 100644 index daf15e676f..0000000000 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ /dev/null @@ -1,112 +0,0 @@ -// -// Base3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Base3DOverlay_h -#define hifi_Base3DOverlay_h - -#include -#include -#include -#include "Overlay.h" - -class Base3DOverlay : public Overlay, public SpatiallyNestable, public scriptable::ModelProvider { - Q_OBJECT - using Parent = Overlay; - -public: - Base3DOverlay(); - Base3DOverlay(const Base3DOverlay* base3DOverlay); - - void setVisible(bool visible) override; - bool queryAACubeNeedsUpdate() const override { return false; } // HACK: queryAACube not relevant for Overlays - - virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } - void setOverlayID(OverlayID overlayID) override { setID(overlayID); } - - virtual QString getName() const override; - void setName(QString name); - - // getters - virtual bool is3D() const override { return true; } - - virtual render::ItemKey getKey() override; - virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } - virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } - - // TODO: consider implementing registration points in this class - glm::vec3 getCenter() const { return getWorldPosition(); } - - bool getIsSolid() const { return _isSolid; } - bool getIsDashedLine() const { return _isDashedLine; } - bool getIsSolidLine() const { return !_isDashedLine; } - bool getIgnorePickIntersection() const { return _ignorePickIntersection; } - bool getDrawInFront() const { return _drawInFront; } - bool getDrawHUDLayer() const { return _drawHUDLayer; } - bool getIsGrabbable() const { return _isGrabbable; } - virtual bool getIsVisibleInSecondaryCamera() const override { return _isVisibleInSecondaryCamera; } - - void setIsSolid(bool isSolid) { _isSolid = isSolid; } - void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } - void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; } - virtual void setDrawInFront(bool value) { _drawInFront = value; } - virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } - void setIsGrabbable(bool value) { _isGrabbable = value; } - virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } - - void update(float deltatime) override; - - void notifyRenderVariableChange() const; - - virtual void setProperties(const QVariantMap& properties) override; - virtual QVariant getProperty(const QString& property) override; - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } - - virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { - return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking); - } - - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } - - virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { - return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking); - } - - virtual SpatialParentTree* getParentTree() const override; - -protected: - virtual void locationChanged(bool tellPhysics = true) override; - virtual void parentDeleted() override; - - mutable Transform _renderTransform; - mutable bool _renderVisible; - virtual Transform evalRenderTransform(); - virtual void setRenderTransform(const Transform& transform); - void setRenderVisible(bool visible); - const Transform& getRenderTransform() const { return _renderTransform; } - - bool _isSolid; - bool _isDashedLine; - bool _ignorePickIntersection; - bool _drawInFront; - bool _drawHUDLayer; - bool _isGrabbable { false }; - bool _isVisibleInSecondaryCamera { false }; - mutable bool _renderVariableDirty { true }; - - QString _name; - mutable ReadWriteLockable _nameLock; -}; - -#endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Billboard3DOverlay.cpp b/interface/src/ui/overlays/Billboard3DOverlay.cpp deleted file mode 100644 index ecade70ef8..0000000000 --- a/interface/src/ui/overlays/Billboard3DOverlay.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// -// Billboard3DOverlay.cpp -// hifi/interface/src/ui/overlays -// -// Created by Zander Otavka on 8/4/15. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Billboard3DOverlay.h" - -Billboard3DOverlay::Billboard3DOverlay(const Billboard3DOverlay* billboard3DOverlay) : - Planar3DOverlay(billboard3DOverlay), - PanelAttachable(*billboard3DOverlay), - Billboardable(*billboard3DOverlay) -{ -} - -void Billboard3DOverlay::setProperties(const QVariantMap& properties) { - Planar3DOverlay::setProperties(properties); - PanelAttachable::setProperties(properties); - Billboardable::setProperties(properties); -} - -QVariant Billboard3DOverlay::getProperty(const QString &property) { - QVariant value; - value = Billboardable::getProperty(property); - if (value.isValid()) { - return value; - } - value = PanelAttachable::getProperty(property); - if (value.isValid()) { - return value; - } - return Planar3DOverlay::getProperty(property); -} - -bool Billboard3DOverlay::applyTransformTo(Transform& transform, bool force) { - bool transformChanged = false; - if (force || usecTimestampNow() > _transformExpiry) { - transformChanged = PanelAttachable::applyTransformTo(transform, true); - transformChanged |= pointTransformAtCamera(transform, getOffsetRotation()); - } - return transformChanged; -} - -void Billboard3DOverlay::update(float duration) { - if (isFacingAvatar()) { - _renderVariableDirty = true; - } - Parent::update(duration); -} - -Transform Billboard3DOverlay::evalRenderTransform() { - Transform transform = getTransform(); - bool transformChanged = applyTransformTo(transform, true); - // If the transform is not modified, setting the transform to - // itself will cause drift over time due to floating point errors. - if (transformChanged) { - setTransform(transform); - } - return transform; -} \ No newline at end of file diff --git a/interface/src/ui/overlays/Billboard3DOverlay.h b/interface/src/ui/overlays/Billboard3DOverlay.h deleted file mode 100644 index 174bc23bc8..0000000000 --- a/interface/src/ui/overlays/Billboard3DOverlay.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Billboard3DOverlay.h -// hifi/interface/src/ui/overlays -// -// Created by Zander Otavka on 8/4/15. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Billboard3DOverlay_h -#define hifi_Billboard3DOverlay_h - -#include "Planar3DOverlay.h" -#include "PanelAttachable.h" -#include "Billboardable.h" - -class Billboard3DOverlay : public Planar3DOverlay, public PanelAttachable, public Billboardable { - Q_OBJECT - using Parent = Planar3DOverlay; - -public: - Billboard3DOverlay() {} - Billboard3DOverlay(const Billboard3DOverlay* billboard3DOverlay); - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - void update(float duration) override; - -protected: - virtual bool applyTransformTo(Transform& transform, bool force = false) override; - - Transform evalRenderTransform() override; -}; - -#endif // hifi_Billboard3DOverlay_h diff --git a/interface/src/ui/overlays/Billboardable.cpp b/interface/src/ui/overlays/Billboardable.cpp deleted file mode 100644 index a125956b5a..0000000000 --- a/interface/src/ui/overlays/Billboardable.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Billboardable.cpp -// interface/src/ui/overlays -// -// Created by Zander Otavka on 8/7/15. -// Modified by Daniela Fontes on 24/10/17. -// 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 "Billboardable.h" - -#include -#include -#include "avatar/AvatarManager.h" - -void Billboardable::setProperties(const QVariantMap& properties) { - auto isFacingAvatar = properties["isFacingAvatar"]; - if (isFacingAvatar.isValid()) { - setIsFacingAvatar(isFacingAvatar.toBool()); - } -} - -// JSDoc for copying to @typedefs of overlay types that inherit Billboardable. -/**jsdoc - * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis - * parallel to the user's avatar's "up" direction. - */ -QVariant Billboardable::getProperty(const QString &property) { - if (property == "isFacingAvatar") { - return isFacingAvatar(); - } - return QVariant(); -} - -bool Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offsetRotation) { - if (isFacingAvatar()) { - glm::vec3 billboardPos = transform.getTranslation(); - glm::vec3 cameraPos = qApp->getCamera().getPosition(); - // use the referencial from the avatar, y isn't always up - glm::vec3 avatarUP = DependencyManager::get()->getMyAvatar()->getWorldOrientation()*Vectors::UP; - // check to see if glm::lookAt will work / using glm::lookAt variable name - glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); - - // make sure s is not NaN for any component - if(glm::length2(s) > 0.0f) { - glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); - transform.setRotation(rotation); - transform.postRotate(offsetRotation); - return true; - } - } - return false; -} diff --git a/interface/src/ui/overlays/Billboardable.h b/interface/src/ui/overlays/Billboardable.h deleted file mode 100644 index 46d9ac6479..0000000000 --- a/interface/src/ui/overlays/Billboardable.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Billboardable.h -// interface/src/ui/overlays -// -// Created by Zander Otavka on 8/7/15. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Billboardable_h -#define hifi_Billboardable_h - -#include -#include - -class QString; -class Transform; - -class Billboardable { -public: - bool isFacingAvatar() const { return _isFacingAvatar; } - void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; } - -protected: - void setProperties(const QVariantMap& properties); - QVariant getProperty(const QString& property); - - bool pointTransformAtCamera(Transform& transform, glm::quat offsetRotation = {1, 0, 0, 0}); - -private: - bool _isFacingAvatar = false; -}; - -#endif // hifi_Billboardable_h diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp deleted file mode 100644 index 1a7d3228c7..0000000000 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ /dev/null @@ -1,589 +0,0 @@ -// -// Circle3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Circle3DOverlay.h" - -#include -#include -#include - -QString const Circle3DOverlay::TYPE = "circle3d"; - -Circle3DOverlay::Circle3DOverlay() {} - -Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : - Planar3DOverlay(circle3DOverlay), - _startAt(circle3DOverlay->_startAt), - _endAt(circle3DOverlay->_endAt), - _outerRadius(circle3DOverlay->_outerRadius), - _innerRadius(circle3DOverlay->_innerRadius), - _innerStartColor(circle3DOverlay->_innerStartColor), - _innerEndColor(circle3DOverlay->_innerEndColor), - _outerStartColor(circle3DOverlay->_outerStartColor), - _outerEndColor(circle3DOverlay->_outerEndColor), - _innerStartAlpha(circle3DOverlay->_innerStartAlpha), - _innerEndAlpha(circle3DOverlay->_innerEndAlpha), - _outerStartAlpha(circle3DOverlay->_outerStartAlpha), - _outerEndAlpha(circle3DOverlay->_outerEndAlpha), - _hasTickMarks(circle3DOverlay->_hasTickMarks), - _majorTickMarksAngle(circle3DOverlay->_majorTickMarksAngle), - _minorTickMarksAngle(circle3DOverlay->_minorTickMarksAngle), - _majorTickMarksLength(circle3DOverlay->_majorTickMarksLength), - _minorTickMarksLength(circle3DOverlay->_minorTickMarksLength), - _majorTickMarksColor(circle3DOverlay->_majorTickMarksColor), - _minorTickMarksColor(circle3DOverlay->_minorTickMarksColor) -{ -} - -Circle3DOverlay::~Circle3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - if (_quadVerticesID) { - geometryCache->releaseID(_quadVerticesID); - } - if (_lineVerticesID) { - geometryCache->releaseID(_lineVerticesID); - } - if (_majorTicksVerticesID) { - geometryCache->releaseID(_majorTicksVerticesID); - } - if (_minorTicksVerticesID) { - geometryCache->releaseID(_minorTicksVerticesID); - } - } -} -void Circle3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - if (alpha == 0.0f) { - return; // do nothing if our alpha is 0, we're not visible - } - - bool geometryChanged = _dirty; - _dirty = false; - - const float FULL_CIRCLE = 360.0f; - const float SLICES = 180.0f; // The amount of segment to create the circle - const float SLICE_ANGLE_RADIANS = glm::radians(FULL_CIRCLE / SLICES); - - auto geometryCache = DependencyManager::get(); - - Q_ASSERT(args->_batch); - auto& batch = *args->_batch; - - DependencyManager::get()->bindSimpleProgram(batch, false, isTransparent(), false, !getIsSolid(), true); - - batch.setModelTransform(getRenderTransform()); - - // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise - // we just draw a line... - if (getIsSolid()) { - if (!_quadVerticesID) { - _quadVerticesID = geometryCache->allocateID(); - } - - if (geometryChanged) { - QVector points; - QVector colors; - - float pulseLevel = updatePulse(); - vec4 pulseModifier = vec4(1); - if (_alphaPulse != 0.0f) { - pulseModifier.a = (_alphaPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); - } - if (_colorPulse != 0.0f) { - float pulseValue = (_colorPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); - pulseModifier = vec4(vec3(pulseValue), pulseModifier.a); - } - vec4 innerStartColor = vec4(toGlm(_innerStartColor), _innerStartAlpha) * pulseModifier; - vec4 outerStartColor = vec4(toGlm(_outerStartColor), _outerStartAlpha) * pulseModifier; - vec4 innerEndColor = vec4(toGlm(_innerEndColor), _innerEndAlpha) * pulseModifier; - vec4 outerEndColor = vec4(toGlm(_outerEndColor), _outerEndAlpha) * pulseModifier; - - const auto startAtRadians = glm::radians(_startAt); - const auto endAtRadians = glm::radians(_endAt); - - const auto totalRange = _endAt - _startAt; - if (_innerRadius <= 0) { - _solidPrimitive = gpu::TRIANGLE_FAN; - points << vec2(); - colors << innerStartColor; - for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) { - float range = (angleRadians - startAtRadians) / totalRange; - points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - colors << glm::mix(outerStartColor, outerEndColor, range); - } - points << glm::vec2(cosf(endAtRadians) * _outerRadius, sinf(endAtRadians) * _outerRadius); - colors << outerEndColor; - - } else { - _solidPrimitive = gpu::TRIANGLE_STRIP; - for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) { - float range = (angleRadians - startAtRadians) / totalRange; - - points << glm::vec2(cosf(angleRadians) * _innerRadius, sinf(angleRadians) * _innerRadius); - colors << glm::mix(innerStartColor, innerEndColor, range); - - points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - colors << glm::mix(outerStartColor, outerEndColor, range); - } - points << glm::vec2(cosf(endAtRadians) * _innerRadius, sinf(endAtRadians) * _innerRadius); - colors << innerEndColor; - - points << glm::vec2(cosf(endAtRadians) * _outerRadius, sinf(endAtRadians) * _outerRadius); - colors << outerEndColor; - } - geometryCache->updateVertices(_quadVerticesID, points, colors); - } - - geometryCache->renderVertices(batch, _solidPrimitive, _quadVerticesID); - - } else { - if (!_lineVerticesID) { - _lineVerticesID = geometryCache->allocateID(); - } - - if (geometryChanged) { - QVector points; - - const auto startAtRadians = glm::radians(_startAt); - const auto endAtRadians = glm::radians(_endAt); - - float angleRadians = startAtRadians; - glm::vec2 firstPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - points << firstPoint; - - while (angleRadians < endAtRadians) { - angleRadians += SLICE_ANGLE_RADIANS; - glm::vec2 thisPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - points << thisPoint; - - if (getIsDashedLine()) { - angleRadians += SLICE_ANGLE_RADIANS / 2.0f; // short gap - glm::vec2 dashStartPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - points << dashStartPoint; - } - } - - // get the last slice portion.... - angleRadians = endAtRadians; - glm::vec2 lastPoint(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); - points << lastPoint; - geometryCache->updateVertices(_lineVerticesID, points, vec4(toGlm(getColor()), getAlpha())); - } - - if (getIsDashedLine()) { - geometryCache->renderVertices(batch, gpu::LINES, _lineVerticesID); - } else { - geometryCache->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID); - } - } - - // draw our tick marks - // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise - // we just draw a line... - if (getHasTickMarks()) { - if (!_majorTicksVerticesID) { - _majorTicksVerticesID = geometryCache->allocateID(); - } - if (!_minorTicksVerticesID) { - _minorTicksVerticesID = geometryCache->allocateID(); - } - - if (geometryChanged) { - QVector majorPoints; - QVector minorPoints; - - // draw our major tick marks - if (getMajorTickMarksAngle() > 0.0f && getMajorTickMarksLength() != 0.0f) { - - float tickMarkAngle = getMajorTickMarksAngle(); - float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; - float tickMarkLength = getMajorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; - float endRadius = startRadius + tickMarkLength; - - while (angle <= _endAt) { - float angleInRadians = glm::radians(angle); - - glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); - glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); - - majorPoints << thisPointA << thisPointB; - - angle += tickMarkAngle; - } - } - - // draw our minor tick marks - if (getMinorTickMarksAngle() > 0.0f && getMinorTickMarksLength() != 0.0f) { - - float tickMarkAngle = getMinorTickMarksAngle(); - float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; - float tickMarkLength = getMinorTickMarksLength(); - float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; - float endRadius = startRadius + tickMarkLength; - - while (angle <= _endAt) { - float angleInRadians = glm::radians(angle); - - glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); - glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); - - minorPoints << thisPointA << thisPointB; - - angle += tickMarkAngle; - } - } - - glm::vec4 majorColor(toGlm(getMajorTickMarksColor()), alpha); - geometryCache->updateVertices(_majorTicksVerticesID, majorPoints, majorColor); - glm::vec4 minorColor(toGlm(getMinorTickMarksColor()), alpha); - geometryCache->updateVertices(_minorTicksVerticesID, minorPoints, minorColor); - } - - geometryCache->renderVertices(batch, gpu::LINES, _majorTicksVerticesID); - - geometryCache->renderVertices(batch, gpu::LINES, _minorTicksVerticesID); - } -} - -const render::ShapeKey Circle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace(); - if (isTransparent()) { - builder.withTranslucent(); - } - if (!getIsSolid()) { - builder.withUnlit().withDepthBias(); - } - return builder.build(); -} - -template T fromVariant(const QVariant& v, bool& valid) { - valid = v.isValid(); - return qvariant_cast(v); -} - -template<> glm::u8vec3 fromVariant(const QVariant& v, bool& valid) { - return u8vec3FromVariant(v, valid); -} - -template -bool updateIfValid(const QVariantMap& properties, const char* key, T& output) { - bool valid; - T result = fromVariant(properties[key], valid); - if (!valid) { - return false; - } - - // Don't signal updates if the value was already set - if (result == output) { - return false; - } - - output = result; - return true; -} - -// Multicast, many outputs -template -bool updateIfValid(const QVariantMap& properties, const char* key, std::initializer_list> outputs) { - bool valid; - T value = fromVariant(properties[key], valid); - if (!valid) { - return false; - } - bool updated = false; - for (T& output : outputs) { - if (output != value) { - output = value; - updated = true; - } - } - return updated; -} - -// Multicast, multiple possible inputs, in order of preference -template -bool updateIfValid(const QVariantMap& properties, const std::initializer_list keys, T& output) { - for (const char* key : keys) { - if (updateIfValid(properties, key, output)) { - return true; - } - } - return false; -} - - -void Circle3DOverlay::setProperties(const QVariantMap& properties) { - Planar3DOverlay::setProperties(properties); - _dirty |= updateIfValid(properties, "alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); - _dirty |= updateIfValid(properties, "Alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); - _dirty |= updateIfValid(properties, "startAlpha", { _innerStartAlpha, _outerStartAlpha }); - _dirty |= updateIfValid(properties, "endAlpha", { _innerEndAlpha, _outerEndAlpha }); - _dirty |= updateIfValid(properties, "innerAlpha", { _innerStartAlpha, _innerEndAlpha }); - _dirty |= updateIfValid(properties, "outerAlpha", { _outerStartAlpha, _outerEndAlpha }); - _dirty |= updateIfValid(properties, "innerStartAlpha", _innerStartAlpha); - _dirty |= updateIfValid(properties, "innerEndAlpha", _innerEndAlpha); - _dirty |= updateIfValid(properties, "outerStartAlpha", _outerStartAlpha); - _dirty |= updateIfValid(properties, "outerEndAlpha", _outerEndAlpha); - - _dirty |= updateIfValid(properties, "color", { _innerStartColor, _innerEndColor, _outerStartColor, _outerEndColor }); - _dirty |= updateIfValid(properties, "startColor", { _innerStartColor, _outerStartColor } ); - _dirty |= updateIfValid(properties, "endColor", { _innerEndColor, _outerEndColor } ); - _dirty |= updateIfValid(properties, "innerColor", { _innerStartColor, _innerEndColor } ); - _dirty |= updateIfValid(properties, "outerColor", { _outerStartColor, _outerEndColor } ); - _dirty |= updateIfValid(properties, "innerStartColor", _innerStartColor); - _dirty |= updateIfValid(properties, "innerEndColor", _innerEndColor); - _dirty |= updateIfValid(properties, "outerStartColor", _outerStartColor); - _dirty |= updateIfValid(properties, "outerEndColor", _outerEndColor); - - _dirty |= updateIfValid(properties, "startAt", _startAt); - _dirty |= updateIfValid(properties, "endAt", _endAt); - - _dirty |= updateIfValid(properties, { "radius", "outerRadius" }, _outerRadius); - _dirty |= updateIfValid(properties, "innerRadius", _innerRadius); - _dirty |= updateIfValid(properties, "hasTickMarks", _hasTickMarks); - _dirty |= updateIfValid(properties, "majorTickMarksAngle", _majorTickMarksAngle); - _dirty |= updateIfValid(properties, "minorTickMarksAngle", _minorTickMarksAngle); - _dirty |= updateIfValid(properties, "majorTickMarksLength", _majorTickMarksLength); - _dirty |= updateIfValid(properties, "minorTickMarksLength", _minorTickMarksLength); - _dirty |= updateIfValid(properties, "majorTickMarksColor", _majorTickMarksColor); - _dirty |= updateIfValid(properties, "minorTickMarksColor", _minorTickMarksColor); - - if (_innerStartAlpha < 1.0f || _innerEndAlpha < 1.0f || _outerStartAlpha < 1.0f || _outerEndAlpha < 1.0f) { - // Force the alpha to 0.5, since we'll ignore it in the presence of these other values, but we need - // it to be non-1 in order to get the right pipeline and non-0 in order to render at all. - _alpha = 0.5f; - } -} - -// Overlay's color and alpha properties are overridden. And the dimensions property is not used. -/**jsdoc - * These are the properties of a circle3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Circle3DProperties - * - * @property {string} type=circle3d - Has the value "circle3d". Read-only. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - * Not used. - * - * @property {number} startAt=0 - The counter-clockwise angle from the overlay's x-axis that drawing starts at, in degrees. - * @property {number} endAt=360 - The counter-clockwise angle from the overlay's x-axis that drawing ends at, in degrees. - * @property {number} outerRadius=1 - The outer radius of the overlay, in meters. Synonym: radius. - * @property {number} innerRadius=0 - The inner radius of the overlay, in meters. - * @property {Color} color=255,255,255 - The color of the overlay. Setting this value also sets the values of - * innerStartColor, innerEndColor, outerStartColor, and outerEndColor. - * @property {Color} startColor - Sets the values of innerStartColor and outerStartColor. - * Write-only. - * @property {Color} endColor - Sets the values of innerEndColor and outerEndColor. - * Write-only. - * @property {Color} innerColor - Sets the values of innerStartColor and innerEndColor. - * Write-only. - * @property {Color} outerColor - Sets the values of outerStartColor and outerEndColor. - * Write-only. - * @property {Color} innerStartcolor - The color at the inner start point of the overlay. - * @property {Color} innerEndColor - The color at the inner end point of the overlay. - * @property {Color} outerStartColor - The color at the outer start point of the overlay. - * @property {Color} outerEndColor - The color at the outer end point of the overlay. - * @property {number} alpha=0.5 - The opacity of the overlay, 0.0 - 1.0. Setting this value also sets - * the values of innerStartAlpha, innerEndAlpha, outerStartAlpha, and - * outerEndAlpha. Synonym: Alpha; write-only. - * @property {number} startAlpha - Sets the values of innerStartAlpha and outerStartAlpha. - * Write-only. - * @property {number} endAlpha - Sets the values of innerEndAlpha and outerEndAlpha. - * Write-only. - * @property {number} innerAlpha - Sets the values of innerStartAlpha and innerEndAlpha. - * Write-only. - * @property {number} outerAlpha - Sets the values of outerStartAlpha and outerEndAlpha. - * Write-only. - * @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. - * @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. - * @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. - * @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. - - * @property {boolean} hasTickMarks=false - If true, tick marks are drawn. - * @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees. - * @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees. - * @property {number} majorTickMarksLength=0 - The length of the major tick marks, in meters. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {number} minorTickMarksLength=0 - The length of the minor tick marks, in meters. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {Color} majorTickMarksColor=0,0,0 - The color of the major tick marks. - * @property {Color} minorTickMarksColor=0,0,0 - The color of the minor tick marks. - */ -QVariant Circle3DOverlay::getProperty(const QString& property) { - if (property == "startAt") { - return _startAt; - } - if (property == "endAt") { - return _endAt; - } - if (property == "radius") { - return _outerRadius; - } - if (property == "outerRadius") { - return _outerRadius; - } - if (property == "innerRadius") { - return _innerRadius; - } - if (property == "innerStartColor") { - return u8vec3ColortoVariant(_innerStartColor); - } - if (property == "innerEndColor") { - return u8vec3ColortoVariant(_innerEndColor); - } - if (property == "outerStartColor") { - return u8vec3ColortoVariant(_outerStartColor); - } - if (property == "outerEndColor") { - return u8vec3ColortoVariant(_outerEndColor); - } - if (property == "innerStartAlpha") { - return _innerStartAlpha; - } - if (property == "innerEndAlpha") { - return _innerEndAlpha; - } - if (property == "outerStartAlpha") { - return _outerStartAlpha; - } - if (property == "outerEndAlpha") { - return _outerEndAlpha; - } - if (property == "hasTickMarks") { - return _hasTickMarks; - } - if (property == "majorTickMarksAngle") { - return _majorTickMarksAngle; - } - if (property == "minorTickMarksAngle") { - return _minorTickMarksAngle; - } - if (property == "majorTickMarksLength") { - return _majorTickMarksLength; - } - if (property == "minorTickMarksLength") { - return _minorTickMarksLength; - } - if (property == "majorTickMarksColor") { - return u8vec3ColortoVariant(_majorTickMarksColor); - } - if (property == "minorTickMarksColor") { - return u8vec3ColortoVariant(_minorTickMarksColor); - } - - return Planar3DOverlay::getProperty(property); -} - -bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // Scale the dimensions by the diameter - glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); - glm::quat rotation = getWorldOrientation(); - - if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) { - glm::vec3 hitPosition = origin + (distance * direction); - glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition()); - localHitPosition.x /= getDimensions().x; - localHitPosition.y /= getDimensions().y; - float distanceToHit = glm::length(localHitPosition); - - if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } - } - - return false; -} - -bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // Scale the dimensions by the diameter - glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { - glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance; - localHitPosition.x /= getDimensions().x; - localHitPosition.y /= getDimensions().y; - float distanceToHit = glm::length(localHitPosition); - - if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } - } - - return false; -} - -Circle3DOverlay* Circle3DOverlay::createClone() const { - return new Circle3DOverlay(this); -} diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h deleted file mode 100644 index ca5e05a53b..0000000000 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ /dev/null @@ -1,96 +0,0 @@ -// -// Circle3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Circle3DOverlay_h -#define hifi_Circle3DOverlay_h - -// include this before QGLWidget, which includes an earlier version of OpenGL -#include "Planar3DOverlay.h" - -class Circle3DOverlay : public Planar3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Circle3DOverlay(); - Circle3DOverlay(const Circle3DOverlay* circle3DOverlay); - ~Circle3DOverlay(); - - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - float getStartAt() const { return _startAt; } - float getEndAt() const { return _endAt; } - float getOuterRadius() const { return _outerRadius; } - float getInnerRadius() const { return _innerRadius; } - bool getHasTickMarks() const { return _hasTickMarks; } - float getMajorTickMarksAngle() const { return _majorTickMarksAngle; } - float getMinorTickMarksAngle() const { return _minorTickMarksAngle; } - float getMajorTickMarksLength() const { return _majorTickMarksLength; } - float getMinorTickMarksLength() const { return _minorTickMarksLength; } - glm::u8vec3 getMajorTickMarksColor() const { return _majorTickMarksColor; } - glm::u8vec3 getMinorTickMarksColor() const { return _minorTickMarksColor; } - - void setStartAt(float value) { _startAt = value; } - void setEndAt(float value) { _endAt = value; } - void setOuterRadius(float value) { _outerRadius = value; } - void setInnerRadius(float value) { _innerRadius = value; } - void setHasTickMarks(bool value) { _hasTickMarks = value; } - void setMajorTickMarksAngle(float value) { _majorTickMarksAngle = value; } - void setMinorTickMarksAngle(float value) { _minorTickMarksAngle = value; } - void setMajorTickMarksLength(float value) { _majorTickMarksLength = value; } - void setMinorTickMarksLength(float value) { _minorTickMarksLength = value; } - void setMajorTickMarksColor(const glm::u8vec3& value) { _majorTickMarksColor = value; } - void setMinorTickMarksColor(const glm::u8vec3& value) { _minorTickMarksColor = value; } - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - - virtual Circle3DOverlay* createClone() const override; - -protected: - float _startAt { 0 }; - float _endAt { 360 }; - float _outerRadius { 1 }; - float _innerRadius { 0 }; - - glm::u8vec3 _innerStartColor { DEFAULT_OVERLAY_COLOR }; - glm::u8vec3 _innerEndColor { DEFAULT_OVERLAY_COLOR }; - glm::u8vec3 _outerStartColor { DEFAULT_OVERLAY_COLOR }; - glm::u8vec3 _outerEndColor { DEFAULT_OVERLAY_COLOR }; - float _innerStartAlpha { DEFAULT_ALPHA }; - float _innerEndAlpha { DEFAULT_ALPHA }; - float _outerStartAlpha { DEFAULT_ALPHA }; - float _outerEndAlpha { DEFAULT_ALPHA }; - - bool _hasTickMarks { false }; - float _majorTickMarksAngle { 0 }; - float _minorTickMarksAngle { 0 }; - float _majorTickMarksLength { 0 }; - float _minorTickMarksLength { 0 }; - glm::u8vec3 _majorTickMarksColor { DEFAULT_OVERLAY_COLOR }; - glm::u8vec3 _minorTickMarksColor { DEFAULT_OVERLAY_COLOR }; - gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN }; - int _quadVerticesID { 0 }; - int _lineVerticesID { 0 }; - int _majorTicksVerticesID { 0 }; - int _minorTicksVerticesID { 0 }; - - bool _dirty { true }; -}; - - -#endif // hifi_Circle3DOverlay_h diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index aecfdba3b8..c382c3de43 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -60,11 +60,12 @@ ContextOverlayInterface::ContextOverlayInterface() { QUuid tabletFrameID = _hmdScriptingInterface->getCurrentTabletFrameID(); auto myAvatar = DependencyManager::get()->getMyAvatar(); glm::quat cameraOrientation = qApp->getCamera().getOrientation(); - QVariantMap props; + + EntityItemProperties properties; float sensorToWorldScale = myAvatar->getSensorToWorldScale(); - props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * ((CONTEXT_OVERLAY_TABLET_DISTANCE * sensorToWorldScale) * (cameraOrientation * Vectors::FRONT)))); - props.insert("orientation", quatToVariant(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f))))); - qApp->getOverlays().editOverlay(tabletFrameID, props); + properties.setPosition(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * ((CONTEXT_OVERLAY_TABLET_DISTANCE * sensorToWorldScale) * (cameraOrientation * Vectors::FRONT))); + properties.setRotation(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f)))); + DependencyManager::get()->editEntity(tabletFrameID, properties); _contextOverlayJustClicked = false; } }); @@ -93,7 +94,6 @@ static const float CONTEXT_OVERLAY_HOVERED_ALPHA = 1.0f; static const float CONTEXT_OVERLAY_UNHOVERED_PULSEMIN = 0.6f; static const float CONTEXT_OVERLAY_UNHOVERED_PULSEMAX = 1.0f; static const float CONTEXT_OVERLAY_UNHOVERED_PULSEPERIOD = 1.0f; -static const float CONTEXT_OVERLAY_UNHOVERED_COLORPULSE = 1.0f; void ContextOverlayInterface::setEnabled(bool enabled) { _enabled = enabled; @@ -192,22 +192,28 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& } // Finally, setup and draw the Context Overlay - if (_contextOverlayID == UNKNOWN_OVERLAY_ID || !qApp->getOverlays().isAddedOverlay(_contextOverlayID)) { - _contextOverlay = std::make_shared(); - _contextOverlay->setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA); - _contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN); - _contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX); - _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); - _contextOverlay->setIgnorePickIntersection(false); - _contextOverlay->setDrawInFront(true); - _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); - _contextOverlay->setIsFacingAvatar(true); - _contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay); + auto entityScriptingInterface = DependencyManager::get(); + if (_contextOverlayID == UNKNOWN_ENTITY_ID || !entityScriptingInterface->isAddedEntity(_contextOverlayID)) { + EntityItemProperties properties; + properties.setType(EntityTypes::Image); + properties.setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA); + properties.getPulse().setMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN); + properties.getPulse().setMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX); + properties.getPulse().setColorMode(PulseMode::IN_PHASE); + properties.setIgnorePickIntersection(false); + properties.setRenderLayer(RenderLayer::FRONT); + properties.setImageURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); + properties.setBillboardMode(BillboardMode::FULL); + + _contextOverlayID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); } - _contextOverlay->setWorldPosition(contextOverlayPosition); - _contextOverlay->setDimensions(contextOverlayDimensions); - _contextOverlay->setWorldOrientation(entityProperties.getRotation()); - _contextOverlay->setVisible(true); + + EntityItemProperties properties; + properties.setPosition(contextOverlayPosition); + properties.setDimensions(glm::vec3(contextOverlayDimensions, 0.01f)); + properties.setRotation(entityProperties.getRotation()); + properties.setVisible(true); + entityScriptingInterface->editEntity(_contextOverlayID, properties); return true; } @@ -227,15 +233,13 @@ bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& ent } bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { - if (_contextOverlayID != UNKNOWN_OVERLAY_ID) { + if (_contextOverlayID != UNKNOWN_ENTITY_ID) { qCDebug(context_overlay) << "Destroying Context Overlay on top of entity with ID: " << entityItemID; disableEntityHighlight(entityItemID); setCurrentEntityWithContextOverlay(QUuid()); _entityMarketplaceID.clear(); - // Destroy the Context Overlay - qApp->getOverlays().deleteOverlay(_contextOverlayID); - _contextOverlay = NULL; - _contextOverlayID = UNKNOWN_OVERLAY_ID; + DependencyManager::get()->deleteEntity(_contextOverlayID); + _contextOverlayID = UNKNOWN_ENTITY_ID; return true; } return false; @@ -245,45 +249,49 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt return ContextOverlayInterface::destroyContextOverlay(entityItemID, PointerEvent()); } -void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { - qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; +void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const QUuid& id, const PointerEvent& event) { + if (id == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { + qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "ID:" << id; emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } } -void ContextOverlayInterface::contextOverlays_hoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event) { - if (_contextOverlayID != UNKNOWN_OVERLAY_ID && _contextOverlay) { - qCDebug(context_overlay) << "Started hovering over Context Overlay. Overlay ID:" << overlayID; - _contextOverlay->setColor(CONTEXT_OVERLAY_COLOR); - _contextOverlay->setColorPulse(0.0f); // pulse off - _contextOverlay->setPulsePeriod(0.0f); // pulse off - _contextOverlay->setAlpha(CONTEXT_OVERLAY_HOVERED_ALPHA); +void ContextOverlayInterface::contextOverlays_hoverEnterOverlay(const QUuid& id, const PointerEvent& event) { + if (_contextOverlayID != UNKNOWN_ENTITY_ID) { + qCDebug(context_overlay) << "Started hovering over Context Overlay. ID:" << id; + EntityItemProperties properties; + properties.setColor(CONTEXT_OVERLAY_COLOR); + properties.getPulse().setColorMode(PulseMode::NONE); + properties.getPulse().setPeriod(0.0f); + properties.setAlpha(CONTEXT_OVERLAY_HOVERED_ALPHA); + DependencyManager::get()->editEntity(_contextOverlayID, properties); } } -void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event) { - if (_contextOverlayID != UNKNOWN_OVERLAY_ID && _contextOverlay) { - qCDebug(context_overlay) << "Stopped hovering over Context Overlay. Overlay ID:" << overlayID; - _contextOverlay->setColor(CONTEXT_OVERLAY_COLOR); - _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); - _contextOverlay->setPulsePeriod(CONTEXT_OVERLAY_UNHOVERED_PULSEPERIOD); - _contextOverlay->setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA); +void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const QUuid& id, const PointerEvent& event) { + if (_contextOverlayID != UNKNOWN_ENTITY_ID) { + qCDebug(context_overlay) << "Stopped hovering over Context Overlay. ID:" << id; + EntityItemProperties properties; + properties.setColor(CONTEXT_OVERLAY_COLOR); + properties.getPulse().setColorMode(PulseMode::IN_PHASE); + properties.getPulse().setPeriod(CONTEXT_OVERLAY_UNHOVERED_PULSEPERIOD); + properties.setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA); + DependencyManager::get()->editEntity(_contextOverlayID, properties); } } -void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event) { +void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& id, const PointerEvent& event) { bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get()->isMouse(event.getID()); - if (contextOverlayFilterPassed(entityID) && _enabled && !isMouse) { - enableEntityHighlight(entityID); + if (contextOverlayFilterPassed(id) && _enabled && !isMouse) { + enableEntityHighlight(id); } } -void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event) { +void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& id, const PointerEvent& event) { bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get()->isMouse(event.getID()); - if (_currentEntityWithContextOverlay != entityID && _enabled && !isMouse) { - disableEntityHighlight(entityID); + if (_currentEntityWithContextOverlay != id && _enabled && !isMouse) { + disableEntityHighlight(id); } } @@ -380,12 +388,12 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } } -void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) { - _selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID); +void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityID) { + _selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } -void ContextOverlayInterface::disableEntityHighlight(const EntityItemID& entityItemID) { - _selectionScriptingInterface->removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID); +void ContextOverlayInterface::disableEntityHighlight(const EntityItemID& entityID) { + _selectionScriptingInterface->removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); } void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 48b14e1a91..b87535acf2 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -22,8 +22,6 @@ #include "avatar/AvatarManager.h" #include "EntityScriptingInterface.h" -#include "ui/overlays/Image3DOverlay.h" -#include "ui/overlays/Overlays.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/SelectionScriptingInterface.h" #include "scripting/WalletScriptingInterface.h" @@ -43,8 +41,7 @@ class ContextOverlayInterface : public QObject, public Dependency { QSharedPointer _hmdScriptingInterface; QSharedPointer _tabletScriptingInterface; QSharedPointer _selectionScriptingInterface; - OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID }; - std::shared_ptr _contextOverlay { nullptr }; + QUuid _contextOverlayID { UNKNOWN_ENTITY_ID }; public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } @@ -68,9 +65,9 @@ public slots: bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event); bool destroyContextOverlay(const EntityItemID& entityItemID); - void contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event); - void contextOverlays_hoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event); - void contextOverlays_hoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event); + void contextOverlays_mousePressOnOverlay(const QUuid& id, const PointerEvent& event); + void contextOverlays_hoverEnterOverlay(const QUuid& id, const PointerEvent& event); + void contextOverlays_hoverLeaveOverlay(const QUuid& id, const PointerEvent& event); void contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event); void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); @@ -83,12 +80,12 @@ private: enum { MAX_SELECTION_COUNT = 16 }; - bool _verboseLogging{ true }; + bool _verboseLogging { true }; bool _enabled { true }; - EntityItemID _mouseDownEntity{}; + EntityItemID _mouseDownEntity; quint64 _mouseDownEntityTimestamp; - EntityItemID _currentEntityWithContextOverlay{}; - EntityItemID _lastInspectedEntity{}; + EntityItemID _currentEntityWithContextOverlay; + EntityItemID _lastInspectedEntity; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp deleted file mode 100644 index 9888d696cf..0000000000 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// -// Cube3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 this before QGLWidget, which includes an earlier version of OpenGL -#include "Cube3DOverlay.h" - -#include -#include -#include -#include - -QString const Cube3DOverlay::TYPE = "cube"; - -Cube3DOverlay::Cube3DOverlay() { - auto geometryCache = DependencyManager::get(); - for (size_t i = 0; i < _geometryIds.size(); ++i) { - _geometryIds[i] = geometryCache->allocateID(); - } -} - -Cube3DOverlay::Cube3DOverlay(const Cube3DOverlay* cube3DOverlay) : - Volume3DOverlay(cube3DOverlay) -{ - auto geometryCache = DependencyManager::get(); - for (size_t i = 0; i < _geometryIds.size(); ++i) { - _geometryIds[i] = geometryCache->allocateID(); - } -} - -Cube3DOverlay::~Cube3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - for (size_t i = 0; i < _geometryIds.size(); ++i) { - geometryCache->releaseID(_geometryIds[i]); - } - } -} - -void Cube3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 cubeColor(toGlm(color), alpha); - - auto batch = args->_batch; - if (batch) { - Transform transform = getRenderTransform(); - auto geometryCache = DependencyManager::get(); - auto shapePipeline = args->_shapePipeline; - if (!shapePipeline) { - shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline(); - } - - if (_isSolid) { - batch->setModelTransform(transform); - geometryCache->renderSolidCubeInstance(args, *batch, cubeColor, shapePipeline); - } else { - geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - if (getIsDashedLine()) { - auto dimensions = transform.getScale(); - transform.setScale(1.0f); - batch->setModelTransform(transform); - - glm::vec3 halfDimensions = dimensions / 2.0f; - glm::vec3 bottomLeftNear(-halfDimensions.x, -halfDimensions.y, -halfDimensions.z); - glm::vec3 bottomRightNear(halfDimensions.x, -halfDimensions.y, -halfDimensions.z); - glm::vec3 topLeftNear(-halfDimensions.x, halfDimensions.y, -halfDimensions.z); - glm::vec3 topRightNear(halfDimensions.x, halfDimensions.y, -halfDimensions.z); - - glm::vec3 bottomLeftFar(-halfDimensions.x, -halfDimensions.y, halfDimensions.z); - glm::vec3 bottomRightFar(halfDimensions.x, -halfDimensions.y, halfDimensions.z); - glm::vec3 topLeftFar(-halfDimensions.x, halfDimensions.y, halfDimensions.z); - glm::vec3 topRightFar(halfDimensions.x, halfDimensions.y, halfDimensions.z); - - geometryCache->renderDashedLine(*batch, bottomLeftNear, bottomRightNear, cubeColor, _geometryIds[0]); - geometryCache->renderDashedLine(*batch, bottomRightNear, bottomRightFar, cubeColor, _geometryIds[1]); - geometryCache->renderDashedLine(*batch, bottomRightFar, bottomLeftFar, cubeColor, _geometryIds[2]); - geometryCache->renderDashedLine(*batch, bottomLeftFar, bottomLeftNear, cubeColor, _geometryIds[3]); - - geometryCache->renderDashedLine(*batch, topLeftNear, topRightNear, cubeColor, _geometryIds[4]); - geometryCache->renderDashedLine(*batch, topRightNear, topRightFar, cubeColor, _geometryIds[5]); - geometryCache->renderDashedLine(*batch, topRightFar, topLeftFar, cubeColor, _geometryIds[6]); - geometryCache->renderDashedLine(*batch, topLeftFar, topLeftNear, cubeColor, _geometryIds[7]); - - geometryCache->renderDashedLine(*batch, bottomLeftNear, topLeftNear, cubeColor, _geometryIds[8]); - geometryCache->renderDashedLine(*batch, bottomRightNear, topRightNear, cubeColor, _geometryIds[9]); - geometryCache->renderDashedLine(*batch, bottomLeftFar, topLeftFar, cubeColor, _geometryIds[10]); - geometryCache->renderDashedLine(*batch, bottomRightFar, topRightFar, cubeColor, _geometryIds[11]); - - } else { - batch->setModelTransform(transform); - geometryCache->renderWireCubeInstance(args, *batch, cubeColor, shapePipeline); - } - } - } -} - -const render::ShapeKey Cube3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); - if (isTransparent()) { - builder.withTranslucent(); - } - if (!getIsSolid()) { - builder.withUnlit().withDepthBias(); - } - return builder.build(); -} - -Cube3DOverlay* Cube3DOverlay::createClone() const { - return new Cube3DOverlay(this); -} - -void Cube3DOverlay::setProperties(const QVariantMap& properties) { - Volume3DOverlay::setProperties(properties); -} - -/**jsdoc - * These are the properties of a cube {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.CubeProperties - * - * @property {string} type=cube - Has the value "cube". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - */ -QVariant Cube3DOverlay::getProperty(const QString& property) { - return Volume3DOverlay::getProperty(property); -} - -Transform Cube3DOverlay::evalRenderTransform() { - // TODO: handle registration point?? - glm::vec3 position = getWorldPosition(); - glm::vec3 dimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - - Transform transform; - transform.setScale(dimensions); - transform.setTranslation(position); - transform.setRotation(rotation); - return transform; -} - -scriptable::ScriptableModelBase Cube3DOverlay::getScriptableModel() { - auto geometryCache = DependencyManager::get(); - auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModelBase result; - if (auto mesh = geometryCache->meshFromShape(GeometryCache::Cube, vertexColor)) { - result.objectID = getID(); - result.append(mesh); - } - return result; -} diff --git a/interface/src/ui/overlays/Cube3DOverlay.h b/interface/src/ui/overlays/Cube3DOverlay.h deleted file mode 100644 index d28d11920a..0000000000 --- a/interface/src/ui/overlays/Cube3DOverlay.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Cube3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Cube3DOverlay_h -#define hifi_Cube3DOverlay_h - -#include "Volume3DOverlay.h" - -class Cube3DOverlay : public Volume3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Cube3DOverlay(); - Cube3DOverlay(const Cube3DOverlay* cube3DOverlay); - ~Cube3DOverlay(); - - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - - virtual Cube3DOverlay* createClone() const override; - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - virtual scriptable::ScriptableModelBase getScriptableModel() override; -protected: - Transform evalRenderTransform() override; - -private: - // edges on a cube - std::array _geometryIds; -}; - - -#endif // hifi_Cube3DOverlay_h diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp deleted file mode 100644 index 92481b8116..0000000000 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// -// Grid3DOverlay.cpp -// interface/src/ui/overlays -// -// Created by Ryan Huffman on 11/06/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 "Grid3DOverlay.h" - -#include -#include -#include -#include -#include - - -QString const Grid3DOverlay::TYPE = "grid"; -const float DEFAULT_SCALE = 100.0f; - -Grid3DOverlay::Grid3DOverlay() { - setDimensions(DEFAULT_SCALE); - updateGrid(); - _geometryId = DependencyManager::get()->allocateID(); -} - -Grid3DOverlay::Grid3DOverlay(const Grid3DOverlay* grid3DOverlay) : - Planar3DOverlay(grid3DOverlay), - _majorGridEvery(grid3DOverlay->_majorGridEvery), - _minorGridEvery(grid3DOverlay->_minorGridEvery) -{ - updateGrid(); - _geometryId = DependencyManager::get()->allocateID(); -} - -Grid3DOverlay::~Grid3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -AABox Grid3DOverlay::getBounds() const { - if (_followCamera) { - // This is a UI element that should always be in view, lie to the octree to avoid culling - const AABox DOMAIN_BOX = AABox(glm::vec3(-TREE_SCALE / 2), TREE_SCALE); - return DOMAIN_BOX; - } - return Planar3DOverlay::getBounds(); -} - -void Grid3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 gridColor(toGlm(color), alpha); - - auto batch = args->_batch; - - if (batch) { - auto minCorner = glm::vec2(-0.5f, -0.5f); - auto maxCorner = glm::vec2(0.5f, 0.5f); - - auto position = getWorldPosition(); - if (_followCamera) { - // Get the camera position rounded to the nearest major grid line - // This grid is for UI and should lie on worldlines - auto cameraPosition = - (float)_majorGridEvery * glm::round(args->getViewFrustum().getPosition() / (float)_majorGridEvery); - - position += glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z); - } - - Transform transform = getRenderTransform(); - transform.setTranslation(position); - batch->setModelTransform(transform); - const float MINOR_GRID_EDGE = 0.0025f; - const float MAJOR_GRID_EDGE = 0.005f; - DependencyManager::get()->renderGrid(*batch, minCorner, maxCorner, - _minorGridRowDivisions, _minorGridColDivisions, MINOR_GRID_EDGE, - _majorGridRowDivisions, _majorGridColDivisions, MAJOR_GRID_EDGE, - gridColor, _geometryId); - } -} - -const render::ShapeKey Grid3DOverlay::getShapeKey() { - return render::ShapeKey::Builder().withOwnPipeline().withUnlit().withDepthBias(); -} - -void Grid3DOverlay::setProperties(const QVariantMap& properties) { - Planar3DOverlay::setProperties(properties); - if (properties["followCamera"].isValid()) { - _followCamera = properties["followCamera"].toBool(); - } - - if (properties["majorGridEvery"].isValid()) { - _majorGridEvery = properties["majorGridEvery"].toInt(); - } - - if (properties["minorGridEvery"].isValid()) { - _minorGridEvery = properties["minorGridEvery"].toFloat(); - } - - updateGrid(); -} - -/**jsdoc - * These are the properties of a grid {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.GridProperties - * - * @property {string} type=grid - Has the value "grid". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - * - * @property {boolean} followCamera=true - If true, the grid is always visible even as the camera moves to another - * position. - * @property {number} majorGridEvery=5 - Integer number of minorGridEvery intervals at which to draw a thick grid - * line. Minimum value = 1. - * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = - * 0.001. - */ -QVariant Grid3DOverlay::getProperty(const QString& property) { - if (property == "followCamera") { - return _followCamera; - } - if (property == "majorGridEvery") { - return _majorGridEvery; - } - if (property == "minorGridEvery") { - return _minorGridEvery; - } - - return Planar3DOverlay::getProperty(property); -} - -Grid3DOverlay* Grid3DOverlay::createClone() const { - return new Grid3DOverlay(this); -} - -void Grid3DOverlay::updateGrid() { - const int MAJOR_GRID_EVERY_MIN = 1; - const float MINOR_GRID_EVERY_MIN = 0.01f; - - _majorGridEvery = std::max(_majorGridEvery, MAJOR_GRID_EVERY_MIN); - _minorGridEvery = std::max(_minorGridEvery, MINOR_GRID_EVERY_MIN); - - _majorGridRowDivisions = getDimensions().x / _majorGridEvery; - _majorGridColDivisions = getDimensions().y / _majorGridEvery; - - _minorGridRowDivisions = getDimensions().x / _minorGridEvery; - _minorGridColDivisions = getDimensions().y / _minorGridEvery; -} - -Transform Grid3DOverlay::evalRenderTransform() { - Transform transform; - transform.setRotation(getWorldOrientation()); - transform.setScale(glm::vec3(getDimensions(), 1.0f)); - return transform; -} diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h deleted file mode 100644 index 64b65b3178..0000000000 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// Grid3DOverlay.h -// interface/src/ui/overlays -// -// Created by Ryan Huffman on 11/06/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 -// - -#ifndef hifi_Grid3DOverlay_h -#define hifi_Grid3DOverlay_h - -#include "Planar3DOverlay.h" - -class Grid3DOverlay : public Planar3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Grid3DOverlay(); - Grid3DOverlay(const Grid3DOverlay* grid3DOverlay); - ~Grid3DOverlay(); - - virtual AABox getBounds() const override; - - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - virtual Grid3DOverlay* createClone() const override; - - // Grids are UI tools, and may not be intersected (pickable) - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, - glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } - -protected: - Transform evalRenderTransform() override; - -private: - void updateGrid(); - - bool _followCamera { true }; - - int _majorGridEvery { 5 }; - float _majorGridRowDivisions; - float _majorGridColDivisions; - - float _minorGridEvery { 1.0f }; - float _minorGridRowDivisions; - float _minorGridColDivisions; - int _geometryId { 0 }; -}; - -#endif // hifi_Grid3DOverlay_h diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp deleted file mode 100644 index dcf3ca2285..0000000000 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ /dev/null @@ -1,341 +0,0 @@ -// -// Image3DOverlay.cpp -// -// -// Created by Clement on 7/1/14. -// Modified and renamed by Zander Otavka on 8/4/15 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Image3DOverlay.h" - -#include -#include -#include -#include - -#include "GeometryUtil.h" - -#include "AbstractViewStateInterface.h" - -QString const Image3DOverlay::TYPE = "image3d"; - -Image3DOverlay::Image3DOverlay() { - _isLoaded = false; - _geometryId = DependencyManager::get()->allocateID(); -} - -Image3DOverlay::Image3DOverlay(const Image3DOverlay* image3DOverlay) : - Billboard3DOverlay(image3DOverlay), - _url(image3DOverlay->_url), - _texture(image3DOverlay->_texture), - _emissive(image3DOverlay->_emissive), - _fromImage(image3DOverlay->_fromImage) -{ - _geometryId = DependencyManager::get()->allocateID(); -} - -Image3DOverlay::~Image3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -void Image3DOverlay::update(float deltatime) { - if (!_isLoaded) { - _isLoaded = true; - _texture = DependencyManager::get()->getTexture(_url); - _textureIsLoaded = false; - } - Parent::update(deltatime); -} - -void Image3DOverlay::render(RenderArgs* args) { - if (!_renderVisible || !getParentVisible() || !_texture || !_texture->isLoaded()) { - return; - } - - // Once the texture has loaded, check if we need to update the render item because of transparency - if (!_textureIsLoaded && _texture && _texture->getGPUTexture()) { - _textureIsLoaded = true; - bool prevAlphaTexture = _alphaTexture; - _alphaTexture = _texture->getGPUTexture()->getUsage().isAlpha(); - if (_alphaTexture != prevAlphaTexture) { - auto itemID = getRenderItemID(); - if (render::Item::isValidID(itemID)) { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - render::Transaction transaction; - transaction.updateItem(itemID); - scene->enqueueTransaction(transaction); - } - } - } - - Q_ASSERT(args->_batch); - gpu::Batch* batch = args->_batch; - - float imageWidth = _texture->getWidth(); - float imageHeight = _texture->getHeight(); - - QRect fromImage; - if (_fromImage.width() <= 0) { - fromImage.setX(0); - fromImage.setWidth(imageWidth); - } else { - float scaleX = imageWidth / _texture->getOriginalWidth(); - fromImage.setX(scaleX * _fromImage.x()); - fromImage.setWidth(scaleX * _fromImage.width()); - } - - if (_fromImage.height() <= 0) { - fromImage.setY(0); - fromImage.setHeight(imageHeight); - } else { - float scaleY = imageHeight / _texture->getOriginalHeight(); - fromImage.setY(scaleY * _fromImage.y()); - fromImage.setHeight(scaleY * _fromImage.height()); - } - - float maxSize = glm::max(fromImage.width(), fromImage.height()); - float x = _keepAspectRatio ? fromImage.width() / (2.0f * maxSize) : 0.5f; - float y = _keepAspectRatio ? -fromImage.height() / (2.0f * maxSize) : -0.5f; - - glm::vec2 topLeft(-x, -y); - glm::vec2 bottomRight(x, y); - glm::vec2 texCoordTopLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight); - glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, - (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 imageColor(toGlm(color), alpha); - - batch->setModelTransform(getRenderTransform()); - batch->setResourceTexture(0, _texture->getGPUTexture()); - - DependencyManager::get()->renderQuad( - *batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, - imageColor, _geometryId - ); - - batch->setResourceTexture(0, nullptr); // restore default white color after me -} - -const render::ShapeKey Image3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (_emissive) { - builder.withUnlit(); - } - if (isTransparent()) { - builder.withTranslucent(); - } - return builder.build(); -} - -void Image3DOverlay::setProperties(const QVariantMap& properties) { - Billboard3DOverlay::setProperties(properties); - - auto urlValue = properties["url"]; - if (urlValue.isValid()) { - QString newURL = urlValue.toString(); - if (newURL != _url) { - setURL(newURL); - } - } - - auto subImageBoundsVar = properties["subImage"]; - if (subImageBoundsVar.isValid()) { - if (subImageBoundsVar.isNull()) { - _fromImage = QRect(); - } else { - QRect oldSubImageRect = _fromImage; - QRect subImageRect = _fromImage; - auto subImageBounds = subImageBoundsVar.toMap(); - if (subImageBounds["x"].isValid()) { - subImageRect.setX(subImageBounds["x"].toInt()); - } else { - subImageRect.setX(oldSubImageRect.x()); - } - if (subImageBounds["y"].isValid()) { - subImageRect.setY(subImageBounds["y"].toInt()); - } else { - subImageRect.setY(oldSubImageRect.y()); - } - if (subImageBounds["width"].isValid()) { - subImageRect.setWidth(subImageBounds["width"].toInt()); - } else { - subImageRect.setWidth(oldSubImageRect.width()); - } - if (subImageBounds["height"].isValid()) { - subImageRect.setHeight(subImageBounds["height"].toInt()); - } else { - subImageRect.setHeight(oldSubImageRect.height()); - } - setClipFromSource(subImageRect); - } - } - - auto keepAspectRatioValue = properties["keepAspectRatio"]; - if (keepAspectRatioValue.isValid()) { - _keepAspectRatio = keepAspectRatioValue.toBool(); - } - - auto emissiveValue = properties["emissive"]; - if (emissiveValue.isValid()) { - _emissive = emissiveValue.toBool(); - } -} - -/**jsdoc - * These are the properties of an image3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Image3DProperties - * - * @property {string} type=image3d - Has the value "image3d". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - * - * @property {bool} keepAspectRatio=true - overlays will maintain the aspect ratio when the subImage is applied. - * - * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis - * parallel to the user's avatar's "up" direction. - * - * @property {string} url - The URL of the PNG or JPG image to display. - * @property {Rect} subImage - The portion of the image to display. Defaults to the full image. - * @property {boolean} emissive - If true, the overlay is displayed at full brightness, otherwise it is rendered - * with scene lighting. - */ -QVariant Image3DOverlay::getProperty(const QString& property) { - if (property == "url") { - return _url; - } - if (property == "subImage") { - return _fromImage; - } - if (property == "emissive") { - return _emissive; - } - if (property == "keepAspectRatio") { - return _keepAspectRatio; - } - - return Billboard3DOverlay::getProperty(property); -} - -void Image3DOverlay::setURL(const QString& url) { - _url = url; - _isLoaded = false; -} - -bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - if (_texture && _texture->isLoaded()) { - Transform transform = getTransform(); - - // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. - bool isNull = _fromImage.isNull(); - float width = isNull ? _texture->getWidth() : _fromImage.width(); - float height = isNull ? _texture->getHeight() : _fromImage.height(); - float maxSize = glm::max(width, height); - glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); - glm::quat rotation = transform.getRotation(); - - if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } - } - - return false; -} - -bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - if (_texture && _texture->isLoaded()) { - Transform transform = getTransform(); - - // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. - bool isNull = _fromImage.isNull(); - float width = isNull ? _texture->getWidth() : _fromImage.width(); - float height = isNull ? _texture->getHeight() : _fromImage.height(); - float maxSize = glm::max(width, height); - glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); - glm::quat rotation = transform.getRotation(); - glm::vec3 position = getWorldPosition(); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } - } - - return false; -} - -Image3DOverlay* Image3DOverlay::createClone() const { - return new Image3DOverlay(this); -} - -Transform Image3DOverlay::evalRenderTransform() { - auto transform = Parent::evalRenderTransform(); - transform.postScale(glm::vec3(getDimensions(), 1.0f)); - return transform; -} diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h deleted file mode 100644 index 1000401abb..0000000000 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Image3DOverlay.h -// -// -// Created by Clement on 7/1/14. -// Modified and renamed by Zander Otavka on 8/4/15 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_Image3DOverlay_h -#define hifi_Image3DOverlay_h - -#include - -#include "Billboard3DOverlay.h" - -class Image3DOverlay : public Billboard3DOverlay { - Q_OBJECT - using Parent = Billboard3DOverlay; - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Image3DOverlay(); - Image3DOverlay(const Image3DOverlay* image3DOverlay); - ~Image3DOverlay(); - virtual void render(RenderArgs* args) override; - - virtual void update(float deltatime) override; - - virtual const render::ShapeKey getShapeKey() override; - - // setters - void setURL(const QString& url); - void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - - virtual Image3DOverlay* createClone() const override; - -protected: - Transform evalRenderTransform() override; - -private: - QString _url; - NetworkTexturePointer _texture; - bool _textureIsLoaded { false }; - bool _alphaTexture { false }; - bool _emissive { false }; - bool _keepAspectRatio { true }; - - QRect _fromImage; // where from in the image to sample - int _geometryId { 0 }; -}; - -#endif // hifi_Image3DOverlay_h diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index 19f32511f6..228c904026 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -19,29 +19,6 @@ QString const ImageOverlay::TYPE = "image"; QUrl const ImageOverlay::URL(QString("hifi/overlays/ImageOverlay.qml")); -// ImageOverlay's properties are defined in the QML file specified above. -/**jsdoc - * These are the properties of an image {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.ImageProperties - * - * @property {Rect} bounds - The position and size of the image display area, in pixels. Write-only. - * @property {number} x - Integer left, x-coordinate value of the image display area = bounds.x. - * Write-only. - * @property {number} y - Integer top, y-coordinate value of the image display area = bounds.y. - * Write-only. - * @property {number} width - Integer width of the image display area = bounds.width. Write-only. - * @property {number} height - Integer height of the image display area = bounds.height. Write-only. - * @property {string} imageURL - The URL of the image file to display. The image is scaled to fit to the bounds. - * Write-only. - * @property {Vec2} subImage=0,0 - Integer coordinates of the top left pixel to start using image content from. - * Write-only. - * @property {Color} color=0,0,0 - The color to apply over the top of the image to colorize it. Write-only. - * @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, 0.0 - - * 1.0. Write-only. - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * Write-only. - */ - ImageOverlay::ImageOverlay() : QmlOverlay(URL) { } @@ -50,5 +27,4 @@ ImageOverlay::ImageOverlay(const ImageOverlay* imageOverlay) : ImageOverlay* ImageOverlay::createClone() const { return new ImageOverlay(this); -} - +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp deleted file mode 100644 index b1c316c6af..0000000000 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ /dev/null @@ -1,366 +0,0 @@ -// -// Line3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Line3DOverlay.h" - -#include -#include - -#include "AbstractViewStateInterface.h" - -QString const Line3DOverlay::TYPE = "line3d"; - -Line3DOverlay::Line3DOverlay() : - _geometryCacheID(DependencyManager::get()->allocateID()) -{ -} - -Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) : - Base3DOverlay(line3DOverlay), - _geometryCacheID(DependencyManager::get()->allocateID()) -{ - setParentID(line3DOverlay->getParentID()); - setParentJointIndex(line3DOverlay->getParentJointIndex()); - setLocalTransform(line3DOverlay->getLocalTransform()); - _direction = line3DOverlay->getDirection(); - _length = line3DOverlay->getLength(); - _endParentID = line3DOverlay->getEndParentID(); - _endParentJointIndex = line3DOverlay->getEndJointIndex(); - _lineWidth = line3DOverlay->getLineWidth(); - _glow = line3DOverlay->getGlow(); -} - -Line3DOverlay::~Line3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (_geometryCacheID && geometryCache) { - geometryCache->releaseID(_geometryCacheID); - } -} - -glm::vec3 Line3DOverlay::getStart() const { - return getWorldPosition(); -} - -glm::vec3 Line3DOverlay::getEnd() const { - bool success; - glm::vec3 localEnd; - glm::vec3 worldEnd; - - if (_endParentID != QUuid()) { - glm::vec3 localOffset = _direction * _length; - bool success; - worldEnd = localToWorld(localOffset, _endParentID, _endParentJointIndex, getScalesWithParent(), success); - return worldEnd; - } - - localEnd = getLocalEnd(); - worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), getScalesWithParent(), success); - if (!success) { - qDebug() << "Line3DOverlay::getEnd failed, parentID = " << getParentID(); - } - return worldEnd; -} - -void Line3DOverlay::setStart(const glm::vec3& start) { - setWorldPosition(start); -} - -void Line3DOverlay::setEnd(const glm::vec3& end) { - bool success; - glm::vec3 localStart; - glm::vec3 localEnd; - glm::vec3 offset; - - if (_endParentID != QUuid()) { - offset = worldToLocal(end, _endParentID, _endParentJointIndex, getScalesWithParent(), success); - } else { - localStart = getLocalStart(); - localEnd = worldToLocal(end, getParentID(), getParentJointIndex(), getScalesWithParent(), success); - offset = localEnd - localStart; - } - if (!success) { - qDebug() << "Line3DOverlay::setEnd failed"; - return; - } - - _length = glm::length(offset); - if (_length > 0.0f) { - _direction = glm::normalize(offset); - } else { - _direction = glm::vec3(0.0f); - } - notifyRenderVariableChange(); -} - -void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) { - glm::vec3 offset; - if (_endParentID != QUuid()) { - offset = localEnd; - } else { - glm::vec3 localStart = getLocalStart(); - offset = localEnd - localStart; - } - _length = glm::length(offset); - if (_length > 0.0f) { - _direction = glm::normalize(offset); - } else { - _direction = glm::vec3(0.0f); - } -} - -AABox Line3DOverlay::getBounds() const { - auto extents = Extents{}; - extents.addPoint(getStart()); - extents.addPoint(getEnd()); - return AABox(extents); -} - -void Line3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 colorv4(toGlm(color), alpha); - auto batch = args->_batch; - if (batch) { - batch->setModelTransform(Transform()); - auto& renderTransform = getRenderTransform(); - glm::vec3 start = renderTransform.getTranslation(); - glm::vec3 end = renderTransform.transform(vec3(0.0, 0.0, -1.0)); - - auto geometryCache = DependencyManager::get(); - if (getIsDashedLine()) { - // TODO: add support for color to renderDashedLine() - geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - geometryCache->renderDashedLine(*batch, start, end, colorv4, _geometryCacheID); - } else { - // renderGlowLine handles both glow = 0 and glow > 0 cases - geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _lineWidth, _geometryCacheID); - } - } -} - -const render::ShapeKey Line3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withOwnPipeline(); - if (isTransparent()) { - builder.withTranslucent(); - } - return builder.build(); -} - -void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { - QVariantMap properties = originalProperties; - glm::vec3 newStart(0.0f); - bool newStartSet { false }; - glm::vec3 newEnd(0.0f); - bool newEndSet { false }; - - auto start = properties["start"]; - // If "start" property was not there, check to see if they included aliases: startPoint, p1 - if (!start.isValid()) { - start = properties["startPoint"]; - } - if (!start.isValid()) { - start = properties["p1"]; - } - if (start.isValid()) { - newStart = vec3FromVariant(start); - newStartSet = true; - } - properties.remove("start"); // so that Base3DOverlay doesn't respond to it - properties.remove("startPoint"); - properties.remove("p1"); - - auto end = properties["end"]; - // If "end" property was not there, check to see if they included aliases: endPoint, p2 - if (!end.isValid()) { - end = properties["endPoint"]; - } - if (!end.isValid()) { - end = properties["p2"]; - } - if (end.isValid()) { - newEnd = vec3FromVariant(end); - newEndSet = true; - } - properties.remove("end"); // so that Base3DOverlay doesn't respond to it - properties.remove("endPoint"); - properties.remove("p2"); - - auto length = properties["length"]; - if (length.isValid()) { - _length = length.toFloat(); - } - - Base3DOverlay::setProperties(properties); - - auto endParentIDProp = properties["endParentID"]; - if (endParentIDProp.isValid()) { - _endParentID = QUuid(endParentIDProp.toString()); - } - auto endParentJointIndexProp = properties["endParentJointIndex"]; - if (endParentJointIndexProp.isValid()) { - _endParentJointIndex = endParentJointIndexProp.toInt(); - } - - auto localStart = properties["localStart"]; - if (localStart.isValid()) { - glm::vec3 tmpLocalEnd = getLocalEnd(); - setLocalStart(vec3FromVariant(localStart)); - setLocalEnd(tmpLocalEnd); - } - - auto localEnd = properties["localEnd"]; - if (localEnd.isValid()) { - setLocalEnd(vec3FromVariant(localEnd)); - } - - // these are saved until after Base3DOverlay::setProperties so parenting infomation can be set, first - if (newStartSet) { - setStart(newStart); - } - if (newEndSet) { - setEnd(newEnd); - } - - auto glow = properties["glow"]; - if (glow.isValid()) { - float prevGlow = _glow; - setGlow(glow.toFloat()); - // Update our payload key if necessary to handle transparency - if ((prevGlow <= 0.0f && _glow > 0.0f) || (prevGlow > 0.0f && _glow <= 0.0f)) { - auto itemID = getRenderItemID(); - if (render::Item::isValidID(itemID)) { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - render::Transaction transaction; - transaction.updateItem(itemID); - scene->enqueueTransaction(transaction); - } - } - } - - auto lineWidth = properties["lineWidth"]; - if (lineWidth.isValid()) { - setLineWidth(lineWidth.toFloat()); - } -} - -/**jsdoc - * These are the properties of a line3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Line3DProperties - * - * @property {string} type=line3d - Has the value "line3d". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to. - * @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is - * attached to if parentID is an avatar skeleton. A value of 65535 means "no joint". - * @property {Vec3} start - The start point of the line. Synonyms: startPoint, p1, and - * position. - * @property {Vec3} end - The end point of the line. Synonyms: endPoint and p2. - * @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as start. Synonym: localPosition. - * @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a - * endParentID set, otherwise the same value as end. - * @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end - * points. - * @property {number} glow=0 - If glow > 0, the line is rendered with a glow. - * @property {number} lineWidth=0.02 - If glow > 0, this is the width of the glow, in meters. - */ -QVariant Line3DOverlay::getProperty(const QString& property) { - if (property == "start" || property == "startPoint" || property == "p1") { - return vec3toVariant(getStart()); - } - if (property == "end" || property == "endPoint" || property == "p2") { - return vec3toVariant(getEnd()); - } - if (property == "length") { - return QVariant(getLength()); - } - if (property == "endParentID") { - return _endParentID; - } - if (property == "endParentJointIndex") { - return _endParentJointIndex; - } - if (property == "localStart") { - return vec3toVariant(getLocalStart()); - } - if (property == "localEnd") { - return vec3toVariant(getLocalEnd()); - } - if (property == "glow") { - return getGlow(); - } - if (property == "lineWidth") { - return _lineWidth; - } - - return Base3DOverlay::getProperty(property); -} - -Line3DOverlay* Line3DOverlay::createClone() const { - return new Line3DOverlay(this); -} - -Transform Line3DOverlay::evalRenderTransform() { - // Capture start and endin the renderTransform: - // start is the origin - // end is at the tip of the front axis aka -Z - Transform transform; - transform.setTranslation( getStart()); - auto endPos = getEnd(); - - auto vec = endPos - transform.getTranslation(); - const float MIN_LINE_LENGTH = 0.0001f; - auto scale = glm::max(glm::length(vec), MIN_LINE_LENGTH); - auto dir = vec / scale; - auto orientation = glm::rotation(glm::vec3(0.0f, 0.0f, -1.0f), dir); - transform.setRotation(orientation); - transform.setScale(scale); - - return transform; -} diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h deleted file mode 100644 index 79af937f23..0000000000 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// Line3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Line3DOverlay_h -#define hifi_Line3DOverlay_h - -#include "Base3DOverlay.h" - -class Line3DOverlay : public Base3DOverlay { - Q_OBJECT - using Parent = Base3DOverlay; - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Line3DOverlay(); - Line3DOverlay(const Line3DOverlay* line3DOverlay); - ~Line3DOverlay(); - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - virtual AABox getBounds() const override; - - // getters - glm::vec3 getStart() const; - glm::vec3 getEnd() const; - const float& getLineWidth() const { return _lineWidth; } - const float& getGlow() const { return _glow; } - - // setters - void setStart(const glm::vec3& start); - void setEnd(const glm::vec3& end); - - void setLocalStart(const glm::vec3& localStart) { setLocalPosition(localStart); } - void setLocalEnd(const glm::vec3& localEnd); - - void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } - void setGlow(const float& glow) { _glow = glow; } - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - bool isTransparent() override { return Base3DOverlay::isTransparent() || _glow > 0.0f; } - - virtual Line3DOverlay* createClone() const override; - - glm::vec3 getDirection() const { return _direction; } - float getLength() const { return _length; } - glm::vec3 getLocalStart() const { return getLocalPosition(); } - glm::vec3 getLocalEnd() const { return getLocalStart() + _direction * _length; } - QUuid getEndParentID() const { return _endParentID; } - quint16 getEndJointIndex() const { return _endParentJointIndex; } - -protected: - Transform evalRenderTransform() override; - -private: - QUuid _endParentID; - quint16 _endParentJointIndex { INVALID_JOINT_INDEX }; - - // _direction and _length are in the parent's frame. If _endParentID is set, they are - // relative to that. Otherwise, they are relative to the local-start-position (which is the - // same as localPosition) - glm::vec3 _direction; // in parent frame - float _length { 1.0 }; // in parent frame - - const float DEFAULT_LINE_WIDTH = 0.02f; - float _lineWidth { DEFAULT_LINE_WIDTH }; - float _glow { 0.0 }; - int _geometryCacheID; -}; - -#endif // hifi_Line3DOverlay_h diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp deleted file mode 100644 index 14e5cdc7f5..0000000000 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ /dev/null @@ -1,767 +0,0 @@ -// -// ModelOverlay.cpp -// -// -// Created by Clement on 6/30/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 "ModelOverlay.h" - -#include -#include - -#include - -#include "Application.h" - - -QString const ModelOverlay::TYPE = "model"; - -ModelOverlay::ModelOverlay() - : _model(std::make_shared(nullptr, this)), - _modelTextures(QVariantMap()) -{ - _model->setLoadingPriority(_loadPriority); - _isLoaded = false; - render::ScenePointer scene = qApp->getMain3DScene(); - _model->setVisibleInScene(false, scene); -} - -ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : - Volume3DOverlay(modelOverlay), - _model(std::make_shared(nullptr, this)), - _modelTextures(QVariantMap()), - _url(modelOverlay->_url), - _updateModel(false), - _scaleToFit(modelOverlay->_scaleToFit), - _loadPriority(modelOverlay->_loadPriority), - - _animationURL(modelOverlay->_animationURL), - _animationFPS(modelOverlay->_animationFPS), - _animationCurrentFrame(modelOverlay->_animationCurrentFrame), - _animationRunning(modelOverlay->_animationRunning), - _animationLoop(modelOverlay->_animationLoop), - _animationFirstFrame(modelOverlay->_animationFirstFrame), - _animationLastFrame(modelOverlay->_animationLastFrame), - _animationHold(modelOverlay->_animationHold), - _animationAllowTranslation(modelOverlay->_animationAllowTranslation) - - // Joint translations and rotations aren't copied because the model needs to load before they can be applied. -{ - _model->setLoadingPriority(_loadPriority); - if (_url.isValid()) { - _updateModel = true; - _isLoaded = false; - } -} - -void ModelOverlay::update(float deltatime) { - Base3DOverlay::update(deltatime); - - if (_updateModel) { - _updateModel = false; - _model->setSnapModelToCenter(true); - Transform transform = evalRenderTransform(); - if (_scaleToFit) { - _model->setScaleToFit(true, transform.getScale() * getDimensions()); - } else { - _model->setScale(transform.getScale()); - } - _model->setRotation(transform.getRotation()); - _model->setTranslation(transform.getTranslation()); - _model->setURL(_url); - _model->simulate(deltatime, true); - } else { - _model->simulate(deltatime); - } - _isLoaded = _model->isActive(); - - if (isAnimatingSomething()) { - if (!jointsMapped()) { - mapAnimationJoints(_model->getJointNames()); - } - animate(); - } - - // check to see if when we added our model to the scene they were ready, if they were not ready, then - // fix them up in the scene - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - if (_model->needsFixupInScene()) { - emit DependencyManager::get()->modelRemovedFromScene(getID(), NestableType::Overlay, _model); - _model->removeFromScene(scene, transaction); - _model->addToScene(scene, transaction); - - auto newRenderItemIDs{ _model->fetchRenderItemIDs() }; - transaction.updateItem(getRenderItemID(), [newRenderItemIDs](Overlay& data) { - auto modelOverlay = static_cast(&data); - modelOverlay->setSubRenderItemIDs(newRenderItemIDs); - }); - processMaterials(); - emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); - } - bool metaDirty = false; - if (_visibleDirty && _texturesLoaded) { - _visibleDirty = false; - // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true - uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW); - - _model->setTagMask(modelRenderTagMask, scene); - _model->setVisibleInScene(getVisible(), scene); - metaDirty = true; - } - if (_renderLayerDirty) { - _renderLayerDirty = false; - _model->setHifiRenderLayer(_drawHUDLayer ? render::hifi::LAYER_3D_HUD : (_drawInFront ? render::hifi::LAYER_3D_FRONT : render::hifi::LAYER_3D), scene); - metaDirty = true; - } - if (_groupCulledDirty) { - _groupCulledDirty = false; - _model->setGroupCulled(_isGroupCulled, scene); - metaDirty = true; - } - if (metaDirty) { - transaction.updateItem(getRenderItemID(), [](Overlay& data) {}); - } - scene->enqueueTransaction(transaction); - - if (_texturesDirty && !_modelTextures.isEmpty()) { - _texturesDirty = false; - _model->setTextures(_modelTextures); - } - - if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) { - _texturesLoaded = true; - - _model->setVisibleInScene(getVisible(), scene); - _model->updateRenderItems(); - } -} - -bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { - Volume3DOverlay::addToScene(overlay, scene, transaction); - _model->addToScene(scene, transaction); - processMaterials(); - emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); - return true; -} - -void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { - Volume3DOverlay::removeFromScene(overlay, scene, transaction); - _model->removeFromScene(scene, transaction); - emit DependencyManager::get()->modelRemovedFromScene(getID(), NestableType::Overlay, _model); - transaction.updateItem(getRenderItemID(), [](Overlay& data) { - auto modelOverlay = static_cast(&data); - modelOverlay->clearSubRenderItemIDs(); - }); -} - -void ModelOverlay::setVisible(bool visible) { - if (visible != getVisible()) { - Overlay::setVisible(visible); - _visibleDirty = true; - } -} - -void ModelOverlay::setDrawInFront(bool drawInFront) { - if (drawInFront != getDrawInFront()) { - Base3DOverlay::setDrawInFront(drawInFront); - _renderLayerDirty = true; - } -} - -void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) { - if (drawHUDLayer != getDrawHUDLayer()) { - Base3DOverlay::setDrawHUDLayer(drawHUDLayer); - _renderLayerDirty = true; - } -} - -void ModelOverlay::setGroupCulled(bool groupCulled) { - if (groupCulled != _isGroupCulled) { - _isGroupCulled = groupCulled; - _groupCulledDirty = true; - } -} - -void ModelOverlay::setProperties(const QVariantMap& properties) { - auto origPosition = getWorldPosition(); - auto origRotation = getWorldOrientation(); - auto origDimensions = getDimensions(); - auto origScale = getSNScale(); - - Base3DOverlay::setProperties(properties); - - auto scale = properties["scale"]; - if (scale.isValid()) { - setSNScale(vec3FromVariant(scale)); - } - - auto dimensions = properties["dimensions"]; - if (!dimensions.isValid()) { - dimensions = properties["size"]; - } - if (dimensions.isValid()) { - _scaleToFit = true; - setDimensions(vec3FromVariant(dimensions)); - } else if (scale.isValid()) { - // if "scale" property is set but "dimensions" is not. - // do NOT scale to fit. - _scaleToFit = false; - } - - if (origPosition != getWorldPosition() || origRotation != getWorldOrientation() || origDimensions != getDimensions() || origScale != getSNScale()) { - _updateModel = true; - } - - auto loadPriorityProperty = properties["loadPriority"]; - if (loadPriorityProperty.isValid()) { - _loadPriority = loadPriorityProperty.toFloat(); - _model->setLoadingPriority(_loadPriority); - } - - auto urlValue = properties["url"]; - if (urlValue.isValid() && urlValue.canConvert()) { - _url = urlValue.toString(); - _updateModel = true; - _isLoaded = false; - _texturesLoaded = false; - } - - auto texturesValue = properties["textures"]; - if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) { - _texturesLoaded = false; - QVariantMap textureMap = texturesValue.toMap(); - _modelTextures = textureMap; - _texturesDirty = true; - } - - auto groupCulledValue = properties["isGroupCulled"]; - if (groupCulledValue.isValid() && groupCulledValue.canConvert(QVariant::Bool)) { - setGroupCulled(groupCulledValue.toBool()); - } - - // jointNames is read-only. - // jointPositions is read-only. - // jointOrientations is read-only. - - // relative - auto jointTranslationsValue = properties["jointTranslations"]; - if (jointTranslationsValue.canConvert(QVariant::List)) { - const QVariantList& jointTranslations = jointTranslationsValue.toList(); - int translationCount = jointTranslations.size(); - int jointCount = _model->getJointStateCount(); - if (translationCount < jointCount) { - jointCount = translationCount; - } - for (int i=0; i < jointCount; i++) { - const auto& translationValue = jointTranslations[i]; - if (translationValue.isValid()) { - _model->setJointTranslation(i, true, vec3FromVariant(translationValue), 1.0f); - } - } - _updateModel = true; - } - - // relative - auto jointRotationsValue = properties["jointRotations"]; - if (jointRotationsValue.canConvert(QVariant::List)) { - const QVariantList& jointRotations = jointRotationsValue.toList(); - int rotationCount = jointRotations.size(); - int jointCount = _model->getJointStateCount(); - if (rotationCount < jointCount) { - jointCount = rotationCount; - } - for (int i=0; i < jointCount; i++) { - const auto& rotationValue = jointRotations[i]; - if (rotationValue.isValid()) { - _model->setJointRotation(i, true, quatFromVariant(rotationValue), 1.0f); - } - } - _updateModel = true; - } - - auto animationSettings = properties["animationSettings"]; - if (animationSettings.canConvert(QVariant::Map)) { - QVariantMap animationSettingsMap = animationSettings.toMap(); - - auto animationURL = animationSettingsMap["url"]; - auto animationFPS = animationSettingsMap["fps"]; - auto animationCurrentFrame = animationSettingsMap["currentFrame"]; - auto animationFirstFrame = animationSettingsMap["firstFrame"]; - auto animationLastFrame = animationSettingsMap["lastFrame"]; - auto animationRunning = animationSettingsMap["running"]; - auto animationLoop = animationSettingsMap["loop"]; - auto animationHold = animationSettingsMap["hold"]; - auto animationAllowTranslation = animationSettingsMap["allowTranslation"]; - - if (animationURL.canConvert(QVariant::Url)) { - _animationURL = animationURL.toUrl(); - } - if (animationFPS.isValid()) { - _animationFPS = animationFPS.toFloat(); - } - if (animationCurrentFrame.isValid()) { - _animationCurrentFrame = animationCurrentFrame.toFloat(); - } - if (animationFirstFrame.isValid()) { - _animationFirstFrame = animationFirstFrame.toFloat(); - } - if (animationLastFrame.isValid()) { - _animationLastFrame = animationLastFrame.toFloat(); - } - - if (animationRunning.canConvert(QVariant::Bool)) { - _animationRunning = animationRunning.toBool(); - } - if (animationLoop.canConvert(QVariant::Bool)) { - _animationLoop = animationLoop.toBool(); - } - if (animationHold.canConvert(QVariant::Bool)) { - _animationHold = animationHold.toBool(); - } - if (animationAllowTranslation.canConvert(QVariant::Bool)) { - _animationAllowTranslation = animationAllowTranslation.toBool(); - } - - } -} - -template -vectorType ModelOverlay::mapJoints(mapFunction function) const { - vectorType result; - if (_model && _model->isActive()) { - const int jointCount = _model->getJointStateCount(); - result.reserve(jointCount); - for (int i = 0; i < jointCount; i++) { - result << function(i); - } - } - return result; -} - -// Note: ModelOverlay overrides Volume3DOverlay's "dimensions" and "scale" properties. -/**jsdoc - * These are the properties of a model {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.ModelProperties - * - * @property {string} type=sphere - Has the value "model". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} isGroupCulled=false - If true, the mesh parts of the model are LOD culled as a group. - * If false, separate mesh parts will be LOD culled individually. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {string} url - The URL of the FBX or OBJ model used for the overlay. - * @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load - * first. - * @property {Vec3} dimensions - The dimensions of the overlay. Synonym: size. - * @property {Vec3} scale - The scale factor applied to the model's dimensions. - * @property {object.} textures - Maps the named textures in the model to the JPG or PNG images in the urls. - * @property {Array.} jointNames - The names of the joints - if any - in the model. Read-only. - * @property {Array.} jointRotations - The relative rotations of the model's joints. Not copied if overlay is - * cloned. - * @property {Array.} jointTranslations - The relative translations of the model's joints. Not copied if overlay is - * cloned. - * @property {Array.} jointOrientations - The absolute orientations of the model's joints, in world coordinates. - * Read-only. - * @property {Array.} jointPositions - The absolute positions of the model's joints, in world coordinates. - * Read-only. - * @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play. - * @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at. - * @property {number} animationSettings.firstFrame=0 - The frame to start playing at. - * @property {number} animationSettings.lastFrame=0 - The frame to finish playing at. - * @property {number} animationSettings.currentFrame=0 - The current frame being played. - * @property {boolean} animationSettings.running=false - Whether or not the animation is playing. - * @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop. - * @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and - * translations of the last frame played should be maintained. - * @property {boolean} animationSettings.allowTranslation=false - Whether or not translations contained in the animation should - * be played. - */ -QVariant ModelOverlay::getProperty(const QString& property) { - if (property == "url") { - return _url.toString(); - } - if (property == "dimensions" || property == "size") { - return vec3toVariant(getDimensions()); - } - if (property == "scale") { - return vec3toVariant(getSNScale()); - } - if (property == "textures") { - if (_modelTextures.size() > 0) { - QVariantMap textures; - foreach(const QString& key, _modelTextures.keys()) { - textures[key] = _modelTextures[key].toString(); - } - return textures; - } else { - return QVariant(); - } - } - - if (property == "loadPriority") { - return _loadPriority; - } - - if (property == "jointNames") { - if (_model && _model->isActive()) { - // note: going through Rig because Model::getJointNames() (which proxies to HFMModel) was always empty - const Rig* rig = &(_model->getRig()); - return mapJoints([rig](int jointIndex) -> QString { - return rig->nameOfJoint(jointIndex); - }); - } - } - - // relative - if (property == "jointRotations") { - return mapJoints( - [this](int jointIndex) -> QVariant { - glm::quat rotation; - _model->getJointRotation(jointIndex, rotation); - return quatToVariant(rotation); - }); - } - - // relative - if (property == "jointTranslations") { - return mapJoints( - [this](int jointIndex) -> QVariant { - glm::vec3 translation; - _model->getJointTranslation(jointIndex, translation); - return vec3toVariant(translation); - }); - } - - // absolute - if (property == "jointOrientations") { - return mapJoints( - [this](int jointIndex) -> QVariant { - glm::quat orientation; - _model->getJointRotationInWorldFrame(jointIndex, orientation); - return quatToVariant(orientation); - }); - } - - // absolute - if (property == "jointPositions") { - return mapJoints( - [this](int jointIndex) -> QVariant { - glm::vec3 position; - _model->getJointPositionInWorldFrame(jointIndex, position); - return vec3toVariant(position); - }); - } - - // animation properties - if (property == "animationSettings") { - QVariantMap animationSettingsMap; - - animationSettingsMap["url"] = _animationURL; - animationSettingsMap["fps"] = _animationFPS; - animationSettingsMap["currentFrame"] = _animationCurrentFrame; - animationSettingsMap["firstFrame"] = _animationFirstFrame; - animationSettingsMap["lastFrame"] = _animationLastFrame; - animationSettingsMap["running"] = _animationRunning; - animationSettingsMap["loop"] = _animationLoop; - animationSettingsMap["hold"]= _animationHold; - animationSettingsMap["allowTranslation"] = _animationAllowTranslation; - - return animationSettingsMap; - } - - - return Volume3DOverlay::getProperty(property); -} - -bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - QVariantMap extraInfo; - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); -} - -bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); -} - -bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - QVariantMap extraInfo; - return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); -} - -bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { - return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); -} - -ModelOverlay* ModelOverlay::createClone() const { - return new ModelOverlay(this); -} - -Transform ModelOverlay::evalRenderTransform() { - Transform transform = getTransform(); - transform.setScale(1.0f); // disable inherited scale - return transform; -} - -void ModelOverlay::locationChanged(bool tellPhysics) { - Base3DOverlay::locationChanged(tellPhysics); - - // FIXME Start using the _renderTransform instead of calling for Transform and Dimensions from here, do the custom things needed in evalRenderTransform() - if (_model && _model->isActive()) { - _model->setRotation(getWorldOrientation()); - _model->setTranslation(getWorldPosition()); - _updateModel = true; - } -} - -QString ModelOverlay::getName() const { - if (_name != "") { - return QString("Overlay:") + getType() + ":" + _name; - } - return QString("Overlay:") + getType() + ":" + _url.toString(); -} - - -void ModelOverlay::animate() { - - if (!_animation || !_animation->isLoaded() || !_model || !_model->isLoaded()) { - return; - } - - - QVector jointsData; - - const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy - int frameCount = frames.size(); - if (frameCount <= 0) { - return; - } - - if (!_lastAnimated) { - _lastAnimated = usecTimestampNow(); - return; - } - - auto now = usecTimestampNow(); - auto interval = now - _lastAnimated; - _lastAnimated = now; - float deltaTime = (float)interval / (float)USECS_PER_SECOND; - _animationCurrentFrame += (deltaTime * _animationFPS); - - int animationCurrentFrame = (int)(glm::floor(_animationCurrentFrame)) % frameCount; - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; - } - - if (animationCurrentFrame == _lastKnownCurrentFrame) { - return; - } - _lastKnownCurrentFrame = animationCurrentFrame; - - if (_jointMapping.size() != _model->getJointStateCount()) { - return; - } - - QStringList animationJointNames = _animation->getHFMModel().getJointNames(); - auto& hfmJoints = _animation->getHFMModel().joints; - - auto& originalHFMJoints = _model->getHFMModel().joints; - auto& originalHFMIndices = _model->getHFMModel().jointIndices; - - const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; - const QVector& translations = frames[_lastKnownCurrentFrame].translations; - - jointsData.resize(_jointMapping.size()); - for (int j = 0; j < _jointMapping.size(); j++) { - int index = _jointMapping[j]; - - if (index >= 0) { - glm::mat4 translationMat; - - if (_animationAllowTranslation) { - if (index < translations.size()) { - translationMat = glm::translate(translations[index]); - } - } else if (index < animationJointNames.size()) { - QString jointName = hfmJoints[index].name; - - if (originalHFMIndices.contains(jointName)) { - // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation. - int remappedIndex = originalHFMIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. - translationMat = glm::translate(originalHFMJoints[remappedIndex].translation); - } - } - glm::mat4 rotationMat; - if (index < rotations.size()) { - rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * rotations[index] * hfmJoints[index].postRotation); - } else { - rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * hfmJoints[index].postRotation); - } - - glm::mat4 finalMat = (translationMat * hfmJoints[index].preTransform * - rotationMat * hfmJoints[index].postTransform); - auto& jointData = jointsData[j]; - jointData.translation = extractTranslation(finalMat); - jointData.translationIsDefaultPose = false; - jointData.rotation = glmExtractRotation(finalMat); - jointData.rotationIsDefaultPose = false; - } - } - // Set the data in the model - copyAnimationJointDataToModel(jointsData); -} - - -void ModelOverlay::mapAnimationJoints(const QStringList& modelJointNames) { - - // if we don't have animation, or we're already joint mapped then bail early - if (!hasAnimation() || jointsMapped()) { - return; - } - - if (!_animation || _animation->getURL() != _animationURL) { - _animation = DependencyManager::get()->getAnimation(_animationURL); - } - - if (_animation && _animation->isLoaded()) { - QStringList animationJointNames = _animation->getJointNames(); - - if (modelJointNames.size() > 0 && animationJointNames.size() > 0) { - _jointMapping.resize(modelJointNames.size()); - for (int i = 0; i < modelJointNames.size(); i++) { - _jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]); - } - _jointMappingCompleted = true; - _jointMappingURL = _animationURL; - } - } -} - -void ModelOverlay::copyAnimationJointDataToModel(QVector jointsData) { - if (!_model || !_model->isLoaded()) { - return; - } - - // relay any inbound joint changes from scripts/animation/network to the model/rig - for (int index = 0; index < jointsData.size(); ++index) { - auto& jointData = jointsData[index]; - _model->setJointRotation(index, true, jointData.rotation, 1.0f); - _model->setJointTranslation(index, true, jointData.translation, 1.0f); - } - _updateModel = true; -} - -void ModelOverlay::clearSubRenderItemIDs() { - _subRenderItemIDs.clear(); -} - -void ModelOverlay::setSubRenderItemIDs(const render::ItemIDs& ids) { - _subRenderItemIDs = ids; -} - -uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { - if (_model) { - auto metaSubItems = _subRenderItemIDs; - subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end()); - return (uint32_t)metaSubItems.size(); - } - return 0; -} - -void ModelOverlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { - Overlay::addMaterial(material, parentMaterialName); - if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->addMaterial(material, parentMaterialName); - } -} - -void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { - Overlay::removeMaterial(material, parentMaterialName); - if (_model && _model->fetchRenderItemIDs().size() > 0) { - _model->removeMaterial(material, parentMaterialName); - } -} - -void ModelOverlay::processMaterials() { - assert(_model); - std::lock_guard lock(_materialsLock); - for (auto& shapeMaterialPair : _materials) { - auto material = shapeMaterialPair.second; - while (!material.empty()) { - _model->addMaterial(material.top(), shapeMaterialPair.first); - material.pop(); - } - } -} - -bool ModelOverlay::canReplaceModelMeshPart(int meshIndex, int partIndex) { - // TODO: bounds checking; for now just used to indicate provider generally supports mesh updates - return _model && _model->isLoaded(); -} - -bool ModelOverlay::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer newModel, int meshIndex, int partIndex) { - return canReplaceModelMeshPart(meshIndex, partIndex) && - _model->replaceScriptableModelMeshPart(newModel, meshIndex, partIndex); -} - -scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() { - if (!_model || !_model->isLoaded()) { - return Base3DOverlay::getScriptableModel(); - } - auto result = _model->getScriptableModel(); - result.objectID = getID(); - { - std::lock_guard lock(_materialsLock); - result.appendMaterials(_materials); - } - return result; -} - -render::ItemKey ModelOverlay::getKey() { - auto builder = render::ItemKey::Builder(Base3DOverlay::getKey()); - if (_isGroupCulled) { - builder.withMetaCullGroup(); - } - return builder.build(); -} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h deleted file mode 100644 index 17a2327d02..0000000000 --- a/interface/src/ui/overlays/ModelOverlay.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// ModelOverlay.h -// -// -// Created by Clement on 6/30/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 -// - -#ifndef hifi_ModelOverlay_h -#define hifi_ModelOverlay_h - -#include -#include - -#include "Volume3DOverlay.h" - -class ModelOverlay : public Volume3DOverlay { - Q_OBJECT -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - virtual QString getName() const override; - - ModelOverlay(); - ModelOverlay(const ModelOverlay* modelOverlay); - - virtual void update(float deltatime) override; - virtual void render(RenderArgs* args) override {}; - - virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override; - - render::ItemKey getKey() override; - void clearSubRenderItemIDs(); - void setSubRenderItemIDs(const render::ItemIDs& ids); - - virtual void setIsVisibleInSecondaryCamera(bool value) override { - Base3DOverlay::setIsVisibleInSecondaryCamera(value); - _visibleDirty = true; - } - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; - - virtual ModelOverlay* createClone() const override; - - virtual bool addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; - virtual void removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; - - void locationChanged(bool tellPhysics) override; - - float getLoadPriority() const { return _loadPriority; } - - bool hasAnimation() const { return !_animationURL.isEmpty(); } - bool jointsMapped() const { return _jointMappingURL == _animationURL && _jointMappingCompleted; } - - void setVisible(bool visible) override; - void setDrawInFront(bool drawInFront) override; - void setDrawHUDLayer(bool drawHUDLayer) override; - void setGroupCulled(bool groupCulled); - - void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; - void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; - - virtual scriptable::ScriptableModelBase getScriptableModel() override; - virtual bool canReplaceModelMeshPart(int meshIndex, int partIndex) override; - virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override; - -protected: - Transform evalRenderTransform() override; - - // helper to extract metadata from our Model's rigged joints - template using mapFunction = std::function; - template - vectorType mapJoints(mapFunction function) const; - - void animate(); - void mapAnimationJoints(const QStringList& modelJointNames); - bool isAnimatingSomething() const { - return !_animationURL.isEmpty() && _animationRunning && _animationFPS != 0.0f; - } - void copyAnimationJointDataToModel(QVector jointsData); - - -private: - - ModelPointer _model; - QVariantMap _modelTextures; - bool _texturesLoaded { false }; - bool _texturesDirty { false }; - - render::ItemIDs _subRenderItemIDs; - - QUrl _url; - bool _updateModel { false }; - bool _scaleToFit { false }; - float _loadPriority { 0.0f }; - - AnimationPointer _animation; - - QUrl _animationURL; - float _animationFPS { 0.0f }; - float _animationCurrentFrame { 0.0f }; - bool _animationRunning { false }; - bool _animationLoop { false }; - float _animationFirstFrame { 0.0f }; - float _animationLastFrame { 0.0f }; - bool _animationHold { false }; - bool _animationAllowTranslation { false }; - uint64_t _lastAnimated { 0 }; - int _lastKnownCurrentFrame { -1 }; - - QUrl _jointMappingURL; - bool _jointMappingCompleted { false }; - QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints - - bool _visibleDirty { true }; - bool _renderLayerDirty { false }; - bool _isGroupCulled { false }; - bool _groupCulledDirty { false }; - - void processMaterials(); - -}; - -#endif // hifi_ModelOverlay_h diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 1bf94adfa0..714db97bc2 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -15,126 +15,31 @@ #include "Application.h" -const glm::u8vec3 Overlay::DEFAULT_OVERLAY_COLOR = { 255, 255, 255 }; -const float Overlay::DEFAULT_ALPHA = 0.7f; - Overlay::Overlay() : _renderItemID(render::Item::INVALID_ITEM_ID), - _isLoaded(true), - _alpha(DEFAULT_ALPHA), - _pulse(1.0f), - _pulseMax(0.0f), - _pulseMin(0.0f), - _pulsePeriod(1.0f), - _pulseDirection(1.0f), - _lastPulseUpdate(usecTimestampNow()), - _alphaPulse(0.0f), - _colorPulse(0.0f), - _color(DEFAULT_OVERLAY_COLOR), _visible(true) { } Overlay::Overlay(const Overlay* overlay) : _renderItemID(render::Item::INVALID_ITEM_ID), - _isLoaded(overlay->_isLoaded), - _alpha(overlay->_alpha), - _pulse(overlay->_pulse), - _pulseMax(overlay->_pulseMax), - _pulseMin(overlay->_pulseMin), - _pulsePeriod(overlay->_pulsePeriod), - _pulseDirection(overlay->_pulseDirection), - _lastPulseUpdate(usecTimestampNow()), - _alphaPulse(overlay->_alphaPulse), - _colorPulse(overlay->_colorPulse), - _color(overlay->_color), _visible(overlay->_visible) { } -Overlay::~Overlay() { -} - void Overlay::setProperties(const QVariantMap& properties) { - bool valid; - auto color = u8vec3FromVariant(properties["color"], valid); - if (valid) { - _color = color; - } - - if (properties["alpha"].isValid()) { - setAlpha(properties["alpha"].toFloat()); - } - - if (properties["pulseMax"].isValid()) { - setPulseMax(properties["pulseMax"].toFloat()); - } - - if (properties["pulseMin"].isValid()) { - setPulseMin(properties["pulseMin"].toFloat()); - } - - if (properties["pulsePeriod"].isValid()) { - setPulsePeriod(properties["pulsePeriod"].toFloat()); - } - - if (properties["alphaPulse"].isValid()) { - setAlphaPulse(properties["alphaPulse"].toFloat()); - } - - if (properties["colorPulse"].isValid()) { - setColorPulse(properties["colorPulse"].toFloat()); - } - if (properties["visible"].isValid()) { bool visible = properties["visible"].toBool(); setVisible(visible); } } -// JSDoc for copying to @typedefs of overlay types that inherit Overlay. -/**jsdoc - * @property {string} type=TODO - Has the value "TODO". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - */ QVariant Overlay::getProperty(const QString& property) { if (property == "type") { return QVariant(getType()); } - if (property == "color") { - return u8vec3ColortoVariant(_color); - } - if (property == "alpha") { - return _alpha; - } - if (property == "pulseMax") { - return _pulseMax; - } - if (property == "pulseMin") { - return _pulseMin; - } - if (property == "pulsePeriod") { - return _pulsePeriod; - } - if (property == "alphaPulse") { - return _alphaPulse; - } - if (property == "colorPulse") { - return _colorPulse; + if (property == "id") { + return getID(); } if (property == "visible") { return _visible; @@ -143,67 +48,6 @@ QVariant Overlay::getProperty(const QString& property) { return QVariant(); } -glm::u8vec3 Overlay::getColor() { - if (_colorPulse == 0.0f) { - return _color; - } - - float pulseLevel = updatePulse(); - glm::u8vec3 result = _color; - if (_colorPulse < 0.0f) { - result.x *= (1.0f - pulseLevel); - result.y *= (1.0f - pulseLevel); - result.z *= (1.0f - pulseLevel); - } else { - result.x *= pulseLevel; - result.y *= pulseLevel; - result.z *= pulseLevel; - } - return result; -} - -float Overlay::getAlpha() { - if (_alphaPulse == 0.0f) { - return _alpha; - } - float pulseLevel = updatePulse(); - return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); -} - -// pulse travels from min to max, then max to min in one period. -float Overlay::updatePulse() { - if (_pulsePeriod <= 0.0f) { - return _pulse; - } - quint64 now = usecTimestampNow(); - quint64 elapsedUSecs = (now - _lastPulseUpdate); - float elapsedSeconds = (float)elapsedUSecs / (float)USECS_PER_SECOND; - float elapsedPeriods = elapsedSeconds / _pulsePeriod; - - // we can safely remove any "full" periods, since those just rotate us back - // to our final pulse level - elapsedPeriods = fmod(elapsedPeriods, 1.0f); - _lastPulseUpdate = now; - - float pulseDistance = (_pulseMax - _pulseMin); - float pulseDistancePerPeriod = pulseDistance * 2.0f; - - float pulseDelta = _pulseDirection * pulseDistancePerPeriod * elapsedPeriods; - float newPulse = _pulse + pulseDelta; - float limit = (_pulseDirection > 0.0f) ? _pulseMax : _pulseMin; - float passedLimit = (_pulseDirection > 0.0f) ? (newPulse >= limit) : (newPulse <= limit); - - if (passedLimit) { - float pulseDeltaToLimit = newPulse - limit; - float pulseDeltaFromLimitBack = pulseDelta - pulseDeltaToLimit; - pulseDelta = -pulseDeltaFromLimitBack; - _pulseDirection *= -1.0f; - } - _pulse += pulseDelta; - - return _pulse; -} - bool Overlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { _renderItemID = scene->allocateID(); transaction.resetItem(_renderItemID, std::make_shared(overlay)); @@ -215,37 +59,6 @@ void Overlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePoint render::Item::clearID(_renderItemID); } -QScriptValue OverlayIDtoScriptValue(QScriptEngine* engine, const OverlayID& id) { - return quuidToScriptValue(engine, id); -} - -void OverlayIDfromScriptValue(const QScriptValue &object, OverlayID& id) { - quuidFromScriptValue(object, id); -} - -QVector qVectorOverlayIDFromScriptValue(const QScriptValue& array) { - if (!array.isArray()) { - return QVector(); - } - QVector newVector; - int length = array.property("length").toInteger(); - newVector.reserve(length); - for (int i = 0; i < length; i++) { - newVector << OverlayID(array.property(i).toString()); - } - return newVector; -} - -void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { - std::lock_guard lock(_materialsLock); - _materials[parentMaterialName].push(material); -} - -void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { - std::lock_guard lock(_materialsLock); - _materials[parentMaterialName].remove(material); -} - render::ItemKey Overlay::getKey() { auto builder = render::ItemKey::Builder().withTypeShape().withTypeMeta(); @@ -256,8 +69,7 @@ render::ItemKey Overlay::getKey() { builder.withInvisible(); } - // always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view - render::hifi::Tag viewTagBits = getIsVisibleInSecondaryCamera() ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW; + render::hifi::Tag viewTagBits = render::hifi::TAG_ALL_VIEWS; builder.withTagBits(viewTagBits); return builder.build(); diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 8e430f7e85..ee6e281193 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -13,13 +13,6 @@ #include -class OverlayID : public QUuid { -public: - OverlayID() : QUuid() {} - OverlayID(QString v) : QUuid(v) {} - OverlayID(QUuid v) : QUuid(v) {} -}; - class Overlay : public QObject { Q_OBJECT @@ -30,12 +23,10 @@ public: Overlay(); Overlay(const Overlay* overlay); - ~Overlay(); - virtual OverlayID getOverlayID() const { return _overlayID; } - virtual void setOverlayID(OverlayID overlayID) { _overlayID = overlayID; } + virtual QUuid getID() const { return _id; } + virtual void setID(const QUuid& id) { _id = id; } - virtual void update(float deltatime) {} virtual void render(RenderArgs* args) = 0; virtual render::ItemKey getKey(); @@ -51,36 +42,13 @@ public: // getters virtual QString getType() const = 0; - virtual bool is3D() const = 0; - bool isLoaded() { return _isLoaded; } + bool isLoaded() { return true; } bool getVisible() const { return _visible; } - virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; - virtual bool getIsVisibleInSecondaryCamera() const { return false; } - - glm::u8vec3 getColor(); - float getAlpha(); - - float getPulseMax() const { return _pulseMax; } - float getPulseMin() const { return _pulseMin; } - float getPulsePeriod() const { return _pulsePeriod; } - float getPulseDirection() const { return _pulseDirection; } - - float getColorPulse() const { return _colorPulse; } - float getAlphaPulse() const { return _alphaPulse; } // setters virtual void setVisible(bool visible) { _visible = visible; } - void setDrawHUDLayer(bool drawHUDLayer); - void setColor(const glm::u8vec3& color) { _color = color; } - void setAlpha(float alpha) { _alpha = alpha; } - - void setPulseMax(float value) { _pulseMax = value; } - void setPulseMin(float value) { _pulseMin = value; } - void setPulsePeriod(float value) { _pulsePeriod = value; } - void setPulseDirection(float value) { _pulseDirection = value; } - - void setColorPulse(float value) { _colorPulse = value; } - void setAlphaPulse(float value) { _alphaPulse = value; } + unsigned int getStackOrder() const { return _stackOrder; } + void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } Q_INVOKABLE virtual void setProperties(const QVariantMap& properties); Q_INVOKABLE virtual Overlay* createClone() const = 0; @@ -89,43 +57,14 @@ public: render::ItemID getRenderItemID() const { return _renderItemID; } void setRenderItemID(render::ItemID renderItemID) { _renderItemID = renderItemID; } - unsigned int getStackOrder() const { return _stackOrder; } - void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } - - virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); - virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); - protected: - float updatePulse(); - - render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID }; - - bool _isLoaded; - float _alpha; - - float _pulse; - float _pulseMax; - float _pulseMin; - float _pulsePeriod; - float _pulseDirection; - quint64 _lastPulseUpdate; - - float _alphaPulse; // ratio of the pulse to the alpha - float _colorPulse; // ratio of the pulse to the color - - glm::u8vec3 _color; - bool _visible; // should the overlay be drawn at all + render::ItemID _renderItemID { render::Item::INVALID_ITEM_ID }; + bool _visible; unsigned int _stackOrder { 0 }; - static const glm::u8vec3 DEFAULT_OVERLAY_COLOR; - static const float DEFAULT_ALPHA; - - std::unordered_map _materials; - std::mutex _materialsLock; - private: - OverlayID _overlayID; // only used for non-3d overlays + QUuid _id; }; namespace render { @@ -136,10 +75,4 @@ namespace render { template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems); } -Q_DECLARE_METATYPE(OverlayID); -Q_DECLARE_METATYPE(QVector); -QScriptValue OverlayIDtoScriptValue(QScriptEngine* engine, const OverlayID& id); -void OverlayIDfromScriptValue(const QScriptValue& object, OverlayID& id); -QVector qVectorOverlayIDFromScriptValue(const QScriptValue& array); - #endif // hifi_Overlay_h diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index 3175df92f1..54ab52b469 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -17,15 +17,12 @@ class Overlay2D : public Overlay { Q_OBJECT - + public: Overlay2D() {} Overlay2D(const Overlay2D* overlay2D); - - virtual AABox getBounds() const override; - - virtual bool is3D() const override { return false; } + virtual AABox getBounds() const override; virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return 1; } // getters @@ -34,7 +31,7 @@ public: int getWidth() const { return _bounds.width(); } int getHeight() const { return _bounds.height(); } const QRect& getBoundingRect() const { return _bounds; } - + // setters void setX(int x) { _bounds.setX(x); } void setY(int y) { _bounds.setY(y); } diff --git a/interface/src/ui/overlays/OverlayTransformNode.cpp b/interface/src/ui/overlays/OverlayTransformNode.cpp deleted file mode 100644 index 817b6af305..0000000000 --- a/interface/src/ui/overlays/OverlayTransformNode.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// -// Created by Sabrina Shanman 9/5/2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "OverlayTransformNode.h" - -template<> -glm::vec3 BaseNestableTransformNode::getActualScale(const std::shared_ptr& nestablePointer) const { - return nestablePointer->getBounds().getScale(); -} \ No newline at end of file diff --git a/interface/src/ui/overlays/OverlayTransformNode.h b/interface/src/ui/overlays/OverlayTransformNode.h deleted file mode 100644 index 11c3415828..0000000000 --- a/interface/src/ui/overlays/OverlayTransformNode.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Created by Sabrina Shanman 9/5/2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_OverlayTransformNode_h -#define hifi_OverlayTransformNode_h - -#include "NestableTransformNode.h" - -#include "Base3DOverlay.h" - -// For 3D overlays only -class OverlayTransformNode : public BaseNestableTransformNode { -public: - OverlayTransformNode(std::weak_ptr spatiallyNestable, int jointIndex) : BaseNestableTransformNode(spatiallyNestable, jointIndex) {}; -}; - -#endif // hifi_OverlayTransformNode_h \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 754c8d26a9..e1708c14fe 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -21,27 +21,27 @@ #include "Application.h" #include "InterfaceLogging.h" -#include "Image3DOverlay.h" -#include "Circle3DOverlay.h" -#include "Cube3DOverlay.h" -#include "Shape3DOverlay.h" + #include "ImageOverlay.h" -#include "Line3DOverlay.h" -#include "ModelOverlay.h" -#include "Rectangle3DOverlay.h" -#include "Sphere3DOverlay.h" -#include "Grid3DOverlay.h" #include "TextOverlay.h" #include "RectangleOverlay.h" -#include "Text3DOverlay.h" -#include "Web3DOverlay.h" + +#include +#include +#include +#include + +#include +#include "VariantMapToScriptValue.h" + #include "ui/Keyboard.h" #include -#include - Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") +std::unordered_map Overlays::_entityToOverlayTypes; +std::unordered_map Overlays::_overlayToEntityTypes; + Overlays::Overlays() { auto pointerManager = DependencyManager::get(); connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); @@ -50,41 +50,53 @@ Overlays::Overlays() { connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent); connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMovePointerEvent); connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleasePointerEvent); + + ADD_TYPE_MAP(Box, cube); + ADD_TYPE_MAP(Sphere, sphere); + _overlayToEntityTypes["rectangle3d"] = "Shape"; + ADD_TYPE_MAP(Shape, shape); + ADD_TYPE_MAP(Model, model); + ADD_TYPE_MAP(Text, text3d); + ADD_TYPE_MAP(Image, image3d); + _overlayToEntityTypes["billboard"] = "Image"; + ADD_TYPE_MAP(Web, web3d); + ADD_TYPE_MAP(PolyLine, line3d); + ADD_TYPE_MAP(Grid, grid); + ADD_TYPE_MAP(Gizmo, circle3d); + + auto mouseRayPick = std::make_shared(Vectors::ZERO, Vectors::UP, + PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES) | + PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE)), 0.0f, true); + mouseRayPick->parentTransform = std::make_shared(); + mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE); + _mouseRayPickID = DependencyManager::get()->addPick(PickQuery::Ray, mouseRayPick); } void Overlays::cleanupAllOverlays() { _shuttingDown = true; - QMap overlaysHUD; - QMap overlaysWorld; + QMap overlays; { QMutexLocker locker(&_mutex); - overlaysHUD.swap(_overlaysHUD); - overlaysWorld.swap(_overlaysWorld); + overlays.swap(_overlays); } - foreach(Overlay::Pointer overlay, overlaysHUD) { - _overlaysToDelete.push_back(overlay); - } - foreach(Overlay::Pointer overlay, overlaysWorld) { + foreach(Overlay::Pointer overlay, overlays) { _overlaysToDelete.push_back(overlay); } cleanupOverlaysToDelete(); } void Overlays::init() { + auto entityScriptingInterface = DependencyManager::get(); + connect(this, &Overlays::hoverEnterOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity); + connect(this, &Overlays::hoverOverOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity); + connect(this, &Overlays::hoverLeaveOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity); + connect(this, &Overlays::mousePressOnOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity); + connect(this, &Overlays::mouseMoveOnOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity); + connect(this, &Overlays::mouseReleaseOnOverlay, entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity); } void Overlays::update(float deltatime) { - { - QMutexLocker locker(&_mutex); - foreach(const auto& thisOverlay, _overlaysHUD) { - thisOverlay->update(deltatime); - } - foreach(const auto& thisOverlay, _overlaysWorld) { - thisOverlay->update(deltatime); - } - } - cleanupOverlaysToDelete(); } @@ -110,7 +122,7 @@ void Overlays::cleanupOverlaysToDelete() { } } -void Overlays::renderHUD(RenderArgs* renderArgs) { +void Overlays::render(RenderArgs* renderArgs) { PROFILE_RANGE(render_overlays, __FUNCTION__); gpu::Batch& batch = *renderArgs->_batch; @@ -123,7 +135,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000); QMutexLocker locker(&_mutex); - foreach(Overlay::Pointer thisOverlay, _overlaysHUD) { + foreach(Overlay::Pointer thisOverlay, _overlays) { // Reset all batch pipeline settings between overlay geometryCache->useSimpleDrawPipeline(batch); @@ -144,203 +156,699 @@ void Overlays::enable() { _enabled = true; } -// Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder -// class on packet processing threads -Overlay::Pointer Overlays::getOverlay(OverlayID id) const { +Overlay::Pointer Overlays::take2DOverlay(const QUuid& id) { if (_shuttingDown) { return nullptr; } QMutexLocker locker(&_mutex); - if (_overlaysHUD.contains(id)) { - return _overlaysHUD[id]; - } else if (_overlaysWorld.contains(id)) { - return _overlaysWorld[id]; + auto overlayIter = _overlays.find(id); + if (overlayIter != _overlays.end()) { + return _overlays.take(id); } return nullptr; } -OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties) { +Overlay::Pointer Overlays::get2DOverlay(const QUuid& id) const { if (_shuttingDown) { - return UNKNOWN_OVERLAY_ID; + return nullptr; + } + + QMutexLocker locker(&_mutex); + auto overlayIter = _overlays.find(id); + if (overlayIter != _overlays.end()) { + return overlayIter.value(); + } + return nullptr; +} + +QString Overlays::entityToOverlayType(const QString& type) { + auto iter = _entityToOverlayTypes.find(type); + if (iter != _entityToOverlayTypes.end()) { + return iter->second; + } + return "unknown"; +} + +QString Overlays::overlayToEntityType(const QString& type) { + auto iter = _overlayToEntityTypes.find(type); + if (iter != _overlayToEntityTypes.end()) { + return iter->second; + } + return "Unknown"; +} + +#define SET_OVERLAY_PROP_DEFAULT(o, d) \ + { \ + if (add && !overlayProps.contains(#o)) { \ + overlayProps[#o] = d; \ + } \ + } + +#define RENAME_PROP(o, e) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + overlayProps[#e] = iter.value(); \ + } \ + } + +#define RENAME_PROP_CONVERT(o, e, C) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + overlayProps[#e] = C(iter.value()); \ + } \ + } + +#define OVERLAY_TO_GROUP_ENTITY_PROP(o, g, e) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + if (!overlayProps.contains(#g)) { \ + overlayProps[#g] = QVariantMap(); \ + } \ + auto map = overlayProps[#g].toMap(); \ + map[#e] = iter.value(); \ + overlayProps[#g] = map; \ + } \ + } + +#define OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(o, g, e, d) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + if (!overlayProps.contains(#g)) { \ + overlayProps[#g] = QVariantMap(); \ + } \ + auto map = overlayProps[#g].toMap(); \ + map[#e] = iter.value(); \ + overlayProps[#g] = map; \ + } else if (add) { \ + if (!overlayProps.contains(#g)) { \ + overlayProps[#g] = QVariantMap(); \ + } \ + auto map = overlayProps[#g].toMap(); \ + map[#e] = d; \ + overlayProps[#g] = map; \ + } \ + } + +#define OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(o, e, d, C) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + overlayProps[#e] = C(iter.value()); \ + } else if (add) { \ + overlayProps[#e] = C(d); \ + } \ + } + +#define OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(o, g, e, C) \ + { \ + auto iter = overlayProps.find(#o); \ + if (iter != overlayProps.end()) { \ + if (!overlayProps.contains(#g)) { \ + overlayProps[#g] = QVariantMap(); \ + } \ + auto map = overlayProps[#g].toMap(); \ + map[#e] = C(iter.value()); \ + overlayProps[#g] = map; \ + } \ + } + +#define GROUP_ENTITY_TO_OVERLAY_PROP(g, e, o) \ + { \ + auto iter = overlayProps.find(#g); \ + if (iter != overlayProps.end()) { \ + auto map = iter.value().toMap(); \ + auto iter2 = map.find(#e); \ + if (iter2 != map.end()) { \ + overlayProps[#o] = iter2.value(); \ + } \ + } \ + } + +#define GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(g, e, o, C) \ + { \ + auto iter = overlayProps.find(#g); \ + if (iter != overlayProps.end()) { \ + auto map = iter.value().toMap(); \ + auto iter2 = map.find(#e); \ + if (iter2 != map.end()) { \ + overlayProps[#o] = C(iter2.value()); \ + } \ + } \ + } + +static QHash savedRotations = QHash(); + +EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id) { + glm::quat rotation; + return convertOverlayToEntityProperties(overlayProps, rotation, type, add, id); +} + +EntityItemProperties Overlays::convertOverlayToEntityProperties(QVariantMap& overlayProps, glm::quat& rotationToSave, const QString& type, bool add, const QUuid& id) { + overlayProps["type"] = type; + + SET_OVERLAY_PROP_DEFAULT(alpha, 0.7); + if (type != "PolyLine") { + RENAME_PROP(p1, position); + RENAME_PROP(start, position); + } + RENAME_PROP(point, position); + RENAME_PROP(scale, dimensions); + RENAME_PROP(size, dimensions); + RENAME_PROP(orientation, rotation); + RENAME_PROP(localOrientation, localRotation); + RENAME_PROP(ignoreRayIntersection, ignorePickIntersection); + + RENAME_PROP_CONVERT(drawInFront, renderLayer, [](const QVariant& v) { return v.toBool() ? "front" : "world"; }); + RENAME_PROP_CONVERT(drawHUDLayer, renderLayer, [=](const QVariant& v) { + bool f = v.toBool(); + if (f) { + return QVariant("hud"); + } else if (overlayProps.contains("renderLayer")) { + return overlayProps["renderLayer"]; + } + return QVariant("world"); + }); + + OVERLAY_TO_GROUP_ENTITY_PROP_DEFAULT(grabbable, grab, grabbable, false); + + OVERLAY_TO_GROUP_ENTITY_PROP(pulseMin, pulse, min); + OVERLAY_TO_GROUP_ENTITY_PROP(pulseMax, pulse, max); + OVERLAY_TO_GROUP_ENTITY_PROP(pulsePeriod, pulse, period); + OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(colorPulse, pulse, colorMode, [](const QVariant& v) { + float f = v.toFloat(); + if (f > 0.0f) { + return "in"; + } else if (f < 0.0f) { + return "out"; + } + return "none"; + }); + OVERLAY_TO_GROUP_ENTITY_PROP_CONVERT(alphaPulse, pulse, alphaMode, [](const QVariant& v) { + float f = v.toFloat(); + if (f > 0.0f) { + return "in"; + } else if (f < 0.0f) { + return "out"; + } + return "none"; + }); + + if (type == "Shape" || type == "Box" || type == "Sphere" || type == "Gizmo") { + RENAME_PROP(solid, isSolid); + RENAME_PROP(isFilled, isSolid); + RENAME_PROP(filled, isSolid); + OVERLAY_TO_ENTITY_PROP_CONVERT_DEFAULT(isSolid, primitiveMode, false, [](const QVariant& v) { return v.toBool() ? "solid" : "lines"; }); + + RENAME_PROP(wire, isWire); + RENAME_PROP_CONVERT(isWire, primitiveMode, [](const QVariant& v) { return v.toBool() ? "lines" : "solid"; }); + } + + if (type == "Shape") { + SET_OVERLAY_PROP_DEFAULT(shape, "Hexagon"); + } else if (type == "Model") { + RENAME_PROP(url, modelURL); + RENAME_PROP(animationSettings, animation); + } else if (type == "Image") { + RENAME_PROP(url, imageURL); + } else if (type == "Web") { + RENAME_PROP(url, sourceUrl); + RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "Mouse" ? "mouse" : "touch"; }); + } else if (type == "Gizmo") { + RENAME_PROP(radius, outerRadius); + if (add || overlayProps.contains("outerRadius")) { + float ratio = 2.0f; + { + auto iter = overlayProps.find("outerRadius"); + if (iter != overlayProps.end()) { + ratio = iter.value().toFloat() / 0.5f; + } + } + glm::vec3 dimensions = glm::vec3(1.0f); + { + auto iter = overlayProps.find("dimensions"); + if (iter != overlayProps.end()) { + dimensions = vec3FromVariant(iter.value()); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_DIMENSIONS; + dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); + } + } + overlayProps["dimensions"] = vec3toVariant(ratio * dimensions); + } + + if (add || overlayProps.contains("rotation")) { + glm::quat rotation; + { + auto iter = overlayProps.find("rotation"); + if (iter != overlayProps.end()) { + rotation = quatFromVariant(iter.value()); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_ROTATION; + rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getRotation(); + } + } + overlayProps["rotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); + } + if (add || overlayProps.contains("localRotation")) { + glm::quat rotation; + { + auto iter = overlayProps.find("localRotation"); + if (iter != overlayProps.end()) { + rotation = quatFromVariant(iter.value()); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_LOCAL_ROTATION; + rotation = DependencyManager::get()->getEntityProperties(id, desiredProperties).getLocalRotation(); + } + } + overlayProps["localRotation"] = quatToVariant(glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation); + } + + { + RENAME_PROP(color, innerStartColor); + RENAME_PROP(color, innerEndColor); + RENAME_PROP(color, outerStartColor); + RENAME_PROP(color, outerEndColor); + + RENAME_PROP(startColor, innerStartColor); + RENAME_PROP(startColor, outerStartColor); + + RENAME_PROP(endColor, innerEndColor); + RENAME_PROP(endColor, outerEndColor); + + RENAME_PROP(innerColor, innerStartColor); + RENAME_PROP(innerColor, innerEndColor); + + RENAME_PROP(outerColor, outerStartColor); + RENAME_PROP(outerColor, outerEndColor); + } + + { + RENAME_PROP(alpha, innerStartAlpha); + RENAME_PROP(alpha, innerEndAlpha); + RENAME_PROP(alpha, outerStartAlpha); + RENAME_PROP(alpha, outerEndAlpha); + + RENAME_PROP(startAlpha, innerStartAlpha); + RENAME_PROP(startAlpha, outerStartAlpha); + + RENAME_PROP(endAlpha, innerEndAlpha); + RENAME_PROP(endAlpha, outerEndAlpha); + + RENAME_PROP(innerAlpha, innerStartAlpha); + RENAME_PROP(innerAlpha, innerEndAlpha); + + RENAME_PROP(outerAlpha, outerStartAlpha); + RENAME_PROP(outerAlpha, outerEndAlpha); + } + + OVERLAY_TO_GROUP_ENTITY_PROP(startAt, ring, startAngle); + OVERLAY_TO_GROUP_ENTITY_PROP(endAt, ring, endAngle); + OVERLAY_TO_GROUP_ENTITY_PROP(innerRadius, ring, innerRadius); + + OVERLAY_TO_GROUP_ENTITY_PROP(innerStartColor, ring, innerStartColor); + OVERLAY_TO_GROUP_ENTITY_PROP(innerEndColor, ring, innerEndColor); + OVERLAY_TO_GROUP_ENTITY_PROP(outerStartColor, ring, outerStartColor); + OVERLAY_TO_GROUP_ENTITY_PROP(outerEndColor, ring, outerEndColor); + OVERLAY_TO_GROUP_ENTITY_PROP(innerStartAlpha, ring, innerStartAlpha); + OVERLAY_TO_GROUP_ENTITY_PROP(innerEndAlpha, ring, innerEndAlpha); + OVERLAY_TO_GROUP_ENTITY_PROP(outerStartAlpha, ring, outerStartAlpha); + OVERLAY_TO_GROUP_ENTITY_PROP(outerEndAlpha, ring, outerEndAlpha); + + OVERLAY_TO_GROUP_ENTITY_PROP(hasTickMarks, ring, hasTickMarks); + OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksAngle, ring, majorTickMarksAngle); + OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksAngle, ring, minorTickMarksAngle); + OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksLength, ring, majorTickMarksLength); + OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksLength, ring, minorTickMarksLength); + OVERLAY_TO_GROUP_ENTITY_PROP(majorTickMarksColor, ring, majorTickMarksColor); + OVERLAY_TO_GROUP_ENTITY_PROP(minorTickMarksColor, ring, minorTickMarksColor); + } else if (type == "PolyLine") { + RENAME_PROP(startPoint, p1); + RENAME_PROP(start, p1); + RENAME_PROP(endPoint, p2); + RENAME_PROP(end, p2); + + RENAME_PROP(p1, position); + RENAME_PROP_CONVERT(p1, p1, [](const QVariant& v) { return vec3toVariant(glm::vec3(0.0f)); }); + RENAME_PROP_CONVERT(p2, p2, [=](const QVariant& v) { + glm::vec3 position; + auto iter2 = overlayProps.find("position"); + if (iter2 != overlayProps.end()) { + position = vec3FromVariant(iter2.value()); + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_POSITION; + position = DependencyManager::get()->getEntityProperties(id, desiredProperties).getPosition(); + } + return vec3toVariant(vec3FromVariant(v) - position); + }); + + RENAME_PROP(localStart, p1); + RENAME_PROP(localEnd, p2); + + { + QVariantList points; + { + auto iter = overlayProps.find("p1"); + if (iter != overlayProps.end()) { + points.push_back(iter.value()); + } + } + { + auto iter = overlayProps.find("p2"); + if (iter != overlayProps.end()) { + points.push_back(iter.value()); + } + } + overlayProps["linePoints"] = points; + } + { + auto iter = overlayProps.find("lineWidth"); + if (iter != overlayProps.end()) { + QVariantList widths; + QVariant width = iter.value(); + widths.append(width); + widths.append(width); + overlayProps["strokeWidths"] = widths; + } + } + + RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toFloat() > 0.0f ? true : false; }); + SET_OVERLAY_PROP_DEFAULT(faceCamera, true); + { + QVariantList normals; + normals.append(vec3toVariant(Vectors::UP)); + normals.append(vec3toVariant(Vectors::UP)); + SET_OVERLAY_PROP_DEFAULT(normals, normals); + } + + SET_OVERLAY_PROP_DEFAULT(textures, PathUtils::resourcesUrl() + "images/whitePixel.png"); + } + + if (type == "Text" || type == "Image" || type == "Grid" || type == "Web") { + glm::quat originalRotation = ENTITY_ITEM_DEFAULT_ROTATION; + { + auto iter = overlayProps.find("rotation"); + if (iter != overlayProps.end()) { + originalRotation = quatFromVariant(iter.value()); + } else { + iter = overlayProps.find("localRotation"); + if (iter != overlayProps.end()) { + originalRotation = quatFromVariant(iter.value()); + } else if (!add) { + auto iter2 = savedRotations.find(id); + if (iter2 != savedRotations.end()) { + originalRotation = iter2.value(); + } + } + } + } + + if (!add) { + savedRotations[id] = originalRotation; + } else { + rotationToSave = originalRotation; + } + + glm::vec3 dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; + { + auto iter = overlayProps.find("dimensions"); + if (iter != overlayProps.end()) { + bool valid = false; + dimensions = vec3FromVariant(iter.value(), valid); + if (!valid) { + dimensions = glm::vec3(vec2FromVariant(iter.value()), 0.0f); + } + } else if (!add) { + EntityPropertyFlags desiredProperties; + desiredProperties += PROP_DIMENSIONS; + dimensions = DependencyManager::get()->getEntityProperties(id, desiredProperties).getDimensions(); + } + } + + bool rotateX = dimensions.y < 0.0f; + bool rotateY = dimensions.x < 0.0f; + + { + glm::quat rotation = originalRotation; + if (rotateX) { + rotation = glm::angleAxis((float)M_PI, rotation * Vectors::RIGHT) * rotation; + } + if (rotateY) { + rotation = glm::angleAxis((float)M_PI, rotation * Vectors::UP) * rotation; + } + + overlayProps["localRotation"] = quatToVariant(rotation); + overlayProps["dimensions"] = vec3toVariant(glm::abs(dimensions)); + } + } + + QScriptEngine scriptEngine; + QScriptValue props = variantMapToScriptValue(overlayProps, scriptEngine); + EntityItemProperties toReturn; + EntityItemPropertiesFromScriptValueHonorReadOnly(props, toReturn); + return toReturn; +} + +QVariantMap Overlays::convertEntityToOverlayProperties(const EntityItemProperties& properties) { + QScriptEngine scriptEngine; + QVariantMap overlayProps = EntityItemPropertiesToScriptValue(&scriptEngine, properties).toVariant().toMap(); + + QString type = overlayProps["type"].toString(); + overlayProps["type"] = entityToOverlayType(type); + + if (type != "PolyLine") { + RENAME_PROP(position, p1); + RENAME_PROP(position, start); + } + RENAME_PROP(position, point); + RENAME_PROP(dimensions, scale); + RENAME_PROP(dimensions, size); + RENAME_PROP(ignorePickIntersection, ignoreRayIntersection); + + { + RENAME_PROP_CONVERT(primitiveMode, isSolid, [](const QVariant& v) { return v.toString() == "solid" ? true : false; }); + RENAME_PROP(isSolid, solid); + RENAME_PROP(isSolid, isFilled); + RENAME_PROP(isSolid, filled); + + RENAME_PROP_CONVERT(primitiveMode, isWire, [](const QVariant& v) { return v.toString() == "lines" ? true : false; }); + RENAME_PROP(isWire, wire); + } + + RENAME_PROP_CONVERT(renderLayer, drawInFront, [](const QVariant& v) { return v.toString() == "front" ? true : false; }); + RENAME_PROP_CONVERT(renderLayer, drawHUDLayer, [](const QVariant& v) { return v.toString() == "hud" ? true : false; }); + + GROUP_ENTITY_TO_OVERLAY_PROP(grab, grabbable, grabbable); + + GROUP_ENTITY_TO_OVERLAY_PROP(pulse, min, pulseMin); + GROUP_ENTITY_TO_OVERLAY_PROP(pulse, max, pulseMax); + GROUP_ENTITY_TO_OVERLAY_PROP(pulse, period, pulsePeriod); + GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, colorMode, colorPulse, [](const QVariant& v) { + QString f = v.toString(); + if (f == "in") { + return 1.0f; + } else if (f == "out") { + return -1.0f; + } + return 0.0f; + }); + GROUP_ENTITY_TO_OVERLAY_PROP_CONVERT(pulse, alphaMode, alphaPulse, [](const QVariant& v) { + QString f = v.toString(); + if (f == "in") { + return 1.0f; + } else if (f == "out") { + return -1.0f; + } + return 0.0f; + }); + + if (type == "Model") { + RENAME_PROP(modelURL, url); + RENAME_PROP(animation, animationSettings); + } else if (type == "Image") { + RENAME_PROP(imageURL, url); + } else if (type == "Web") { + RENAME_PROP(sourceUrl, url); + RENAME_PROP_CONVERT(inputMode, inputMode, [](const QVariant& v) { return v.toString() == "mouse" ? "Mouse" : "Touch"; }); + } else if (type == "Gizmo") { + RENAME_PROP_CONVERT(dimensions, outerRadius, [](const QVariant& v) { return 2.0f * vec3FromVariant(v).x; }); + RENAME_PROP(outerRadius, radius); + + RENAME_PROP_CONVERT(rotation, rotation, [](const QVariant& v) { + glm::quat rot = quatFromVariant(v); + return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); + }); + RENAME_PROP_CONVERT(localRotation, localRotation, [](const QVariant& v) { + glm::quat rot = quatFromVariant(v); + return quatToVariant(glm::angleAxis((float)M_PI_2, rot * Vectors::RIGHT) * rot); + }); + + GROUP_ENTITY_TO_OVERLAY_PROP(ring, startAngle, startAt); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, endAngle, endAt); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerRadius, innerRadius); + + GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartColor, innerStartColor); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndColor, innerEndColor); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartColor, outerStartColor); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndColor, outerEndColor); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerStartAlpha, innerStartAlpha); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, innerEndAlpha, innerEndAlpha); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerStartAlpha, outerStartAlpha); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, outerEndAlpha, outerEndAlpha); + + GROUP_ENTITY_TO_OVERLAY_PROP(ring, hasTickMarks, hasTickMarks); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksAngle, majorTickMarksAngle); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksAngle, minorTickMarksAngle); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksLength, majorTickMarksLength); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksLength, minorTickMarksLength); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, majorTickMarksColor, majorTickMarksColor); + GROUP_ENTITY_TO_OVERLAY_PROP(ring, minorTickMarksColor, minorTickMarksColor); + } else if (type == "PolyLine") { + QVector points = qVectorVec3FromScriptValue(scriptEngine.newVariant(overlayProps["linePoints"])); + glm::vec3 position = vec3FromVariant(overlayProps["position"]); + if (points.length() > 1) { + overlayProps["p1"] = vec3toVariant(points[0] + position); + overlayProps["p2"] = vec3toVariant(points[1] + position); + + overlayProps["localStart"] = vec3toVariant(points[0]); + overlayProps["localEnd"] = vec3toVariant(points[1]); + } + + RENAME_PROP(p1, startPoint); + RENAME_PROP(p1, start); + RENAME_PROP(p2, endPoint); + RENAME_PROP(p2, end); + + QVector widths = qVectorFloatFromScriptValue(scriptEngine.newVariant(overlayProps["strokeWidths"])); + if (widths.length() > 0) { + overlayProps["lineWidth"] = widths[0]; + } + + RENAME_PROP_CONVERT(glow, glow, [](const QVariant& v) { return v.toBool() ? 1.0f : 0.0f; }); + } + + // Do at the end, in case this type was rotated above + RENAME_PROP(rotation, orientation); + RENAME_PROP(localRotation, localOrientation); + + return overlayProps; +} + +QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { + if (_shuttingDown) { + return UNKNOWN_ENTITY_ID; } if (QThread::currentThread() != thread()) { - OverlayID result; + QUuid result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(QString, type), Q_ARG(QVariant, properties)); + BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); return result; } - Overlay::Pointer thisOverlay = nullptr; - - /**jsdoc - *

An overlay may be one of the following types:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Value2D/3DDescription
circle3d3DA circle.
cube3DA cube. Can also use a shape overlay to create a - * cube.
grid3DA grid of lines in a plane.
image2DAn image. Synonym: billboard.
image3d3DAn image.
line3d3DA line.
model3DA model.
rectangle2DA rectangle.
rectangle3d3DA rectangle.
shape3DA geometric shape, such as a cube, sphere, or cylinder.
sphere3DA sphere. Can also use a shape overlay to create a - * sphere.
text2DText.
text3d3DText.
web3d3DWeb content.
- *

2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are - * rendered at a position and orientation in-world.

- *

Each overlay type has different {@link Overlays.OverlayProperties|OverlayProperties}.

- * @typedef {string} Overlays.OverlayType - */ - - /**jsdoc - *

Different overlay types have different properties:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
{@link Overlays.OverlayType|OverlayType}Overlay Properties
circle3d{@link Overlays.Circle3DProperties|Circle3DProperties}
cube{@link Overlays.CubeProperties|CubeProperties}
grid{@link Overlays.GridProperties|GridProperties}
image{@link Overlays.ImageProperties|ImageProperties}
image3d{@link Overlays.Image3DProperties|Image3DProperties}
line3d{@link Overlays.Line3DProperties|Line3DProperties}
model{@link Overlays.ModelProperties|ModelProperties}
rectangle{@link Overlays.RectangleProperties|RectangleProperties}
rectangle3d{@link Overlays.Rectangle3DProperties|Rectangle3DProperties}
shape{@link Overlays.ShapeProperties|ShapeProperties}
sphere{@link Overlays.SphereProperties|SphereProperties}
text{@link Overlays.TextProperties|TextProperties}
text3d{@link Overlays.Text3DProperties|Text3DProperties}
web3d{@link Overlays.Web3DProperties|Web3DProperties}
- * @typedef {object} Overlays.OverlayProperties - */ - + Overlay::Pointer overlay; if (type == ImageOverlay::TYPE) { #if !defined(DISABLE_QML) - thisOverlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + overlay = Overlay::Pointer(new ImageOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); #endif - } else if (type == Image3DOverlay::TYPE || type == "billboard") { // "billboard" for backwards compatibility - thisOverlay = Overlay::Pointer(new Image3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); } else if (type == TextOverlay::TYPE) { #if !defined(DISABLE_QML) - thisOverlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + overlay = Overlay::Pointer(new TextOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); #endif - } else if (type == Text3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Text3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Shape3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Shape3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Cube3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Cube3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Sphere3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Sphere3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Circle3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Circle3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Rectangle3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Rectangle3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Line3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Line3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Grid3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Grid3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == ModelOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new ModelOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); - } else if (type == Web3DOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new Web3DOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); } else if (type == RectangleOverlay::TYPE) { - thisOverlay = Overlay::Pointer(new RectangleOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); + overlay = Overlay::Pointer(new RectangleOverlay(), [](Overlay* ptr) { ptr->deleteLater(); }); } - if (thisOverlay) { - thisOverlay->setProperties(properties.toMap()); - return addOverlay(thisOverlay); + if (overlay) { + overlay->setProperties(properties.toMap()); + return add2DOverlay(overlay); } - return UNKNOWN_OVERLAY_ID; + + QString entityType = overlayToEntityType(type); + if (entityType == "Unknown") { + return UNKNOWN_ENTITY_ID; + } + + QVariantMap propertyMap = properties.toMap(); + if (type == "rectangle3d") { + propertyMap["shape"] = "Quad"; + } + glm::quat rotationToSave; + QUuid id = DependencyManager::get()->addEntityInternal(convertOverlayToEntityProperties(propertyMap, rotationToSave, entityType, true), entity::HostType::LOCAL); + if (entityType == "Text" || entityType == "Image" || entityType == "Grid" || entityType == "Web") { + savedRotations[id] = rotationToSave; + } + return id; } -OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { +QUuid Overlays::add2DOverlay(const Overlay::Pointer& overlay) { if (_shuttingDown) { - return UNKNOWN_OVERLAY_ID; + return UNKNOWN_ENTITY_ID; } - OverlayID thisID = OverlayID(QUuid::createUuid()); - overlay->setOverlayID(thisID); + QUuid thisID = QUuid::createUuid(); + overlay->setID(thisID); overlay->setStackOrder(_stackOrder++); - if (overlay->is3D()) { - { - QMutexLocker locker(&_mutex); - _overlaysWorld[thisID] = overlay; - } - - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - overlay->addToScene(overlay, scene, transaction); - scene->enqueueTransaction(transaction); - } else { + { QMutexLocker locker(&_mutex); - _overlaysHUD[thisID] = overlay; + _overlays[thisID] = overlay; } return thisID; } -OverlayID Overlays::cloneOverlay(OverlayID id) { +QUuid Overlays::cloneOverlay(const QUuid& id) { if (_shuttingDown) { - return UNKNOWN_OVERLAY_ID; + return UNKNOWN_ENTITY_ID; } if (QThread::currentThread() != thread()) { - OverlayID result; + QUuid result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(OverlayID, id)); + BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); return result; } - Overlay::Pointer thisOverlay = getOverlay(id); - - if (thisOverlay) { - OverlayID cloneId = addOverlay(Overlay::Pointer(thisOverlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); - return cloneId; + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); } - return UNKNOWN_OVERLAY_ID; // Not found + return DependencyManager::get()->cloneEntity(id); } -bool Overlays::editOverlay(OverlayID id, const QVariant& properties) { +bool Overlays::editOverlay(const QUuid& id, const QVariant& properties) { if (_shuttingDown) { return false; } - auto thisOverlay = getOverlay(id); - if (!thisOverlay) { - return false; - } + auto overlay = get2DOverlay(id); + if (overlay) { + if (QThread::currentThread() != thread()) { + // NOTE editOverlay can be called very frequently in scripts and can't afford to + // block waiting on the main thread. Additionally, no script actually + // examines the return value and does something useful with it, so use a non-blocking + // invoke and just always return true + QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(const QUuid&, id), Q_ARG(const QVariant&, properties)); + return true; + } - if (!thisOverlay->is3D() && QThread::currentThread() != thread()) { - // NOTE editOverlay can be called very frequently in scripts and can't afford to - // block waiting on the main thread. Additionally, no script actually - // examines the return value and does something useful with it, so use a non-blocking - // invoke and just always return true - QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(OverlayID, id), Q_ARG(QVariant, properties)); + overlay->setProperties(properties.toMap()); return true; } - thisOverlay->setProperties(properties.toMap()); - return true; + auto entityScriptingInterface = DependencyManager::get(); + auto propertyMap = properties.toMap(); + EntityItemProperties entityProperties = convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id); + return !entityScriptingInterface->editEntity(id, entityProperties).isNull(); } bool Overlays::editOverlays(const QVariant& propertiesById) { @@ -348,286 +856,229 @@ bool Overlays::editOverlays(const QVariant& propertiesById) { return false; } - bool defer2DOverlays = QThread::currentThread() != thread(); + bool deferOverlays = QThread::currentThread() != thread(); - QVariantMap deferrred; + QVariantMap deferred; const QVariantMap map = propertiesById.toMap(); - bool success = true; + auto entityScriptingInterface = DependencyManager::get(); for (const auto& key : map.keys()) { - OverlayID id = OverlayID(key); - Overlay::Pointer thisOverlay = getOverlay(id); - if (!thisOverlay) { - success = false; - continue; - } - + QUuid id = QUuid(key); const QVariant& properties = map[key]; - if (defer2DOverlays && !thisOverlay->is3D()) { - deferrred[key] = properties; - continue; + + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + if (deferOverlays) { + deferred[key] = properties; + continue; + } + overlay->setProperties(properties.toMap()); + } else { + auto propertyMap = properties.toMap(); + entityScriptingInterface->editEntity(id, convertOverlayToEntityProperties(propertyMap, entityScriptingInterface->getEntityType(id), false, id)); } - thisOverlay->setProperties(properties.toMap()); } // For 2D/QML overlays, we need to perform the edit on the main thread - if (defer2DOverlays && !deferrred.empty()) { + if (!deferred.empty()) { // NOTE see comment on editOverlay for why this is not a blocking call - QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(QVariant, deferrred)); + QMetaObject::invokeMethod(this, "editOverlays", Q_ARG(const QVariant&, deferred)); } - return success; + return true; } -void Overlays::deleteOverlay(OverlayID id) { +void Overlays::deleteOverlay(const QUuid& id) { if (_shuttingDown) { return; } - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(OverlayID, id)); + Overlay::Pointer overlay = take2DOverlay(id); + if (overlay) { + _overlaysToDelete.push_back(overlay); + emit overlayDeleted(id); return; } - Overlay::Pointer overlayToDelete; - - { - QMutexLocker locker(&_mutex); - if (_overlaysHUD.contains(id)) { - overlayToDelete = _overlaysHUD.take(id); - } else if (_overlaysWorld.contains(id)) { - overlayToDelete = _overlaysWorld.take(id); - } else { - return; - } - } - - _overlaysToDelete.push_back(overlayToDelete); + DependencyManager::get()->deleteEntity(id); emit overlayDeleted(id); } -QString Overlays::getOverlayType(OverlayID overlayId) { +QString Overlays::getOverlayType(const QUuid& id) { if (_shuttingDown) { return ""; } + if (QThread::currentThread() != thread()) { QString result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(OverlayID, overlayId)); + BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); return result; } - Overlay::Pointer overlay = getOverlay(overlayId); + Overlay::Pointer overlay = get2DOverlay(id); if (overlay) { return overlay->getType(); } - return ""; + + return entityToOverlayType(DependencyManager::get()->getEntityType(id)); } -QObject* Overlays::getOverlayObject(OverlayID id) { +QObject* Overlays::getOverlayObject(const QUuid& id) { if (QThread::currentThread() != thread()) { QObject* result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id)); + BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); return result; } - Overlay::Pointer thisOverlay = getOverlay(id); - if (thisOverlay) { - return qobject_cast(&(*thisOverlay)); + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + return qobject_cast(&(*overlay)); } - return nullptr; + + return DependencyManager::get()->getEntityObject(id); } -OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) { +QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (_shuttingDown || !_enabled) { - return UNKNOWN_OVERLAY_ID; + return UNKNOWN_ENTITY_ID; } QMutexLocker locker(&_mutex); - QMapIterator i(_overlaysHUD); + QMapIterator i(_overlays); unsigned int bestStackOrder = 0; - OverlayID bestOverlayID = UNKNOWN_OVERLAY_ID; + QUuid bestID = UNKNOWN_ENTITY_ID; while (i.hasNext()) { i.next(); auto thisOverlay = std::dynamic_pointer_cast(i.value()); if (thisOverlay && thisOverlay->getVisible() && thisOverlay->isLoaded() && thisOverlay->getBoundingRect().contains(point.x, point.y, false)) { if (thisOverlay->getStackOrder() > bestStackOrder) { - bestOverlayID = i.key(); + bestID = i.key(); bestStackOrder = thisOverlay->getStackOrder(); } } } - return bestOverlayID; + return bestID; } -OverlayPropertyResult Overlays::getProperty(OverlayID id, const QString& property) { - Overlay::Pointer thisOverlay = getOverlay(id); - OverlayPropertyResult result; - if (thisOverlay && thisOverlay->supportsGetProperty()) { - result.value = thisOverlay->getProperty(property); - } - return result; -} - -OverlayPropertyResult Overlays::getProperties(const OverlayID& id, const QStringList& properties) { - Overlay::Pointer thisOverlay = getOverlay(id); - OverlayPropertyResult result; - if (thisOverlay && thisOverlay->supportsGetProperty()) { - QVariantMap mapResult; - for (const auto& property : properties) { - mapResult.insert(property, thisOverlay->getProperty(property)); +QVariant Overlays::getProperty(const QUuid& id, const QString& property) { + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + if (overlay->supportsGetProperty()) { + return overlay->getProperty(property); } - result.value = mapResult; + return QVariant(); } - return result; + + QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); + auto propIter = overlayProperties.find(property); + if (propIter != overlayProperties.end()) { + return propIter.value(); + } + return QVariant(); } -OverlayPropertyResult Overlays::getOverlaysProperties(const QVariant& propertiesById) { - QVariantMap map = propertiesById.toMap(); - OverlayPropertyResult result; - QVariantMap resultMap; - for (const auto& key : map.keys()) { - OverlayID id = OverlayID(key); - QVariantMap overlayResult; - Overlay::Pointer thisOverlay = getOverlay(id); - if (thisOverlay && thisOverlay->supportsGetProperty()) { - QStringList propertiesToFetch = map[key].toStringList(); - for (const auto& property : propertiesToFetch) { - overlayResult[property] = thisOverlay->getProperty(property); +QVariantMap Overlays::getProperties(const QUuid& id, const QStringList& properties) { + Overlay::Pointer overlay = get2DOverlay(id); + QVariantMap result; + if (overlay) { + if (overlay->supportsGetProperty()) { + for (const auto& property : properties) { + result.insert(property, overlay->getProperty(property)); } } - resultMap[key] = overlayResult; + return result; + } + + QVariantMap overlayProperties = convertEntityToOverlayProperties(DependencyManager::get()->getEntityProperties(id)); + for (const auto& property : properties) { + auto propIter = overlayProperties.find(property); + if (propIter != overlayProperties.end()) { + result.insert(property, propIter.value()); + } } - result.value = resultMap; return result; } -OverlayPropertyResult::OverlayPropertyResult() { -} - -QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const OverlayPropertyResult& value) { - if (!value.value.isValid()) { - return QScriptValue::UndefinedValue; +QVariantMap Overlays::getOverlaysProperties(const QVariant& propertiesById) { + QVariantMap map = propertiesById.toMap(); + QVariantMap result; + for (const auto& key : map.keys()) { + result[key] = getProperties(QUuid(key), map[key].toStringList()); } - return engine->toScriptValue(value.value); + return result; } -void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value) { - value.value = object.toVariant(); -} - - RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& overlayIDsToInclude, const QScriptValue& overlayIDsToDiscard, bool visibleOnly, bool collidableOnly) { - const QVector overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); - const QVector overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); + const QVector include = qVectorEntityItemIDFromScriptValue(overlayIDsToInclude); + const QVector discard = qVectorEntityItemIDFromScriptValue(overlayIDsToDiscard); - return findRayIntersectionVector(ray, precisionPicking, - overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); + return findRayIntersectionVector(ray, precisionPicking, include, discard, visibleOnly, collidableOnly); } - RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, + const QVector& include, + const QVector& discard, bool visibleOnly, bool collidableOnly) { - float bestDistance = std::numeric_limits::max(); - bool bestIsFront = false; - bool bestIsTablet = false; - auto tabletIDs = qApp->getTabletIDs(); + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); - QMutexLocker locker(&_mutex); - RayToOverlayIntersectionResult result; - QMapIterator i(_overlaysWorld); - while (i.hasNext()) { - i.next(); - OverlayID thisID = i.key(); - auto thisOverlay = std::dynamic_pointer_cast(i.value()); - - if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || - (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { - continue; - } - - if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { - float thisDistance; - BoxFace thisFace; - glm::vec3 thisSurfaceNormal; - QVariantMap thisExtraInfo; - if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, - thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { - bool isDrawInFront = thisOverlay->getDrawInFront(); - bool isTablet = tabletIDs.contains(thisID); - if ((isDrawInFront && !bestIsFront && !bestIsTablet) - || ((isTablet || isDrawInFront || !bestIsFront) && thisDistance < bestDistance)) { - bestIsFront = isDrawInFront; - bestIsTablet = isTablet; - bestDistance = thisDistance; - result.intersects = true; - result.distance = thisDistance; - result.face = thisFace; - result.surfaceNormal = thisSurfaceNormal; - result.overlayID = thisID; - result.intersection = ray.origin + (ray.direction * thisDistance); - result.extraInfo = thisExtraInfo; - } - } - } + if (!precisionPicking) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } - return result; + + if (visibleOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); + } + + if (collidableOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); + } + auto result = DependencyManager::get()->evalRayIntersectionVector(ray, PickFilter(searchFilter), include, discard); + + RayToOverlayIntersectionResult overlayResult; + overlayResult.overlayID = result.entityID; + overlayResult.intersects = result.intersects; + overlayResult.intersection = result.intersection; + overlayResult.distance = result.distance; + overlayResult.surfaceNormal = result.surfaceNormal; + overlayResult.face = result.face; + overlayResult.extraInfo = result.extraInfo; + return overlayResult; } ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, + const QVector& include, + const QVector& discard, bool visibleOnly, bool collidableOnly) { - float bestDistance = std::numeric_limits::max(); - bool bestIsFront = false; - const QVector keyboardKeysToDiscard = DependencyManager::get()->getKeysID(); - QMutexLocker locker(&_mutex); - ParabolaToOverlayIntersectionResult result; - QMapIterator i(_overlaysWorld); - while (i.hasNext()) { - i.next(); - OverlayID thisID = i.key(); - auto thisOverlay = std::dynamic_pointer_cast(i.value()); + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); - if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || - (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID)) || - (keyboardKeysToDiscard.size() > 0 && keyboardKeysToDiscard.contains(thisID))) { - continue; - } - - if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { - float thisDistance; - BoxFace thisFace; - glm::vec3 thisSurfaceNormal; - QVariantMap thisExtraInfo; - if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance, - thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { - bool isDrawInFront = thisOverlay->getDrawInFront(); - if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) - || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { - - bestIsFront = isDrawInFront; - bestDistance = thisDistance; - result.intersects = true; - result.parabolicDistance = thisDistance; - result.face = thisFace; - result.surfaceNormal = thisSurfaceNormal; - result.overlayID = thisID; - result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance; - result.distance = glm::distance(result.intersection, parabola.origin); - result.extraInfo = thisExtraInfo; - } - } - } + if (!precisionPicking) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } - return result; + + if (visibleOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); + } + + if (collidableOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); + } + auto result = DependencyManager::get()->evalParabolaIntersectionVector(parabola, PickFilter(searchFilter), include, discard); + + ParabolaToOverlayIntersectionResult overlayResult; + overlayResult.overlayID = result.entityID; + overlayResult.intersects = result.intersects; + overlayResult.intersection = result.intersection; + overlayResult.parabolicDistance = result.parabolicDistance; + overlayResult.surfaceNormal = result.surfaceNormal; + overlayResult.face = result.face; + overlayResult.extraInfo = result.extraInfo; + return overlayResult; } QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { @@ -664,86 +1115,72 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -bool Overlays::isLoaded(OverlayID id) { +bool Overlays::isLoaded(const QUuid& id) { if (QThread::currentThread() != thread()) { bool result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id)); + BLOCKING_INVOKE_METHOD(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(const QUuid&, id)); return result; } - Overlay::Pointer thisOverlay = getOverlay(id); - if (!thisOverlay) { - return false; // not found + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + return overlay->isLoaded(); } - return thisOverlay->isLoaded(); + + return DependencyManager::get()->isLoaded(id); } -QSizeF Overlays::textSize(OverlayID id, const QString& text) { +QSizeF Overlays::textSize(const QUuid& id, const QString& text) { if (QThread::currentThread() != thread()) { QSizeF result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(OverlayID, id), Q_ARG(QString, text)); + BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(const QUuid&, id), Q_ARG(QString, text)); return result; } - Overlay::Pointer thisOverlay = getOverlay(id); - if (thisOverlay) { - if (thisOverlay->is3D()) { - if (auto text3dOverlay = std::dynamic_pointer_cast(thisOverlay)) { - return text3dOverlay->textSize(text); - } - } else { - if (auto textOverlay = std::dynamic_pointer_cast(thisOverlay)) { - return textOverlay->textSize(text); - } + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + if (auto textOverlay = std::dynamic_pointer_cast(overlay)) { + return textOverlay->textSize(text); } + return QSizeF(0.0f, 0.0f); + } else { + return DependencyManager::get()->textSize(id, text); } - return QSizeF(0.0f, 0.0f); } -bool Overlays::isAddedOverlay(OverlayID id) { - if (QThread::currentThread() != thread()) { - bool result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "isAddedOverlay", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id)); - return result; +bool Overlays::isAddedOverlay(const QUuid& id) { + Overlay::Pointer overlay = get2DOverlay(id); + if (overlay) { + return true; } - QMutexLocker locker(&_mutex); - return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); + return DependencyManager::get()->isAddedEntity(id); } -void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - mousePressPointerEvent(overlayID, event); +void Overlays::sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event) { + mousePressPointerEvent(id, event); } -void Overlays::sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - mouseReleasePointerEvent(overlayID, event); +void Overlays::sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event) { + mouseReleasePointerEvent(id, event); } -void Overlays::sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { - mouseMovePointerEvent(overlayID, event); +void Overlays::sendMouseMoveOnOverlay(const QUuid& id, const PointerEvent& event) { + mouseMovePointerEvent(id, event); } -void Overlays::sendHoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event) { - hoverEnterPointerEvent(overlayID, event); +void Overlays::sendHoverEnterOverlay(const QUuid& id, const PointerEvent& event) { + hoverEnterPointerEvent(id, event); } -void Overlays::sendHoverOverOverlay(const OverlayID& overlayID, const PointerEvent& event) { - hoverOverPointerEvent(overlayID, event); +void Overlays::sendHoverOverOverlay(const QUuid& id, const PointerEvent& event) { + hoverOverPointerEvent(id, event); } -void Overlays::sendHoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event) { - hoverLeavePointerEvent(overlayID, event); -} - -OverlayID Overlays::getKeyboardFocusOverlay() { - return qApp->getKeyboardFocusOverlay(); -} - -void Overlays::setKeyboardFocusOverlay(const OverlayID& id) { - qApp->setKeyboardFocusOverlay(id); +void Overlays::sendHoverLeaveOverlay(const QUuid& id, const PointerEvent& event) { + hoverLeavePointerEvent(id, event); } float Overlays::width() { @@ -770,28 +1207,6 @@ float Overlays::height() { return offscreenUi->getWindow()->size().height(); } -static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay, - const RayToOverlayIntersectionResult& rayPickResult) { - - // Project the intersection point onto the local xy plane of the overlay. - float distance; - glm::vec3 planePosition = position; - glm::vec3 planeNormal = rotation * Vectors::UNIT_Z; - glm::vec3 overlayDimensions = glm::vec3(dimensions.x, dimensions.y, 0.0f); - glm::vec3 rayDirection = pickRay.direction; - glm::vec3 rayStart = pickRay.origin; - glm::vec3 p; - if (rayPlaneIntersection(planePosition, planeNormal, rayStart, rayDirection, distance)) { - p = rayStart + rayDirection * distance; - } else { - p = rayPickResult.intersection; - } - glm::vec3 localP = glm::inverse(rotation) * (p - position); - glm::vec3 normalizedP = (localP / overlayDimensions) + glm::vec3(0.5f); - return glm::vec2(normalizedP.x * overlayDimensions.x, - (1.0f - normalizedP.y) * overlayDimensions.y); // flip y-axis -} - static uint32_t toPointerButtons(const QMouseEvent& event) { uint32_t buttons = 0; buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0; @@ -813,73 +1228,76 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, - RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, - PointerEvent::EventType eventType) { - auto overlay = std::dynamic_pointer_cast(getOverlay(overlayID)); - if (getOverlayType(overlayID) == "web3d") { - overlay = std::dynamic_pointer_cast(getOverlay(overlayID)); +RayToOverlayIntersectionResult getPrevPickResult(unsigned int mouseRayPickID) { + RayToOverlayIntersectionResult overlayResult; + overlayResult.intersects = false; + auto pickResult = DependencyManager::get()->getPrevPickResultTyped(mouseRayPickID); + if (pickResult) { + overlayResult.intersects = pickResult->type != IntersectionType::NONE; + if (overlayResult.intersects) { + overlayResult.intersection = pickResult->intersection; + overlayResult.distance = pickResult->distance; + overlayResult.surfaceNormal = pickResult->surfaceNormal; + overlayResult.overlayID = pickResult->objectID; + overlayResult.extraInfo = pickResult->extraInfo; + } } - if (!overlay) { - return PointerEvent(); - } - glm::vec3 position = overlay->getWorldPosition(); - glm::quat rotation = overlay->getWorldOrientation(); - glm::vec2 dimensions = overlay->getSize(); - - - glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult); - - PointerEvent pointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, - ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); - - return pointerEvent; + return overlayResult; } +PointerEvent Overlays::calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, + const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, + PointerEvent::EventType eventType) { + glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(id, rayPickResult.intersection); + return PointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, + ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); +} -bool Overlays::mousePressEvent(QMouseEvent* event) { +void Overlays::hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + emit hoverEnterOverlay(id, event); + } +} + +void Overlays::hoverOverPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + emit hoverOverOverlay(id, event); + } +} + +void Overlays::hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + emit hoverLeaveOverlay(id, event); + } +} + +std::pair Overlays::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mousePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), - QVector()); + RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(_mouseRayPickID); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); mousePressPointerEvent(_currentClickingOnOverlayID, pointerEvent); - return true; + return { rayPickResult.distance, rayPickResult.overlayID }; } - // if we didn't press on an overlay, disable overlay keyboard focus - setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID); - // emit to scripts emit mousePressOffOverlay(); - return false; + return { FLT_MAX, UNKNOWN_ENTITY_ID }; } -void Overlays::mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - if (event.shouldFocus()) { - // Focus keyboard on web overlays - DependencyManager::get()->setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); - setKeyboardFocusOverlay(overlayID); - } - - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); - } - - +void Overlays::mousePressPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit mousePressOnOverlay(overlayID, event); + if (!keyboard->getKeyIDs().contains(id)) { + emit mousePressOnOverlay(id, event); } } @@ -887,109 +1305,37 @@ bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), - QVector()); + RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(_mouseRayPickID); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); - // emit to scripts emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); return true; } - // emit to scripts emit mouseDoublePressOffOverlay(); return false; } -void Overlays::hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "hoverEnterOverlay", Q_ARG(PointerEvent, event)); - } - - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit hoverEnterOverlay(overlayID, event); - } -} - -void Overlays::hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); - } - - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit hoverOverOverlay(overlayID, event); - } -} - -void Overlays::hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "hoverLeaveOverlay", Q_ARG(PointerEvent, event)); - } - - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit hoverLeaveOverlay(overlayID, event); - } -} - bool Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), - QVector()); + RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(_mouseRayPickID); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); } - _currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID; + _currentClickingOnOverlayID = UNKNOWN_ENTITY_ID; return false; } -void Overlays::mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); - } - +void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit mouseReleaseOnOverlay(overlayID, event); + if (!keyboard->getKeyIDs().contains(id)) { + emit mouseReleaseOnOverlay(id, event); } } @@ -997,14 +1343,13 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), - QVector()); + RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(_mouseRayPickID); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); // If previously hovering over a different overlay then leave hover on that overlay. - if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) { + if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) { auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); } @@ -1020,69 +1365,678 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) { _currentHoverOverOverlayID = rayPickResult.overlayID; } else { // If previously hovering an overlay then leave hover. - if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID) { + if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID) { auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); - _currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID; + _currentHoverOverOverlayID = UNKNOWN_ENTITY_ID; } } return false; } -void Overlays::mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event) { - // TODO: generalize this to allow any overlay to recieve events - std::shared_ptr thisOverlay; - if (getOverlayType(overlayID) == "web3d") { - thisOverlay = std::static_pointer_cast(getOverlay(overlayID)); - } - if (thisOverlay) { - // Send to web overlay - QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); - } - +void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeysID().contains(overlayID)) { - // emit to scripts - emit mouseMoveOnOverlay(overlayID, event); + if (!keyboard->getKeyIDs().contains(id)) { + emit mouseMoveOnOverlay(id, event); } } QVector Overlays::findOverlays(const glm::vec3& center, float radius) { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVector result; - //if (QThread::currentThread() != thread()) { - // PROFILE_RANGE(script, __FUNCTION__); - // BLOCKING_INVOKE_METHOD(this, "findOverlays", Q_RETURN_ARG(QVector, result), Q_ARG(glm::vec3, center), Q_ARG(float, radius)); - // return result; - //} - - QMutexLocker locker(&_mutex); - QMapIterator i(_overlaysWorld); - int checked = 0; - while (i.hasNext()) { - checked++; - i.next(); - OverlayID thisID = i.key(); - auto overlay = std::dynamic_pointer_cast(i.value()); - - if (overlay && overlay->getVisible() && overlay->isLoaded()) { - // get AABox in frame of overlay - glm::vec3 dimensions = overlay->getDimensions(); - glm::vec3 low = dimensions * -0.5f; - AABox overlayFrameBox(low, dimensions); - - Transform overlayToWorldMatrix = overlay->getTransform(); - overlayToWorldMatrix.setScale(1.0f); // ignore inherited scale factor from parents - glm::mat4 worldToOverlayMatrix = glm::inverse(overlayToWorldMatrix.getMatrix()); - glm::vec3 overlayFrameSearchPosition = glm::vec3(worldToOverlayMatrix * glm::vec4(center, 1.0f)); - glm::vec3 penetration; - if (overlayFrameBox.findSpherePenetration(overlayFrameSearchPosition, radius, penetration)) { - result.append(thisID); - } - } + auto entityTree = DependencyManager::get()->getEntityTree(); + if (entityTree) { + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); + // For legacy reasons, this only finds visible objects + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); + entityTree->withReadLock([&] { + entityTree->evalEntitiesInSphere(center, radius, PickFilter(searchFilter), result); + }); } - return result; } + +/**jsdoc + *

An overlay may be one of the following types:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Value2D/3DDescription
image2DAn image. Synonym: billboard.
rectangle2DA rectangle.
text2DText.
cube3DA cube. Can also use a shape overlay to create a cube.
sphere3DA sphere. Can also use a shape overlay to create a sphere.
rectangle3d3DA rectangle.
shape3DA geometric shape, such as a cube, sphere, or cylinder.
model3DA model.
text3d3DText.
image3d3DAn image.
web3d3DWeb content.
line3d3DA line.
grid3DA grid of lines in a plane.
circle3d3DA circle.
+ *

2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are + * rendered at a position and orientation in-world, but are deprecated (use local entities instead).

+ *

Each overlay type has different {@link Overlays.OverlayProperties|OverlayProperties}.

+ * @typedef {string} Overlays.OverlayType + */ + +/**jsdoc + * Different overlay types have different properties: some common to all overlays (listed below) and some specific to each + * {@link Overlays.OverlayType|OverlayType} (linked to below). The properties are accessed as an object of property names and + * values. 3D Overlays are local entities, internally, so they also support the corresponding entity properties. + * + * @typedef {object} Overlays.OverlayProperties + * @property {Uuid} id - The ID of the overlay. Read-only. + * @property {Overlays.OverlayType} type - The overlay type. Read-only. + * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. + * + * @see The different entity types have additional properties as follows: + * @see {@link Overlays.OverlayProperties-Image|OverlayProperties-Image} + * @see {@link Overlays.OverlayProperties-Text|OverlayProperties-Text} + * @see {@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle} + * @see {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube} + * @see {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere} + * @see {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D} + * @see {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape} + * @see {@link Overlays.OverlayProperties-Model|OverlayProperties-Model} + * @see {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D} + * @see {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D} + * @see {@link Overlays.OverlayProperties-Web|OverlayProperties-Web} + * @see {@link Overlays.OverlayProperties-Line|OverlayProperties-Line} + * @see {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid} + * @see {@link Overlays.OverlayProperties-Circle|OverlayProperties-Circle} + */ + +/**jsdoc + * The "Image" {@link Overlays.OverlayType|OverlayType} is a 2D image. + * @typedef {object} Overlays.OverlayProperties-Image + * @property {Rect} bounds - The position and size of the image display area, in pixels. Write-only. + * @property {number} x - Integer left, x-coordinate value of the image display area = bounds.x. + * Write-only. + * @property {number} y - Integer top, y-coordinate value of the image display area = bounds.y. + * Write-only. + * @property {number} width - Integer width of the image display area = bounds.width. Write-only. + * @property {number} height - Integer height of the image display area = bounds.height. Write-only. + * @property {string} imageURL - The URL of the image file to display. The image is scaled to fit to the bounds. + * Write-only. + * @property {Vec2} subImage=0,0 - Integer coordinates of the top left pixel to start using image content from. + * Write-only. + * @property {Color} color=0,0,0 - The color to apply over the top of the image to colorize it. Write-only. + * @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, 0.0 - + * 1.0. Write-only. + */ + +/**jsdoc + * The "Text" {@link Overlays.OverlayType|OverlayType} is for 2D text. + * @typedef {object} Overlays.OverlayProperties-Text + * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. + * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. + * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. + * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. + * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. + * + * @property {number} margin=0 - Sets the leftMargin and topMargin values, in pixels. + * Write-only. + * @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin. + * Write-only. + * @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin. + * Write-only. + * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. Text + * is clipped to the bounds. Write-only. + * @property {number} font.size=18 - The size of the text, in pixels. Write-only. + * @property {number} lineHeight=18 - The height of a line of text, in pixels. Write-only. + * @property {Color} color=255,255,255 - The color of the text. Synonym: textColor. Write-only. + * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. + * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. Write-only. + * @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle. Write-only. + */ + +/**jsdoc + * The "Rectangle" {@link Overlays.OverlayType|OverlayType} is for 2D rectangles. + * @typedef {object} Overlays.OverlayProperties-Rectangle + * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. + * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. + * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. + * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. + * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. + * + * @property {Color} color=0,0,0 - The color of the overlay. Write-only. + * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. + * @property {number} borderWidth=1 - Integer width of the border, in pixels. The border is drawn within the rectangle's bounds. + * It is not drawn unless either borderColor or borderAlpha are specified. Write-only. + * @property {number} radius=0 - Integer corner radius, in pixels. Write-only. + * @property {Color} borderColor=0,0,0 - The color of the border. Write-only. + * @property {number} borderAlpha=1.0 - The opacity of the border, 0.0 - 1.0. + * Write-only. + */ + +/**jsdoc + * The "Cube" {@link Overlays.OverlayType|OverlayType} is for 3D cubes. + * @typedef {object} Overlays.OverlayProperties-Cube + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + */ + +/**jsdoc + * The "Sphere" {@link Overlays.OverlayType|OverlayType} is for 3D spheres. + * @typedef {object} Overlays.OverlayProperties-Sphere + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + */ + +/**jsdoc + * The "Rectangle3D" {@link Overlays.OverlayType|OverlayType} is for 3D rectangles. + * @typedef {object} Overlays.OverlayProperties-Rectangle3D + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + */ + +/**jsdoc + *

A shape {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Line"1DA line oriented in 3D.
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
+ * @typedef {string} Overlays.Shape + */ + +/**jsdoc + * The "Shape" {@link Overlays.OverlayType|OverlayType} is for 3D shapes. + * @typedef {object} Overlays.OverlayProperties-Shape + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. + */ + +/**jsdoc + * The "Model" {@link Overlays.OverlayType|OverlayType} is for 3D models. + * @typedef {object} Overlays.OverlayProperties-Model + * @property {string} name - The name of the overlay. + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {string} url - The URL of the FBX or OBJ model used for the overlay. + * @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load + * first. + * @property {object.} textures - Maps the named textures in the model to the JPG or PNG images in the urls. + * @property {string[]} jointNames - The names of the joints - if any - in the model. Read-only. + * @property {Quat[]} jointRotations - The relative rotations of the model's joints. + * @property {Vec3[]} jointTranslations - The relative translations of the model's joints. + * @property {Quat[]} jointOrientations - The absolute orientations of the model's joints, in world coordinates. Read-only. + * @property {Vec3[]} jointPositions - The absolute positions of the model's joints, in world coordinates. Read-only. + * @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play. + * @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at. + * @property {number} animationSettings.firstFrame=0 - The frame to start playing at. + * @property {number} animationSettings.lastFrame=0 - The frame to finish playing at. + * @property {number} animationSettings.currentFrame=0 - The current frame being played. + * @property {boolean} animationSettings.running=false - Whether or not the animation is playing. + * @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop. + * @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and + * translations of the last frame played should be maintained. + * @property {boolean} animationSettings.allowTranslation=false - Whether or not translations contained in the animation should + * be played. + */ + +/**jsdoc + * The "Text3D" {@link Overlays.OverlayType|OverlayType} is for 3D text. + * @typedef {object} Overlays.OverlayProperties-Text3D + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {boolean} isFacingAvatar - If true< / code>, the overlay is rotated to face the user's camera about an axis + * parallel to the user's avatar's "up" direction. + * @property {string} text="" - The text to display.Text does not automatically wrap; use \n< / code> for a line break. + * @property {number} textAlpha=1 - The text alpha value. + * @property {Color} backgroundColor=0,0,0 - The background color. + * @property {number} backgroundAlpha=0.7 - The background alpha value. + * @property {number} lineHeight=1 - The height of a line of text in meters. + * @property {number} leftMargin=0.1 - The left margin, in meters. + * @property {number} topMargin=0.1 - The top margin, in meters. + * @property {number} rightMargin=0.1 - The right margin, in meters. + * @property {number} bottomMargin=0.1 - The bottom margin, in meters. + */ + +/**jsdoc + * The "Image3D" {@link Overlays.OverlayType|OverlayType} is for 3D images. + * @typedef {object} Overlays.OverlayProperties-Image3D + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis + * parallel to the user's avatar's "up" direction. + * @property {string} url - The URL of the PNG or JPG image to display. + * @property {Rect} subImage - The portion of the image to display. Defaults to the full image. + * @property {boolean} emissive - If true, the overlay is displayed at full brightness, otherwise it is rendered + * with scene lighting. + * @property {bool} keepAspectRatio=true - overlays will maintain the aspect ratio when the subImage is applied. + */ + +/**jsdoc + * The "Web" {@link Overlays.OverlayType|OverlayType} is for 3D web surfaces. + * @typedef {object} Overlays.OverlayProperties-Web + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis + * parallel to the user's avatar's "up" direction. + * @property {string} url - The URL of the Web page to display. + * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. + * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. + * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. + * @property {string} inputMode=Touch - The user input mode to use - either "Touch" or "Mouse". + */ + +/**jsdoc + * The "Line" {@link Overlays.OverlayType|OverlayType} is for 3D lines. + * @typedef {object} Overlays.OverlayProperties-Line + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to. + * @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is + * attached to if parentID is an avatar skeleton. A value of 65535 means "no joint". + * @property {Vec3} start - The start point of the line. Synonyms: startPoint and p1. + * @property {Vec3} end - The end point of the line. Synonyms: endPoint and p2. + * @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as start. Synonym: localPosition. + * @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a + * endParentID set, otherwise the same value as end. + * @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end + * points. + * @property {number} glow=0 - If glow > 0, the line is rendered with a glow. + * @property {number} lineWidth=0.02 - Width of the line, in meters. + */ + +/**jsdoc + * The "Grid" {@link Overlays.OverlayType|OverlayType} is for 3D grid. + * @typedef {object} Overlays.OverlayProperties-Grid + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {boolean} followCamera=true - If true, the grid is always visible even as the camera moves to another position. + * @property {number} majorGridEvery=5 - Integer number of minorGridEvery intervals at which to draw a thick grid line. Minimum value = 1. + * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = 0.001. + */ + +/**jsdoc + * The "Circle" {@link Overlays.OverlayType|OverlayType} is for 3D circle. + * @typedef {object} Overlays.OverlayProperties-Circle + * @property {string} name - The name of the overlay. + * @property {Color} color=255,255,255 - The color of the overlay. + * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. + * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. + * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. + * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * pulseMin to pulseMax, then pulseMax to pulseMin in one period. + * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the + * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 + * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise + * used.) + * + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * start. + * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. + * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. + * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as position. + * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a + * parentID set, otherwise the same value as rotation. Synonym: localOrientation. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. + * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of objects in the world, but behind the HUD. + * @property {boolean} drawHUDLayer=false - If true, the overlay is rendered in front of everything, including the HUD. + * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. + * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if + * parentID is an avatar skeleton. A value of 65535 means "no joint". + * + * @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at, in degrees. + * @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at, in degrees. + * @property {number} outerRadius = 1 - The outer radius of the overlay, in meters.Synonym: radius< / code>. + * @property {number} innerRadius = 0 - The inner radius of the overlay, in meters. + * @property {Color} color = 255, 255, 255 - The color of the overlay.Setting this value also sets the values of + * innerStartColor< / code>, innerEndColor< / code>, outerStartColor< / code>, and outerEndColor< / code>. + * @property {Color} startColor - Sets the values of innerStartColor< / code> and outerStartColor< / code>. + * Write - only.< / em> + * @property {Color} endColor - Sets the values of innerEndColor< / code> and outerEndColor< / code>. + * Write - only.< / em> + * @property {Color} innerColor - Sets the values of innerStartColor< / code> and innerEndColor< / code>. + * Write - only.< / em> + * @property {Color} outerColor - Sets the values of outerStartColor< / code> and outerEndColor< / code>. + * Write - only.< / em> + * @property {Color} innerStartcolor - The color at the inner start point of the overlay. + * @property {Color} innerEndColor - The color at the inner end point of the overlay. + * @property {Color} outerStartColor - The color at the outer start point of the overlay. + * @property {Color} outerEndColor - The color at the outer end point of the overlay. + * @property {number} alpha = 0.5 - The opacity of the overlay, 0.0< / code> -1.0< / code>.Setting this value also sets + * the values of innerStartAlpha< / code>, innerEndAlpha< / code>, outerStartAlpha< / code>, and + * outerEndAlpha< / code>.Synonym: Alpha< / code>; write - only< / em>. + * @property {number} startAlpha - Sets the values of innerStartAlpha< / code> and outerStartAlpha< / code>. + * Write - only.< / em> + * @property {number} endAlpha - Sets the values of innerEndAlpha< / code> and outerEndAlpha< / code>. + * Write - only.< / em> + * @property {number} innerAlpha - Sets the values of innerStartAlpha< / code> and innerEndAlpha< / code>. + * Write - only.< / em> + * @property {number} outerAlpha - Sets the values of outerStartAlpha< / code> and outerEndAlpha< / code>. + * Write - only.< / em> + * @property {number} innerStartAlpha = 0 - The alpha at the inner start point of the overlay. + * @property {number} innerEndAlpha = 0 - The alpha at the inner end point of the overlay. + * @property {number} outerStartAlpha = 0 - The alpha at the outer start point of the overlay. + * @property {number} outerEndAlpha = 0 - The alpha at the outer end point of the overlay. + * + * @property {boolean} hasTickMarks = false - If true< / code>, tick marks are drawn. + * @property {number} majorTickMarksAngle = 0 - The angle between major tick marks, in degrees. + * @property {number} minorTickMarksAngle = 0 - The angle between minor tick marks, in degrees. + * @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters.A positive value draws tick marks + * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. + * @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters.A positive value draws tick marks + * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. + * @property {Color} majorTickMarksColor = 0, 0, 0 - The color of the major tick marks. + * @property {Color} minorTickMarksColor = 0, 0, 0 - The color of the minor tick marks. + */ diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 208fc8d78d..7612779099 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -26,29 +26,16 @@ #include "Overlay.h" -#include "PanelAttachable.h" +#include class PickRay; -class OverlayPropertyResult { -public: - OverlayPropertyResult(); - QVariant value; -}; - -Q_DECLARE_METATYPE(OverlayPropertyResult); - -QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const OverlayPropertyResult& value); -void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value); - -const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); - /**jsdoc * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection}. * @typedef {object} Overlays.RayToOverlayIntersectionResult * @property {boolean} intersects - true if the {@link PickRay} intersected with a 3D overlay, otherwise * false. - * @property {Uuid} overlayID - The UUID of the overlay that was intersected. + * @property {Uuid} overlayID - The UUID of the local entity that was intersected. * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. * @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point. * @property {Vec3} intersection - The position of the intersection point. @@ -57,7 +44,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); class RayToOverlayIntersectionResult { public: bool intersects { false }; - OverlayID overlayID { UNKNOWN_OVERLAY_ID }; + QUuid overlayID; float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 surfaceNormal; @@ -71,7 +58,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R class ParabolaToOverlayIntersectionResult { public: bool intersects { false }; - OverlayID overlayID { UNKNOWN_OVERLAY_ID }; + QUuid overlayID; float distance { 0.0f }; float parabolicDistance { 0.0f }; BoxFace face { UNKNOWN_FACE }; @@ -81,55 +68,59 @@ public: }; /**jsdoc - * The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to + * (Note: 3D Overlays are deprecated. Use local entities instead.) The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to * yourself and that aren't persisted to the domain. They are used for UI. * @namespace Overlays * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * - * @property {Uuid} keyboardFocusOverlay - Get or set the {@link Overlays.OverlayType|web3d} overlay that has keyboard focus. - * If no overlay has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to + * @property {Uuid} keyboardFocusOverlay - Get or set the {@link Entities.EntityTypes|Web} entity that has keyboard focus. + * If no entity has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to * clear keyboard focus. */ class Overlays : public QObject { Q_OBJECT - Q_PROPERTY(OverlayID keyboardFocusOverlay READ getKeyboardFocusOverlay WRITE setKeyboardFocusOverlay) + Q_PROPERTY(QUuid keyboardFocusOverlay READ getKeyboardFocusOverlay WRITE setKeyboardFocusOverlay) public: Overlays(); void init(); void update(float deltatime); - void renderHUD(RenderArgs* renderArgs); + void render(RenderArgs* renderArgs); void disable(); void enable(); - Overlay::Pointer getOverlay(OverlayID id) const; + Overlay::Pointer take2DOverlay(const QUuid& id); + Overlay::Pointer get2DOverlay(const QUuid& id) const; /// adds an overlay that's already been created - OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } - OverlayID addOverlay(const Overlay::Pointer& overlay); + QUuid addOverlay(Overlay* overlay) { return add2DOverlay(Overlay::Pointer(overlay)); } + QUuid add2DOverlay(const Overlay::Pointer& overlay); RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, + const QVector& include, + const QVector& discard, bool visibleOnly = false, bool collidableOnly = false); ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, + const QVector& include, + const QVector& discard, bool visibleOnly = false, bool collidableOnly = false); - bool mousePressEvent(QMouseEvent* event); + std::pair mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); bool mouseMoveEvent(QMouseEvent* event); void cleanupAllOverlays(); + mutable QScriptEngine _scriptEngine; + public slots: /**jsdoc * Add an overlay to the scene. @@ -145,34 +136,20 @@ public slots: * solid: true * }); */ - OverlayID addOverlay(const QString& type, const QVariant& properties); + QUuid addOverlay(const QString& type, const QVariant& properties); /**jsdoc - * Create a clone of an existing overlay. + * Create a clone of an existing entity (or 2D overlay). * @function Overlays.cloneOverlay - * @param {Uuid} overlayID - The ID of the overlay to clone. - * @returns {Uuid} The ID of the new overlay if successful, otherwise {@link Uuid|Uuid.NULL}. - * @example Add an overlay in front of your avatar, clone it, and move the clone to be above the - * original. - * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })); - * var original = Overlays.addOverlay("cube", { - * position: position, - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * - * var clone = Overlays.cloneOverlay(original); - * Overlays.editOverlay(clone, { - * position: Vec3.sum({ x: 0, y: 0.5, z: 0}, position) - * }); + * @param {Uuid} id - The ID of the entity/2D overlay to clone. + * @returns {Uuid} The ID of the new object if successful, otherwise {@link Uuid|Uuid.NULL}. */ - OverlayID cloneOverlay(OverlayID id); + QUuid cloneOverlay(const QUuid& id); /**jsdoc * Edit an overlay's properties. * @function Overlays.editOverlay - * @param {Uuid} overlayID - The ID of the overlay to edit. + * @param {Uuid} id - The ID of the overlay to edit. * @param {Overlays.OverlayProperties} properties - The properties changes to make. * @returns {boolean} true if the overlay was found and edited, otherwise false. * @example Add an overlay in front of your avatar then change its color. @@ -188,7 +165,7 @@ public slots: * }); * print("Success: " + success); */ - bool editOverlay(OverlayID id, const QVariant& properties); + bool editOverlay(const QUuid& id, const QVariant& properties); /**jsdoc * Edit multiple overlays' properties. @@ -220,27 +197,18 @@ public slots: bool editOverlays(const QVariant& propertiesById); /**jsdoc - * Delete an overlay. + * Delete an entity or 2D overlay. * @function Overlays.deleteOverlay - * @param {Uuid} overlayID - The ID of the overlay to delete. - * @example Create an overlay in front of your avatar then delete it. - * var overlay = Overlays.addOverlay("cube", { - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { x: 0.3, y: 0.3, z: 0.3 }, - * solid: true - * }); - * print("Overlay: " + overlay); - * Overlays.deleteOverlay(overlay); + * @param {Uuid} id - The ID of the object to delete. */ - void deleteOverlay(OverlayID id); + void deleteOverlay(const QUuid& id); /**jsdoc - * Get the type of an overlay. + * Get the type of an entity or 2D overlay. * @function Overlays.getOverlayType - * @param {Uuid} overlayID - The ID of the overlay to get the type of. - * @returns {Overlays.OverlayType} The type of the overlay if found, otherwise an empty string. - * @example Create an overlay in front of your avatar then get and report its type. + * @param {Uuid} id - The ID of the object to get the type of. + * @returns {string} The type of the object if found, otherwise an empty string. + * @example Create an object in front of your avatar then get and report its type. * var overlay = Overlays.addOverlay("cube", { * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), * rotation: MyAvatar.orientation, @@ -250,7 +218,7 @@ public slots: * var type = Overlays.getOverlayType(overlay); * print("Type: " + type); */ - QString getOverlayType(OverlayID overlayId); + QString getOverlayType(const QUuid& id); /**jsdoc * Get the overlay script object. In particular, this is useful for accessing the event bridge for a web3d @@ -294,7 +262,7 @@ public slots: * Overlays.deleteOverlay(web3dOverlay); * }); */ - QObject* getOverlayObject(OverlayID id); + QObject* getOverlayObject(const QUuid& id); /**jsdoc * Get the ID of the 2D overlay at a particular point on the screen or HUD. @@ -313,12 +281,12 @@ public slots: * print("Clicked: " + overlay); * }); */ - OverlayID getOverlayAtPoint(const glm::vec2& point); + QUuid getOverlayAtPoint(const glm::vec2& point); /**jsdoc * Get the value of a 3D overlay's property. * @function Overlays.getProperty - * @param {Uuid} overlayID - The ID of the overlay. Must be for a 3D {@link Overlays.OverlayType|OverlayType}. + * @param {Uuid} id - The ID of the overlay. Must be for a 3D {@link Overlays.OverlayType|OverlayType}. * @param {string} property - The name of the property value to get. * @returns {object} The value of the property if the 3D overlay and property can be found, otherwise * undefined. @@ -332,12 +300,12 @@ public slots: * var alpha = Overlays.getProperty(overlay, "alpha"); * print("Overlay alpha: " + alpha); */ - OverlayPropertyResult getProperty(OverlayID id, const QString& property); + QVariant getProperty(const QUuid& id, const QString& property); /**jsdoc * Get the values of an overlay's properties. * @function Overlays.getProperties - * @param {Uuid} overlayID - The ID of the overlay. + * @param {Uuid} id - The ID of the overlay. * @param {Array.} properties - An array of names of properties to get the values of. * @returns {Overlays.OverlayProperties} The values of valid properties if the overlay can be found, otherwise * undefined. @@ -351,7 +319,7 @@ public slots: * var properties = Overlays.getProperties(overlay, ["color", "alpha", "grabbable"]); * print("Overlay properties: " + JSON.stringify(properties)); */ - OverlayPropertyResult getProperties(const OverlayID& id, const QStringList& properties); + QVariantMap getProperties(const QUuid& id, const QStringList& properties); /**jsdoc * Get the values of multiple overlays' properties. @@ -379,7 +347,7 @@ public slots: * var properties = Overlays.getOverlaysProperties(propertiesToGet); * print("Overlays properties: " + JSON.stringify(properties)); */ - OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties); + QVariantMap getOverlaysProperties(const QVariant& overlaysProperties); /**jsdoc * Find the closest 3D overlay intersected by a {@link PickRay}. Overlays with their drawInFront property set @@ -390,10 +358,12 @@ public slots: * @function Overlays.findRayIntersection * @param {PickRay} pickRay - The PickRay to use for finding overlays. * @param {boolean} [precisionPicking=false] - Unused; exists to match Entity API. - * @param {Array.} [overlayIDsToInclude=[]] - If not empty then the search is restricted to these overlays. - * @param {Array.} [overlayIDsToExclude=[]] - Overlays to ignore during the search. - * @param {boolean} [visibleOnly=false] - Unused; exists to match Entity API. - * @param {boolean} [collidableOnly=false] - Unused; exists to match Entity API. + * @param {Array.} [include=[]] - If not empty then the search is restricted to these overlays. + * @param {Array.} [discard=[]] - Overlays to ignore during the search. + * @param {boolean} [visibleOnly=false] - If true then only entities that are + * {@link Entities.EntityProperties|visible} are searched. + * @param {boolean} [collideableOnly=false] - If true then only entities that are not + * {@link Entities.EntityProperties|collisionless} are searched. * @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by pickRay, taking * into account overlayIDsToInclude and overlayIDsToExclude if they're not empty. * @example Create a cube overlay in front of your avatar. Report 3D overlay intersection details for mouse @@ -413,18 +383,18 @@ public slots: */ RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false, - const QScriptValue& overlayIDsToInclude = QScriptValue(), - const QScriptValue& overlayIDsToDiscard = QScriptValue(), + const QScriptValue& include = QScriptValue(), + const QScriptValue& discard = QScriptValue(), bool visibleOnly = false, bool collidableOnly = false); /**jsdoc - * Return a list of 3D overlays with bounding boxes that touch a search sphere. + * Return a list of local entities with bounding boxes that touch a search sphere. * @function Overlays.findOverlays * @param {Vec3} center - The center of the search sphere. * @param {number} radius - The radius of the search sphere. - * @returns {Uuid[]} An array of overlay IDs with bounding boxes that touch a search sphere. - * @example Create two cube overlays in front of your avatar then search for overlays near your avatar. + * @returns {Uuid[]} An array of entity IDs with bounding boxes that touch a search sphere. + * @example Create two cube entities in front of your avatar then search for entities near your avatar. * var overlayA = Overlays.addOverlay("cube", { * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })), * rotation: MyAvatar.orientation, @@ -449,7 +419,7 @@ public slots: * Check whether an overlay's assets have been loaded. For example, for an image overlay the result indicates * whether its image has been loaded. * @function Overlays.isLoaded - * @param {Uuid} overlayID - The ID of the overlay to check. + * @param {Uuid} id - The ID of the overlay to check. * @returns {boolean} true if the overlay's assets have been loaded, otherwise false. * @example Create an image overlay and report whether its image is loaded after 1s. * var overlay = Overlays.addOverlay("image", { @@ -461,17 +431,17 @@ public slots: * print("Image loaded: " + isLoaded); * }, 1000); */ - bool isLoaded(OverlayID id); + bool isLoaded(const QUuid& id); /**jsdoc - * Calculates the size of the given text in the specified overlay if it is a text overlay. + * Calculates the size of the given text in the specified object if it is a text entity or overlay. * @function Overlays.textSize - * @param {Uuid} overlayID - The ID of the overlay to use for calculation. + * @param {Uuid} id - The ID of the object to use for calculation. * @param {string} text - The string to calculate the size of. - * @returns {Size} The size of the text if the overlay is a text overlay, otherwise - * { height: 0, width : 0 }. If the overlay is a 2D overlay, the size is in pixels; if the overlay is a 3D - * overlay, the size is in meters. - * @example Calculate the size of "hello" in a 3D text overlay. + * @returns {Size} The size of the text if the object is a text entity or overlay, otherwise + * { height: 0, width : 0 }. If the object is a 2D overlay, the size is in pixels; if the object is an entity, + * the size is in meters. + * @example Calculate the size of "hello" in a 3D text entity. * var overlay = Overlays.addOverlay("text3d", { * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -2 })), * rotation: MyAvatar.orientation, @@ -481,7 +451,7 @@ public slots: * var textSize = Overlays.textSize(overlay, "hello"); * print("Size of \"hello\": " + JSON.stringify(textSize)); */ - QSizeF textSize(OverlayID id, const QString& text); + QSizeF textSize(const QUuid& id, const QString& text); /**jsdoc * Get the width of the window or HUD. @@ -498,17 +468,17 @@ public slots: float height(); /**jsdoc - * Check if there is an overlay of a given ID. + * Check if there is an object of a given ID. * @function Overlays.isAddedOverlay - * @param {Uuid} overlayID - The ID to check. - * @returns {boolean} true if an overlay with the given ID exists, false otherwise. + * @param {Uuid} id - The ID to check. + * @returns {boolean} true if an object with the given ID exists, false otherwise. */ - bool isAddedOverlay(OverlayID id); + bool isAddedOverlay(const QUuid& id); /**jsdoc * Generate a mouse press event on an overlay. * @function Overlays.sendMousePressOnOverlay - * @param {Uuid} overlayID - The ID of the overlay to generate a mouse press event on. + * @param {Uuid} id - The ID of the overlay to generate a mouse press event on. * @param {PointerEvent} event - The mouse press event details. * @example Create a 2D rectangle overlay plus a 3D cube overlay and generate mousePressOnOverlay events for the 2D * overlay. @@ -543,23 +513,23 @@ public slots: * } * }); */ - void sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Generate a mouse release event on an overlay. * @function Overlays.sendMouseReleaseOnOverlay - * @param {Uuid} overlayID - The ID of the overlay to generate a mouse release event on. + * @param {Uuid} id - The ID of the overlay to generate a mouse release event on. * @param {PointerEvent} event - The mouse release event details. */ - void sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Generate a mouse move event on an overlay. * @function Overlays.sendMouseMoveOnOverlay - * @param {Uuid} overlayID - The ID of the overlay to generate a mouse move event on. + * @param {Uuid} id - The ID of the overlay to generate a mouse move event on. * @param {PointerEvent} event - The mouse move event details. */ - void sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendMouseMoveOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Generate a hover enter event on an overlay. @@ -567,45 +537,45 @@ public slots: * @param {Uuid} id - The ID of the overlay to generate a hover enter event on. * @param {PointerEvent} event - The hover enter event details. */ - void sendHoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendHoverEnterOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Generate a hover over event on an overlay. * @function Overlays.sendHoverOverOverlay - * @param {Uuid} overlayID - The ID of the overlay to generate a hover over event on. + * @param {Uuid} id - The ID of the overlay to generate a hover over event on. * @param {PointerEvent} event - The hover over event details. */ - void sendHoverOverOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendHoverOverOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Generate a hover leave event on an overlay. * @function Overlays.sendHoverLeaveOverlay - * @param {Uuid} overlayID - The ID of the overlay to generate a hover leave event on. + * @param {Uuid} id - The ID of the overlay to generate a hover leave event on. * @param {PointerEvent} event - The hover leave event details. */ - void sendHoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event); + void sendHoverLeaveOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc - * Get the ID of the Web3D overlay that has keyboard focus. + * Get the ID of the Web3D entity that has keyboard focus. * @function Overlays.getKeyboardFocusOverlay - * @returns {Uuid} The ID of the {@link Overlays.OverlayType|web3d} overlay that has focus, if any, otherwise + * @returns {Uuid} The ID of the {@link Entities.EntityTypes|Web} overlay that has focus, if any, otherwise * null. */ - OverlayID getKeyboardFocusOverlay(); + QUuid getKeyboardFocusOverlay() { return DependencyManager::get()->getKeyboardFocusEntity(); } /**jsdoc - * Set the Web3D overlay that has keyboard focus. + * Set the Web3D entity that has keyboard focus. * @function Overlays.setKeyboardFocusOverlay - * @param {Uuid} overlayID - The ID of the {@link Overlays.OverlayType|web3d} overlay to set keyboard focus to. Use + * @param {Uuid} id - The ID of the {@link Entities.EntityTypes|Web} entity to set keyboard focus to. Use * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an overlay. */ - void setKeyboardFocusOverlay(const OverlayID& id); + void setKeyboardFocusOverlay(const QUuid& id) { DependencyManager::get()->setKeyboardFocusEntity(id); } signals: /**jsdoc * Triggered when an overlay is deleted. * @function Overlays.overlayDeleted - * @param {Uuid} overlayID - The ID of the overlay that was deleted. + * @param {Uuid} id - The ID of the overlay that was deleted. * @returns {Signal} * @example Create an overlay then delete it after 1s. * var overlay = Overlays.addOverlay("cube", { @@ -623,13 +593,13 @@ signals: * Overlays.deleteOverlay(overlay); * }, 1000); */ - void overlayDeleted(OverlayID id); + void overlayDeleted(const QUuid& id); /**jsdoc * Triggered when a mouse press event occurs on an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendMousePressOnOverlay|sendMousePressOnOverlay} for a 2D overlay). * @function Overlays.mousePressOnOverlay - * @param {Uuid} overlayID - The ID of the overlay the mouse press event occurred on. + * @param {Uuid} id - The ID of the overlay the mouse press event occurred on. * @param {PointerEvent} event - The mouse press event details. * @returns {Signal} * @example Create a cube overlay in front of your avatar and report mouse clicks on it. @@ -647,36 +617,36 @@ signals: * } * }); */ - void mousePressOnOverlay(OverlayID overlayID, const PointerEvent& event); + void mousePressOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse double press event occurs on an overlay. Only occurs for 3D overlays. * @function Overlays.mouseDoublePressOnOverlay - * @param {Uuid} overlayID - The ID of the overlay the mouse double press event occurred on. + * @param {Uuid} id - The ID of the overlay the mouse double press event occurred on. * @param {PointerEvent} event - The mouse double press event details. * @returns {Signal} */ - void mouseDoublePressOnOverlay(OverlayID overlayID, const PointerEvent& event); + void mouseDoublePressOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse release event occurs on an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendMouseReleaseOnOverlay|sendMouseReleaseOnOverlay} for a 2D overlay). * @function Overlays.mouseReleaseOnOverlay - * @param {Uuid} overlayID - The ID of the overlay the mouse release event occurred on. + * @param {Uuid} id - The ID of the overlay the mouse release event occurred on. * @param {PointerEvent} event - The mouse release event details. * @returns {Signal} */ - void mouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event); + void mouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse move event occurs on an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendMouseMoveOnOverlay|sendMouseMoveOnOverlay} for a 2D overlay). * @function Overlays.mouseMoveOnOverlay - * @param {Uuid} overlayID - The ID of the overlay the mouse moved event occurred on. + * @param {Uuid} id - The ID of the overlay the mouse moved event occurred on. * @param {PointerEvent} event - The mouse move event details. * @returns {Signal} */ - void mouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event); + void mouseMoveOnOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse press event occurs on something other than a 3D overlay. @@ -696,7 +666,7 @@ signals: * Triggered when a mouse cursor starts hovering over an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendHoverEnterOverlay|sendHoverEnterOverlay} for a 2D overlay). * @function Overlays.hoverEnterOverlay - * @param {Uuid} overlayID - The ID of the overlay the mouse moved event occurred on. + * @param {Uuid} id - The ID of the overlay the mouse moved event occurred on. * @param {PointerEvent} event - The mouse move event details. * @returns {Signal} * @example Create a cube overlay in front of your avatar and report when you start hovering your mouse over @@ -712,54 +682,67 @@ signals: * print("Hover enter: " + overlayID); * }); */ - void hoverEnterOverlay(OverlayID overlayID, const PointerEvent& event); + void hoverEnterOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse cursor continues hovering over an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendHoverOverOverlay|sendHoverOverOverlay} for a 2D overlay). * @function Overlays.hoverOverOverlay - * @param {Uuid} overlayID - The ID of the overlay the hover over event occurred on. + * @param {Uuid} id - The ID of the overlay the hover over event occurred on. * @param {PointerEvent} event - The hover over event details. * @returns {Signal} */ - void hoverOverOverlay(OverlayID overlayID, const PointerEvent& event); + void hoverOverOverlay(const QUuid& id, const PointerEvent& event); /**jsdoc * Triggered when a mouse cursor finishes hovering over an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendHoverLeaveOverlay|sendHoverLeaveOverlay} for a 2D overlay). * @function Overlays.hoverLeaveOverlay - * @param {Uuid} overlayID - The ID of the overlay the hover leave event occurred on. + * @param {Uuid} id - The ID of the overlay the hover leave event occurred on. * @param {PointerEvent} event - The hover leave event details. * @returns {Signal} */ - void hoverLeaveOverlay(OverlayID overlayID, const PointerEvent& event); + void hoverLeaveOverlay(const QUuid& id, const PointerEvent& event); private: void cleanupOverlaysToDelete(); mutable QMutex _mutex { QMutex::Recursive }; - QMap _overlaysHUD; - QMap _overlaysWorld; - + QMap _overlays; QList _overlaysToDelete; + unsigned int _stackOrder { 1 }; - bool _enabled = true; - std::atomic _shuttingDown{ false }; + bool _enabled { true }; + std::atomic _shuttingDown { false }; - PointerEvent calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, RayToOverlayIntersectionResult rayPickResult, + PointerEvent calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType); - OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; - OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; + unsigned int _mouseRayPickID; + QUuid _currentClickingOnOverlayID; + QUuid _currentHoverOverOverlayID; + + static QString entityToOverlayType(const QString& type); + static QString overlayToEntityType(const QString& type); + static std::unordered_map _entityToOverlayTypes; + static std::unordered_map _overlayToEntityTypes; + + QVariantMap convertEntityToOverlayProperties(const EntityItemProperties& entityProps); + EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, const QString& type, bool add, const QUuid& id); + EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, glm::quat& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); private slots: - void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mousePressPointerEvent(const QUuid& id, const PointerEvent& event); + void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event); + void mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event); + void hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event); + void hoverOverPointerEvent(const QUuid& id, const PointerEvent& event); + void hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event); }; +#define ADD_TYPE_MAP(entity, overlay) \ + _entityToOverlayTypes[#entity] = #overlay; \ + _overlayToEntityTypes[#overlay] = #entity; + #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 37fadef0b4..0d1bcdd071 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -8,27 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include - -#include -#include -#include -#include - -#include "Image3DOverlay.h" -#include "Circle3DOverlay.h" -#include "Cube3DOverlay.h" -#include "ImageOverlay.h" -#include "Line3DOverlay.h" -#include "ModelOverlay.h" -#include "Overlays.h" -#include "Rectangle3DOverlay.h" -#include "Sphere3DOverlay.h" -#include "Grid3DOverlay.h" -#include "TextOverlay.h" -#include "Text3DOverlay.h" - +#include "Overlay.h" namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { diff --git a/interface/src/ui/overlays/PanelAttachable.cpp b/interface/src/ui/overlays/PanelAttachable.cpp deleted file mode 100644 index b53474390c..0000000000 --- a/interface/src/ui/overlays/PanelAttachable.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// PanelAttachable.cpp -// interface/src/ui/overlays -// -// Created by Zander Otavka on 7/15/15. -// Copyright 2015 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 "PanelAttachable.h" - -#include - -bool PanelAttachable::getParentVisible() const { - return true; -} - -// JSDoc for copying to @typedefs of overlay types that inherit PanelAttachable. -// No JSDoc because these properties are not actually used. -QVariant PanelAttachable::getProperty(const QString& property) { - if (property == "offsetPosition") { - return vec3toVariant(getOffsetPosition()); - } - if (property == "offsetRotation") { - return quatToVariant(getOffsetRotation()); - } - if (property == "offsetScale") { - return vec3toVariant(getOffsetScale()); - } - return QVariant(); -} - -void PanelAttachable::setProperties(const QVariantMap& properties) { - auto offsetPosition = properties["offsetPosition"]; - bool valid; - if (offsetPosition.isValid()) { - glm::vec3 newPosition = vec3FromVariant(offsetPosition, valid); - if (valid) { - setOffsetPosition(newPosition); - } - } - - auto offsetRotation = properties["offsetRotation"]; - if (offsetRotation.isValid()) { - setOffsetRotation(quatFromVariant(offsetRotation)); - } - - auto offsetScale = properties["offsetScale"]; - if (offsetScale.isValid()) { - setOffsetScale(vec3FromVariant(offsetScale)); - } -} - -bool PanelAttachable::applyTransformTo(Transform& transform, bool force) { - if (force || usecTimestampNow() > _transformExpiry) { - const quint64 TRANSFORM_UPDATE_PERIOD = 100000; // frequency is 10 Hz - _transformExpiry = usecTimestampNow() + TRANSFORM_UPDATE_PERIOD; - } - return false; -} diff --git a/interface/src/ui/overlays/PanelAttachable.h b/interface/src/ui/overlays/PanelAttachable.h deleted file mode 100644 index 95faf38cf2..0000000000 --- a/interface/src/ui/overlays/PanelAttachable.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// PanelAttachable.h -// interface/src/ui/overlays -// -// Created by Zander Otavka on 7/1/15. -// Copyright 2015 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 -// -// Base class for anything that can attach itself to an `OverlayPanel` as a child. -// `PanelAttachable keeps an `std::shared_ptr` to it's parent panel, and sets its -// transformations and visibility based on the parent. -// -// When subclassing `PanelAttachable`, make sure `applyTransformTo`, `getProperty`, and -// `setProperties are all called in the appropriate places. Look through `Image3DOverlay` and -// `Billboard3DOverlay` for examples. Pay special attention to `applyTransformTo`; it should -// be called in three places for `Overlay`s: `render`, `update`, and `findRayIntersection`. -// -// When overriding `applyTransformTo`, make sure to wrap all of your code, including the call -// to the superclass method, with the following `if` block. Then call the superclass method -// with force = true. -// -// if (force || usecTimestampNow() > _transformExpiry) { -// PanelAttachable::applyTransformTo(transform, true); -// ... -// } -// - -#ifndef hifi_PanelAttachable_h -#define hifi_PanelAttachable_h - -#include - -#include -#include -#include -#include -#include - -class OverlayPanel; -class PanelAttachable { -public: - // getters - glm::vec3 getOffsetPosition() const { return _offset.getTranslation(); } - glm::quat getOffsetRotation() const { return _offset.getRotation(); } - glm::vec3 getOffsetScale() const { return _offset.getScale(); } - bool getParentVisible() const; - - // setters - void setOffsetPosition(const glm::vec3& position) { _offset.setTranslation(position); } - void setOffsetRotation(const glm::quat& rotation) { _offset.setRotation(rotation); } - void setOffsetScale(float scale) { _offset.setScale(scale); } - void setOffsetScale(const glm::vec3& scale) { _offset.setScale(scale); } - -protected: - void setProperties(const QVariantMap& properties); - QVariant getProperty(const QString& property); - - /// set position, rotation and scale on transform based on offsets, and parent panel offsets - /// if force is false, only apply transform if it hasn't been applied in the last .1 seconds - virtual bool applyTransformTo(Transform& transform, bool force = false); - quint64 _transformExpiry = 0; - -private: - Transform _offset; -}; - -#endif // hifi_PanelAttachable_h diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp deleted file mode 100644 index c33d3a0c39..0000000000 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// -// Planar3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Planar3DOverlay.h" - -#include -#include -#include - -Planar3DOverlay::Planar3DOverlay() : - Base3DOverlay(), - _dimensions{1.0f, 1.0f} -{ -} - -Planar3DOverlay::Planar3DOverlay(const Planar3DOverlay* planar3DOverlay) : - Base3DOverlay(planar3DOverlay), - _dimensions(planar3DOverlay->_dimensions) -{ -} - -AABox Planar3DOverlay::getBounds() const { - auto halfDimensions = glm::vec3{_dimensions / 2.0f, 0.01f}; - - auto extents = Extents{-halfDimensions, halfDimensions}; - extents.transform(getTransform()); - - return AABox(extents); -} - -void Planar3DOverlay::setDimensions(const glm::vec2& value) { - _dimensions = value; - notifyRenderVariableChange(); -} - -void Planar3DOverlay::setProperties(const QVariantMap& properties) { - Base3DOverlay::setProperties(properties); - - auto dimensions = properties["dimensions"]; - - // if "dimensions" property was not there, check to see if they included aliases: scale - if (!dimensions.isValid()) { - dimensions = properties["scale"]; - if (!dimensions.isValid()) { - dimensions = properties["size"]; - } - } - - if (dimensions.isValid()) { - setDimensions(vec2FromVariant(dimensions)); - } -} - -// JSDoc for copying to @typedefs of overlay types that inherit Planar3DOverlay. -/**jsdoc - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - */ -QVariant Planar3DOverlay::getProperty(const QString& property) { - if (property == "dimensions" || property == "scale" || property == "size") { - return vec2ToVariant(getDimensions()); - } - - return Base3DOverlay::getProperty(property); -} - -bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - glm::vec2 xyDimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - glm::vec3 forward = rotation * Vectors::FRONT; - if (glm::dot(forward, direction) > 0.0f) { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } else { - face = MIN_Z_FACE; - surfaceNormal = forward; - } - return true; - } - return false; -} - -bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - glm::vec2 xyDimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - glm::quat inverseRot = glm::inverse(rotation); - glm::vec3 localOrigin = inverseRot * (origin - position); - glm::vec3 localVelocity = inverseRot * velocity; - glm::vec3 localAcceleration = inverseRot * acceleration; - - if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { - float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; - glm::vec3 forward = rotation * Vectors::FRONT; - if (localIntersectionVelocityZ > 0.0f) { - face = MIN_Z_FACE; - surfaceNormal = forward; - } else { - face = MAX_Z_FACE; - surfaceNormal = -forward; - } - return true; - } - return false; -} - -Transform Planar3DOverlay::evalRenderTransform() { - auto transform = getTransform(); - transform.setScale(1.0f); // ignore inherited scale factor from parents - if (glm::length2(getDimensions()) != 1.0f) { - transform.postScale(vec3(getDimensions(), 1.0f)); - } - return transform; -} diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h deleted file mode 100644 index 0054b0baf1..0000000000 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Planar3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Planar3DOverlay_h -#define hifi_Planar3DOverlay_h - -#include "Base3DOverlay.h" - -class Planar3DOverlay : public Base3DOverlay { - Q_OBJECT - -public: - Planar3DOverlay(); - Planar3DOverlay(const Planar3DOverlay* planar3DOverlay); - - virtual AABox getBounds() const override; - virtual glm::vec2 getSize() const { return _dimensions; }; - - glm::vec2 getDimensions() const { return _dimensions; } - void setDimensions(float value) { setDimensions(glm::vec2(value)); } - void setDimensions(const glm::vec2& value); - - virtual void setProperties(const QVariantMap& properties) override; - virtual QVariant getProperty(const QString& property) override; - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - -protected: - glm::vec2 _dimensions; - - Transform evalRenderTransform() override; -}; - - -#endif // hifi_Planar3DOverlay_h diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index 2a583e0450..537c421ca7 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -26,8 +26,8 @@ QmlOverlay::QmlOverlay(const QUrl& url) { buildQmlElement(url); } -QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* textOverlay) - : Overlay2D(textOverlay) { +QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* overlay) + : Overlay2D(overlay) { buildQmlElement(url); } diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index 7f2cf5a918..0951a04772 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -22,10 +22,9 @@ class QmlOverlay : public Overlay2D { public: QmlOverlay(const QUrl& url); - QmlOverlay(const QUrl& url, const QmlOverlay* textOverlay); + QmlOverlay(const QUrl& url, const QmlOverlay* overlay); ~QmlOverlay(); - // Cannot fetch properties from QML based overlays due to race conditions bool supportsGetProperty() const override { return false; } void setProperties(const QVariantMap& properties) override; diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp deleted file mode 100644 index e30171bd50..0000000000 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// -// Rectangle3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Rectangle3DOverlay.h" - -#include -#include - - -QString const Rectangle3DOverlay::TYPE = "rectangle3d"; - -Rectangle3DOverlay::Rectangle3DOverlay() : - _geometryCacheID(DependencyManager::get()->allocateID()) -{ - auto geometryCache = DependencyManager::get(); - for (size_t i = 0; i < _rectGeometryIds.size(); ++i) { - _rectGeometryIds[i] = geometryCache->allocateID(); - } -} - -Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay) : - Planar3DOverlay(rectangle3DOverlay), - _geometryCacheID(DependencyManager::get()->allocateID()) -{ - auto geometryCache = DependencyManager::get(); - for (size_t i = 0; i < _rectGeometryIds.size(); ++i) { - _rectGeometryIds[i] = geometryCache->allocateID(); - } -} - -Rectangle3DOverlay::~Rectangle3DOverlay() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryCacheID); - for (size_t i = 0; i < _rectGeometryIds.size(); ++i) { - geometryCache->releaseID(_rectGeometryIds[i]); - } - } -} - -void Rectangle3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 rectangleColor(toGlm(color), alpha); - - auto batch = args->_batch; - if (batch) { - Transform transform = getRenderTransform(); - glm::vec2 halfDimensions = transform.getScale() * 0.5f; - transform.setScale(1.0f); - - batch->setModelTransform(transform); - auto geometryCache = DependencyManager::get(); - - if (getIsSolid()) { - glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, 0.0f); - glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, 0.0f); - geometryCache->bindSimpleProgram(*batch); - geometryCache->renderQuad(*batch, topLeft, bottomRight, rectangleColor, _geometryCacheID); - } else { - geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - if (getIsDashedLine()) { - glm::vec3 point1(-halfDimensions.x, -halfDimensions.y, 0.0f); - glm::vec3 point2(halfDimensions.x, -halfDimensions.y, 0.0f); - glm::vec3 point3(halfDimensions.x, halfDimensions.y, 0.0f); - glm::vec3 point4(-halfDimensions.x, halfDimensions.y, 0.0f); - - geometryCache->renderDashedLine(*batch, point1, point2, rectangleColor, _rectGeometryIds[0]); - geometryCache->renderDashedLine(*batch, point2, point3, rectangleColor, _rectGeometryIds[1]); - geometryCache->renderDashedLine(*batch, point3, point4, rectangleColor, _rectGeometryIds[2]); - geometryCache->renderDashedLine(*batch, point4, point1, rectangleColor, _rectGeometryIds[3]); - } else { - if (halfDimensions != _previousHalfDimensions) { - QVector border; - border << glm::vec3(-halfDimensions.x, -halfDimensions.y, 0.0f); - border << glm::vec3(halfDimensions.x, -halfDimensions.y, 0.0f); - border << glm::vec3(halfDimensions.x, halfDimensions.y, 0.0f); - border << glm::vec3(-halfDimensions.x, halfDimensions.y, 0.0f); - border << glm::vec3(-halfDimensions.x, -halfDimensions.y, 0.0f); - geometryCache->updateVertices(_geometryCacheID, border, rectangleColor); - - _previousHalfDimensions = halfDimensions; - } - geometryCache->renderVertices(*batch, gpu::LINE_STRIP, _geometryCacheID); - } - } - } -} - -const render::ShapeKey Rectangle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withOwnPipeline(); - if (isTransparent()) { - builder.withTranslucent(); - } - return builder.build(); -} - -/**jsdoc - * These are the properties of a rectangle3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Rectangle3DProperties - * - * @property {string} type=rectangle3d - Has the value "rectangle3d". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - */ -void Rectangle3DOverlay::setProperties(const QVariantMap& properties) { - Planar3DOverlay::setProperties(properties); -} - -Rectangle3DOverlay* Rectangle3DOverlay::createClone() const { - return new Rectangle3DOverlay(this); -} diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.h b/interface/src/ui/overlays/Rectangle3DOverlay.h deleted file mode 100644 index 645553ed38..0000000000 --- a/interface/src/ui/overlays/Rectangle3DOverlay.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Rectangle3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Rectangle3DOverlay_h -#define hifi_Rectangle3DOverlay_h - -#include "Planar3DOverlay.h" - -class Rectangle3DOverlay : public Planar3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Rectangle3DOverlay(); - Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay); - ~Rectangle3DOverlay(); - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - void setProperties(const QVariantMap& properties) override; - - virtual Rectangle3DOverlay* createClone() const override; - -private: - int _geometryCacheID; - std::array _rectGeometryIds; - glm::vec2 _previousHalfDimensions; -}; - - -#endif // hifi_Rectangle3DOverlay_h diff --git a/interface/src/ui/overlays/RectangleOverlay.cpp b/interface/src/ui/overlays/RectangleOverlay.cpp index af37a4ac02..1487a4cb63 100644 --- a/interface/src/ui/overlays/RectangleOverlay.cpp +++ b/interface/src/ui/overlays/RectangleOverlay.cpp @@ -11,29 +11,6 @@ QString const RectangleOverlay::TYPE = "rectangle"; QUrl const RectangleOverlay::URL(QString("hifi/overlays/RectangleOverlay.qml")); -// RectangleOverlay's properties are defined in the QML file specified above. -/**jsdoc - * These are the properties of a rectangle {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.RectangleProperties - * - * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. - * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. - * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. - * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. - * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. - * - * @property {Color} color=0,0,0 - The color of the overlay. Write-only. - * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. - * @property {number} borderWidth=1 - Integer width of the border, in pixels. The border is drawn within the rectangle's bounds. - * It is not drawn unless either borderColor or borderAlpha are specified. Write-only. - * @property {number} radius=0 - Integer corner radius, in pixels. Write-only. - * @property {Color} borderColor=0,0,0 - The color of the border. Write-only. - * @property {number} borderAlpha=1.0 - The opacity of the border, 0.0 - 1.0. - * Write-only. - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * Write-only. - */ - RectangleOverlay::RectangleOverlay() : QmlOverlay(URL) {} RectangleOverlay::RectangleOverlay(const RectangleOverlay* rectangleOverlay) diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp deleted file mode 100644 index 4adbbf3792..0000000000 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// -// Shape3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 this before QGLWidget, which includes an earlier version of OpenGL -#include "Shape3DOverlay.h" - -#include -#include -#include -#include - -QString const Shape3DOverlay::TYPE = "shape"; - -Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* shape3DOverlay) : - Volume3DOverlay(shape3DOverlay), - _shape(shape3DOverlay->_shape) -{ -} - -void Shape3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 shapeColor(toGlm(color), alpha); - - auto batch = args->_batch; - if (batch) { - auto geometryCache = DependencyManager::get(); - auto shapePipeline = args->_shapePipeline; - if (!shapePipeline) { - shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline(); - } - - batch->setModelTransform(getRenderTransform()); - if (_isSolid) { - geometryCache->renderSolidShapeInstance(args, *batch, _shape, shapeColor, shapePipeline); - } else { - geometryCache->renderWireShapeInstance(args, *batch, _shape, shapeColor, shapePipeline); - } - } -} - -const render::ShapeKey Shape3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); - if (isTransparent()) { - builder.withTranslucent(); - } - if (!getIsSolid()) { - builder.withUnlit().withDepthBias(); - } - return builder.build(); -} - -Shape3DOverlay* Shape3DOverlay::createClone() const { - return new Shape3DOverlay(this); -} - - -/**jsdoc - *

A shape {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Line"1DA line oriented in 3D.
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
- * @typedef {string} Overlays.Shape - */ -static const std::array shapeStrings { { - "Line", - "Triangle", - "Quad", - "Hexagon", - "Octagon", - "Circle", - "Cube", - "Sphere", - "Tetrahedron", - "Octahedron", - "Dodecahedron", - "Icosahedron", - "Torus", // Not implemented yet. - "Cone", - "Cylinder" -} }; - - -void Shape3DOverlay::setProperties(const QVariantMap& properties) { - Volume3DOverlay::setProperties(properties); - - auto shape = properties["shape"]; - if (shape.isValid()) { - const QString shapeStr = shape.toString(); - for (size_t i = 0; i < shapeStrings.size(); ++i) { - if (shapeStr == shapeStrings[i]) { - this->_shape = static_cast(i); - break; - } - } - } -} - -/**jsdoc - * These are the properties of a shape {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.ShapeProperties - * - * @property {string} type=shape - Has the value "shape". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid
, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - * - * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. - */ -QVariant Shape3DOverlay::getProperty(const QString& property) { - if (property == "shape") { - return shapeStrings[_shape]; - } - - return Volume3DOverlay::getProperty(property); -} - -Transform Shape3DOverlay::evalRenderTransform() { - // TODO: handle registration point?? - glm::vec3 position = getWorldPosition(); - glm::vec3 dimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - - Transform transform; - transform.setScale(dimensions); - transform.setTranslation(position); - transform.setRotation(rotation); - return transform; -} - -scriptable::ScriptableModelBase Shape3DOverlay::getScriptableModel() { - auto geometryCache = DependencyManager::get(); - auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModelBase result; - result.objectID = getID(); - if (auto mesh = geometryCache->meshFromShape(_shape, vertexColor)) { - result.append(mesh); - } - return result; -} diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h deleted file mode 100644 index 60af287af0..0000000000 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Shape3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Shape3DOverlay_h -#define hifi_Shape3DOverlay_h - -#include "Volume3DOverlay.h" - -#include - -class Shape3DOverlay : public Volume3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Shape3DOverlay() {} - Shape3DOverlay(const Shape3DOverlay* shape3DOverlay); - - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - - virtual Shape3DOverlay* createClone() const override; - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - virtual scriptable::ScriptableModelBase getScriptableModel() override; -protected: - Transform evalRenderTransform() override; - -private: - GeometryCache::Shape _shape { GeometryCache::Hexagon }; -}; - - -#endif // hifi_Shape3DOverlay_h diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp deleted file mode 100644 index b1d5c878c4..0000000000 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// -// Sphere3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Sphere3DOverlay.h" - -#include -#include -#include -#include - -QString const Sphere3DOverlay::TYPE = "sphere"; - -// Sphere overlays should fit inside a cube of the specified dimensions, hence it needs to be a half unit sphere. -// However, the geometry cache renders a UNIT sphere, so we need to scale down. -static const float SPHERE_OVERLAY_SCALE = 0.5f; - -Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : - Volume3DOverlay(Sphere3DOverlay) -{ -} - -// If Sphere3DOverlay had a getProperty() method then it would go here; do JSDoc here. -/**jsdoc - * These are the properties of a sphere {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.SphereProperties - * - * @property {string} type=sphere - Has the value "sphere". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - */ - -void Sphere3DOverlay::render(RenderArgs* args) { - if (!_renderVisible) { - return; // do nothing if we're not visible - } - - float alpha = getAlpha(); - glm::u8vec3 color = getColor(); - glm::vec4 sphereColor(toGlm(color), alpha); - - auto batch = args->_batch; - - if (batch) { - batch->setModelTransform(getRenderTransform()); - - auto geometryCache = DependencyManager::get(); - auto shapePipeline = args->_shapePipeline; - if (!shapePipeline) { - shapePipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline(); - } - - if (_isSolid) { - geometryCache->renderSolidSphereInstance(args, *batch, sphereColor, shapePipeline); - } else { - geometryCache->renderWireSphereInstance(args, *batch, sphereColor, shapePipeline); - } - } -} - -const render::ShapeKey Sphere3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); - if (isTransparent()) { - builder.withTranslucent(); - } - if (!getIsSolid()) { - builder.withUnlit().withDepthBias(); - } - return builder.build(); -} - -Sphere3DOverlay* Sphere3DOverlay::createClone() const { - return new Sphere3DOverlay(this); -} - -Transform Sphere3DOverlay::evalRenderTransform() { - Transform transform = getTransform(); - transform.setScale(1.0f); // ignore inherited scale from SpatiallyNestable - transform.postScale(getDimensions() * SPHERE_OVERLAY_SCALE); - - return transform; -} - - -scriptable::ScriptableModelBase Sphere3DOverlay::getScriptableModel() { - auto geometryCache = DependencyManager::get(); - auto vertexColor = ColorUtils::toVec3(_color); - scriptable::ScriptableModelBase result; - if (auto mesh = geometryCache->meshFromShape(GeometryCache::Sphere, vertexColor)) { - result.objectID = getID(); - result.append(mesh); - } - return result; -} diff --git a/interface/src/ui/overlays/Sphere3DOverlay.h b/interface/src/ui/overlays/Sphere3DOverlay.h deleted file mode 100644 index 9a434e7182..0000000000 --- a/interface/src/ui/overlays/Sphere3DOverlay.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// Sphere3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Sphere3DOverlay_h -#define hifi_Sphere3DOverlay_h - -#include "Volume3DOverlay.h" - -class Sphere3DOverlay : public Volume3DOverlay { - Q_OBJECT - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Sphere3DOverlay() {} - Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay); - - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - - virtual Sphere3DOverlay* createClone() const override; - - virtual scriptable::ScriptableModelBase getScriptableModel() override; -protected: - Transform evalRenderTransform() override; -}; - - -#endif // hifi_Sphere3DOverlay_h diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp deleted file mode 100644 index 58ce16a9fc..0000000000 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ /dev/null @@ -1,295 +0,0 @@ -// -// Text3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Text3DOverlay.h" - -#include -#include -#include -#include -#include - -#include - -const int FIXED_FONT_POINT_SIZE = 40; -const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 92.0f; // Determined through experimentation to fit font to line - // height. -const float LINE_SCALE_RATIO = 1.2f; - -QString const Text3DOverlay::TYPE = "text3d"; - -Text3DOverlay::Text3DOverlay() { - _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE); - _geometryId = DependencyManager::get()->allocateID(); -} - -Text3DOverlay::Text3DOverlay(const Text3DOverlay* text3DOverlay) : - Billboard3DOverlay(text3DOverlay), - _text(text3DOverlay->_text), - _backgroundColor(text3DOverlay->_backgroundColor), - _textAlpha(text3DOverlay->_textAlpha), - _lineHeight(text3DOverlay->_lineHeight), - _leftMargin(text3DOverlay->_leftMargin), - _topMargin(text3DOverlay->_topMargin), - _rightMargin(text3DOverlay->_rightMargin), - _bottomMargin(text3DOverlay->_bottomMargin) -{ - _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE); - _geometryId = DependencyManager::get()->allocateID(); -} - -Text3DOverlay::~Text3DOverlay() { - delete _textRenderer; - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -const QString Text3DOverlay::getText() const { - QMutexLocker lock(&_mutex); - return _text; -} - -void Text3DOverlay::setText(const QString& text) { - QMutexLocker lock(&_mutex); - _text = text; -} - -glm::u8vec3 Text3DOverlay::getBackgroundColor() { - if (_colorPulse == 0.0f) { - return _backgroundColor; - } - - float pulseLevel = updatePulse(); - glm::u8vec3 result = _backgroundColor; - if (_colorPulse < 0.0f) { - result.x *= (1.0f - pulseLevel); - result.y *= (1.0f - pulseLevel); - result.z *= (1.0f - pulseLevel); - } else { - result.x *= pulseLevel; - result.y *= pulseLevel; - result.z *= pulseLevel; - } - return result; -} - -void Text3DOverlay::render(RenderArgs* args) { - if (!_renderVisible || !getParentVisible()) { - return; // do nothing if we're not visible - } - - Q_ASSERT(args->_batch); - auto& batch = *args->_batch; - - auto transform = getRenderTransform(); - batch.setModelTransform(transform); - - glm::u8vec3 backgroundColor = getBackgroundColor(); - glm::vec4 quadColor(toGlm(backgroundColor), getBackgroundAlpha()); - - glm::vec2 dimensions = getDimensions(); - glm::vec2 halfDimensions = dimensions * 0.5f; - - const float SLIGHTLY_BEHIND = -0.001f; - - glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND); - glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND); - DependencyManager::get()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false); - DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, quadColor, _geometryId); - - // Same font properties as textSize() - float maxHeight = (float)_textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO; - - float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight; - - glm::vec2 clipDimensions((dimensions.x - (_leftMargin + _rightMargin)) / scaleFactor, - (dimensions.y - (_topMargin + _bottomMargin)) / scaleFactor); - - transform.postTranslate(glm::vec3(-(halfDimensions.x - _leftMargin), - halfDimensions.y - _topMargin, 0.001f)); - transform.setScale(scaleFactor); - batch.setModelTransform(transform); - - glm::vec4 textColor = { toGlm(_color), getTextAlpha() }; - - // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline for a gpu performance increase. - _textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f)); -} - -const render::ShapeKey Text3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder(); - if (isTransparent()) { - builder.withTranslucent(); - } - return builder.build(); -} - -void Text3DOverlay::setProperties(const QVariantMap& properties) { - Billboard3DOverlay::setProperties(properties); - - auto text = properties["text"]; - if (text.isValid()) { - setText(text.toString()); - } - - auto textAlpha = properties["textAlpha"]; - if (textAlpha.isValid()) { - float prevTextAlpha = getTextAlpha(); - setTextAlpha(textAlpha.toFloat()); - // Update our payload key if necessary to handle transparency - if ((prevTextAlpha < 1.0f && _textAlpha >= 1.0f) || (prevTextAlpha >= 1.0f && _textAlpha < 1.0f)) { - auto itemID = getRenderItemID(); - if (render::Item::isValidID(itemID)) { - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - render::Transaction transaction; - transaction.updateItem(itemID); - scene->enqueueTransaction(transaction); - } - } - } - - bool valid; - auto backgroundColor = properties["backgroundColor"]; - if (backgroundColor.isValid()) { - auto color = u8vec3FromVariant(backgroundColor, valid); - if (valid) { - _backgroundColor = color; - } - } - - if (properties["backgroundAlpha"].isValid()) { - setAlpha(properties["backgroundAlpha"].toFloat()); - } - - if (properties["lineHeight"].isValid()) { - setLineHeight(properties["lineHeight"].toFloat()); - } - - if (properties["leftMargin"].isValid()) { - setLeftMargin(properties["leftMargin"].toFloat()); - } - - if (properties["topMargin"].isValid()) { - setTopMargin(properties["topMargin"].toFloat()); - } - - if (properties["rightMargin"].isValid()) { - setRightMargin(properties["rightMargin"].toFloat()); - } - - if (properties["bottomMargin"].isValid()) { - setBottomMargin(properties["bottomMargin"].toFloat()); - } -} - -/**jsdoc - * These are the properties of a text3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Text3DProperties - * - * @property {string} type=text3d - Has the value "text3d". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. - * - * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis - * parallel to the user's avatar's "up" direction. - * - * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. - * @property {number} textAlpha=1 - The text alpha value. - * @property {Color} backgroundColor=0,0,0 - The background color. - * @property {number} backgroundAlpha=0.7 - The background alpha value. - * @property {number} lineHeight=1 - The height of a line of text in meters. - * @property {number} leftMargin=0.1 - The left margin, in meters. - * @property {number} topMargin=0.1 - The top margin, in meters. - * @property {number} rightMargin=0.1 - The right margin, in meters. - * @property {number} bottomMargin=0.1 - The bottom margin, in meters. - */ - -QVariant Text3DOverlay::getProperty(const QString& property) { - if (property == "text") { - return getText(); - } - if (property == "textAlpha") { - return _textAlpha; - } - if (property == "backgroundColor") { - return u8vec3ColortoVariant(_backgroundColor); - } - if (property == "backgroundAlpha") { - return Billboard3DOverlay::getProperty("alpha"); - } - if (property == "lineHeight") { - return _lineHeight; - } - if (property == "leftMargin") { - return _leftMargin; - } - if (property == "topMargin") { - return _topMargin; - } - if (property == "rightMargin") { - return _rightMargin; - } - if (property == "bottomMargin") { - return _bottomMargin; - } - - return Billboard3DOverlay::getProperty(property); -} - -Text3DOverlay* Text3DOverlay::createClone() const { - return new Text3DOverlay(this);; -} - -QSizeF Text3DOverlay::textSize(const QString& text) const { - auto extents = _textRenderer->computeExtent(text); - - float maxHeight = (float)_textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO; - float pointToWorldScale = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight; - - return QSizeF(extents.x, extents.y) * pointToWorldScale; -} diff --git a/interface/src/ui/overlays/Text3DOverlay.h b/interface/src/ui/overlays/Text3DOverlay.h deleted file mode 100644 index 16bbdcb4c4..0000000000 --- a/interface/src/ui/overlays/Text3DOverlay.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// Text3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Text3DOverlay_h -#define hifi_Text3DOverlay_h - -#include -#include -#include "Billboard3DOverlay.h" - -class TextRenderer3D; - -class Text3DOverlay : public Billboard3DOverlay { - Q_OBJECT - using Parent = Billboard3DOverlay; - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Text3DOverlay(); - Text3DOverlay(const Text3DOverlay* text3DOverlay); - ~Text3DOverlay(); - virtual void render(RenderArgs* args) override; - - virtual const render::ShapeKey getShapeKey() override; - - // getters - const QString getText() const; - float getLineHeight() const { return _lineHeight; } - float getLeftMargin() const { return _leftMargin; } - float getTopMargin() const { return _topMargin; } - float getRightMargin() const { return _rightMargin; } - float getBottomMargin() const { return _bottomMargin; } - glm::u8vec3 getBackgroundColor(); - float getTextAlpha() { return _textAlpha; } - float getBackgroundAlpha() { return getAlpha(); } - bool isTransparent() override { return Overlay::isTransparent() || _textAlpha < 1.0f; } - - // setters - void setText(const QString& text); - void setTextAlpha(float alpha) { _textAlpha = alpha; } - void setLineHeight(float value) { _lineHeight = value; } - void setLeftMargin(float margin) { _leftMargin = margin; } - void setTopMargin(float margin) { _topMargin = margin; } - void setRightMargin(float margin) { _rightMargin = margin; } - void setBottomMargin(float margin) { _bottomMargin = margin; } - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - QSizeF textSize(const QString& test) const; // Meters - - virtual Text3DOverlay* createClone() const override; - -private: - TextRenderer3D* _textRenderer = nullptr; - - QString _text; - mutable QMutex _mutex; // used to make get/setText threadsafe, mutable so can be used in const functions - glm::u8vec3 _backgroundColor { 0, 0, 0 }; - float _textAlpha { 1.0f }; - float _lineHeight { 1.0f }; - float _leftMargin { 0.1f }; - float _topMargin { 0.1f }; - float _rightMargin { 0.1f }; - float _bottomMargin { 0.1f }; - int _geometryId { 0 }; -}; - -#endif // hifi_Text3DOverlay_h diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index e7641ab2c2..2f4353dae8 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -27,43 +27,12 @@ QString const TextOverlay::TYPE = "text"; QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml")); -// TextOverlay's properties are defined in the QML file specified above. -/**jsdoc - * These are the properties of a text {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.TextProperties - * - * @property {Rect} bounds - The position and size of the rectangle, in pixels. Write-only. - * @property {number} x - Integer left, x-coordinate value = bounds.x. Write-only. - * @property {number} y - Integer top, y-coordinate value = bounds.y. Write-only. - * @property {number} width - Integer width of the rectangle = bounds.width. Write-only. - * @property {number} height - Integer height of the rectangle = bounds.height. Write-only. - * - * @property {number} margin=0 - Sets the leftMargin and topMargin values, in pixels. - * Write-only. - * @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin. - * Write-only. - * @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin. - * Write-only. - * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. Text - * is clipped to the bounds. Write-only. - * @property {number} font.size=18 - The size of the text, in pixels. Write-only. - * @property {number} lineHeight=18 - The height of a line of text, in pixels. Write-only. - * @property {Color} color=255,255,255 - The color of the text. Synonym: textColor. Write-only. - * @property {number} alpha=1.0 - The opacity of the overlay, 0.0 - 1.0. Write-only. - * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. Write-only. - * @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle. Write-only. - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * Write-only. - */ - TextOverlay::TextOverlay() : QmlOverlay(URL) { } TextOverlay::TextOverlay(const TextOverlay* textOverlay) : QmlOverlay(URL, textOverlay) { } -TextOverlay::~TextOverlay() { } - TextOverlay* TextOverlay::createClone() const { return new TextOverlay(this); } @@ -80,10 +49,4 @@ QSizeF TextOverlay::textSize(const QString& text) const { QFontMetrics fm(font); QSizeF result = QSizeF(fm.width(text), 18 * lines); return result; -} - - -void TextOverlay::setTopMargin(float margin) {} -void TextOverlay::setLeftMargin(float margin) {} -void TextOverlay::setFontSize(float size) {} -void TextOverlay::setText(const QString& text) {} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 53c1805345..e78e2afa04 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -19,16 +19,8 @@ public: QString getType() const override { return TYPE; } static QUrl const URL; - TextOverlay(); TextOverlay(const TextOverlay* textOverlay); - ~TextOverlay(); - - void setTopMargin(float margin); - void setLeftMargin(float margin); - void setFontSize(float size); - void setText(const QString& text); - TextOverlay* createClone() const override; QSizeF textSize(const QString& text) const; // Pixels diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp deleted file mode 100644 index 0cceb44a36..0000000000 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// -// Volume3DOverlay.cpp -// interface/src/ui/overlays -// -// 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 "Volume3DOverlay.h" - -#include -#include - -Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) : - Base3DOverlay(volume3DOverlay), - _localBoundingBox(volume3DOverlay->_localBoundingBox) -{ -} - -AABox Volume3DOverlay::getBounds() const { - AABox bounds = _localBoundingBox; - bounds.transform(getTransform()); - return bounds; -} - -void Volume3DOverlay::setDimensions(const glm::vec3& value) { - _localBoundingBox.setBox(-value / 2.0f, value); - notifyRenderVariableChange(); -} - -void Volume3DOverlay::setProperties(const QVariantMap& properties) { - Base3DOverlay::setProperties(properties); - - auto dimensions = properties["dimensions"]; - - // if "dimensions" property was not there, check to see if they included aliases: scale, size - if (!dimensions.isValid()) { - dimensions = properties["scale"]; - if (!dimensions.isValid()) { - dimensions = properties["size"]; - } - } - - if (dimensions.isValid()) { - glm::vec3 scale = vec3FromVariant(dimensions); - // don't allow a zero or negative dimension component to reach the renderTransform - const float MIN_DIMENSION = 0.0001f; - scale = glm::max(scale, MIN_DIMENSION); - setDimensions(scale); - } -} - -// JSDoc for copying to @typedefs of overlay types that inherit Volume3DOverlay. -/**jsdoc - * @typedef - * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. - */ -QVariant Volume3DOverlay::getProperty(const QString& property) { - if (property == "dimensions" || property == "scale" || property == "size") { - return vec3toVariant(getDimensions()); - } - - return Base3DOverlay::getProperty(property); -} - -bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 worldToEntityMatrix; - Transform transform = getTransform(); - transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable - transform.getInverseMatrix(worldToEntityMatrix); - - glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 overlayFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); - - // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame - // and testing intersection there. - bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, 1.0f / overlayFrameDirection, distance, face, surfaceNormal); - - if (hit) { - surfaceNormal = transform.getRotation() * surfaceNormal; - } - return hit; -} - -bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, - float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { - // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 worldToEntityMatrix; - Transform transform = getTransform(); - transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable - transform.getInverseMatrix(worldToEntityMatrix); - - glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); - glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); - - // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame - // and testing intersection there. - bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal); - - if (hit) { - surfaceNormal = transform.getRotation() * surfaceNormal; - } - return hit; -} - -Transform Volume3DOverlay::evalRenderTransform() { - Transform transform = getTransform(); -#ifndef USE_SN_SCALE - transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable -#endif - return transform; -} diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h deleted file mode 100644 index 2083f7344a..0000000000 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Volume3DOverlay.h -// interface/src/ui/overlays -// -// 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 -// - -#ifndef hifi_Volume3DOverlay_h -#define hifi_Volume3DOverlay_h - -#include "Base3DOverlay.h" - -class Volume3DOverlay : public Base3DOverlay { - Q_OBJECT - using Parent = Base3DOverlay; - -public: - Volume3DOverlay() {} - Volume3DOverlay(const Volume3DOverlay* volume3DOverlay); - - virtual AABox getBounds() const override; - - const glm::vec3& getDimensions() const { return _localBoundingBox.getDimensions(); } - void setDimensions(const glm::vec3& value); - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; - -protected: - // Centered local bounding box - AABox _localBoundingBox { vec3(-0.5), 1.0f }; - - Transform evalRenderTransform() override; -}; - - -#endif // hifi_Volume3DOverlay_h diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp deleted file mode 100644 index eb61ca7281..0000000000 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ /dev/null @@ -1,490 +0,0 @@ -// -// Web3DOverlay.cpp -// -// Created by Clement on 7/1/14. -// Modified and renamed by Zander Otavka on 8/4/15 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "Web3DOverlay.h" - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "scripting/HMDScriptingInterface.h" -#include "scripting/AssetMappingsScriptingInterface.h" -#include "scripting/MenuScriptingInterface.h" -#include "scripting/SettingsScriptingInterface.h" -#include "scripting/KeyboardScriptingInterface.h" -#include -#include -#include -#include "FileDialogHelper.h" -#include "avatar/AvatarManager.h" -#include "AudioClient.h" -#include "LODManager.h" -#include "ui/LoginDialog.h" -#include "ui/OctreeStatsProvider.h" -#include "ui/DomainConnectionModel.h" -#include "ui/AvatarInputs.h" -#include "avatar/AvatarManager.h" -#include "scripting/AccountServicesScriptingInterface.h" -#include "scripting/WalletScriptingInterface.h" -#include -#include "ui/Snapshot.h" -#include "SoundCacheScriptingInterface.h" -#include "raypick/PointerScriptingInterface.h" -#include -#include "AboutUtil.h" -#include "ResourceRequestObserver.h" - -#include - -static int MAX_WINDOW_SIZE = 4096; -static const float METERS_TO_INCHES = 39.3701f; -static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; - -const QString Web3DOverlay::TYPE = "web3d"; - -Web3DOverlay::Web3DOverlay() { - _touchDevice.setCapabilities(QTouchDevice::Position); - _touchDevice.setType(QTouchDevice::TouchScreen); - _touchDevice.setName("Web3DOverlayTouchDevice"); - _touchDevice.setMaximumTouchPoints(4); - - _geometryId = DependencyManager::get()->allocateID(); - connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); - connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); - - buildWebSurface(true); -} - -Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : - Billboard3DOverlay(Web3DOverlay), - _url(Web3DOverlay->_url), - _scriptURL(Web3DOverlay->_scriptURL), - _dpi(Web3DOverlay->_dpi) -{ - _geometryId = DependencyManager::get()->allocateID(); -} - -Web3DOverlay::~Web3DOverlay() { - destroyWebSurface(); - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -void Web3DOverlay::rebuildWebSurface() { - destroyWebSurface(); - buildWebSurface(); -} - -void Web3DOverlay::destroyWebSurface() { - if (_webSurface) { - render::entities::WebEntityRenderer::releaseWebSurface(_webSurface, _cachedWebSurface, _connections); - } -} - -void Web3DOverlay::buildWebSurface(bool overrideWeb) { - if (_webSurface) { - return; - } - - render::entities::WebEntityRenderer::acquireWebSurface(_url, overrideWeb || isWebContent(), _webSurface, _cachedWebSurface); - onResizeWebSurface(); - _webSurface->resume(); - - _connections.push_back(QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent)); - _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived)); -} - -void Web3DOverlay::update(float deltatime) { - if (_webSurface) { - // update globalPosition - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); - } - Parent::update(deltatime); -} - -bool Web3DOverlay::isWebContent() const { - QUrl sourceUrl(_url); - if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || - _url.toLower().endsWith(".htm") || _url.toLower().endsWith(".html")) { - return true; - } - return false; -} - -void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { - // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces and the current rendering load) - _desiredMaxFPS = maxFPS; - if (_webSurface) { - _webSurface->setMaxFps(_desiredMaxFPS); - _currentMaxFPS = _desiredMaxFPS; - } -} - -void Web3DOverlay::onResizeWebSurface() { - glm::vec2 dims = glm::vec2(getDimensions()); - dims *= METERS_TO_INCHES * _dpi; - - // ensure no side is never larger then MAX_WINDOW_SIZE - float max = (dims.x > dims.y) ? dims.x : dims.y; - if (max > MAX_WINDOW_SIZE) { - dims *= MAX_WINDOW_SIZE / max; - } - - _webSurface->resize(QSize(dims.x, dims.y)); -} - -void Web3DOverlay::render(RenderArgs* args) { - if (!_renderVisible || !getParentVisible()) { - return; - } - - if (!_webSurface) { - emit requestWebSurface(false); - return; - } - - if (_mayNeedResize) { - emit resizeWebSurface(); - } - - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - - vec4 color(toGlm(getColor()), getAlpha()); - - if (!_texture) { - _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); - _texture->setSource(__FUNCTION__); - } - OffscreenQmlSurface::TextureAndFence newTextureAndFence; - bool newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence); - if (newTextureAvailable) { - _texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second); - } - - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - batch.setResourceTexture(0, _texture); - - auto renderTransform = getRenderTransform(); - auto size = renderTransform.getScale(); - renderTransform.setScale(1.0f); - batch.setModelTransform(renderTransform); - - // Turn off jitter for these entities - batch.pushProjectionJitter(); - - auto geometryCache = DependencyManager::get(); - if (color.a < OPAQUE_ALPHA_THRESHOLD) { - geometryCache->bindWebBrowserProgram(batch, true); - } else { - geometryCache->bindWebBrowserProgram(batch); - } - vec2 halfSize = vec2(size.x, size.y) / 2.0f; - geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color, _geometryId); - batch.popProjectionJitter(); // Restore jitter - batch.setResourceTexture(0, nullptr); // restore default white color after me - -} - -Transform Web3DOverlay::evalRenderTransform() { - Transform transform = Parent::evalRenderTransform(); - transform.setScale(1.0f); - transform.postScale(glm::vec3(getDimensions(), 1.0f)); - return transform; -} - -const render::ShapeKey Web3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias().withOwnPipeline(); - if (isTransparent()) { - builder.withTranslucent(); - } - return builder.build(); -} - -QObject* Web3DOverlay::getEventHandler() { - if (!_webSurface) { - return nullptr; - } - return _webSurface->getEventHandler(); -} - -void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) { - if (!_webSurface) { - return; - } - - _webSurface->setProxyWindow(proxyWindow); -} - -void Web3DOverlay::hoverEnterOverlay(const PointerEvent& event) { - if (_inputMode == Mouse) { - handlePointerEvent(event); - } else if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverBeginEvent(webEvent, _touchDevice); - } -} - -void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { - if (_inputMode == Mouse) { - PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), - event.getButton(), event.getButtons(), event.getKeyboardModifiers()); - handlePointerEvent(endEvent); - // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. - PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); - handlePointerEvent(endMoveEvent); - } else if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverEndEvent(webEvent, _touchDevice); - } -} - -void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { - if (_inputMode == Touch) { - handlePointerEventAsTouch(event); - } else { - handlePointerEventAsMouse(event); - } -} - -void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { - if (_webSurface) { - PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->handlePointerEvent(webEvent, _touchDevice); - } -} - -void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { - if (!_webSurface) { - return; - } - - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); - QPointF windowPoint(windowPos.x, windowPos.y); - - Qt::MouseButtons buttons = Qt::NoButton; - if (event.getButtons() & PointerEvent::PrimaryButton) { - buttons |= Qt::LeftButton; - } - - Qt::MouseButton button = Qt::NoButton; - if (event.getButton() == PointerEvent::PrimaryButton) { - button = Qt::LeftButton; - } - - QEvent::Type type; - switch (event.getType()) { - case PointerEvent::Press: - type = QEvent::MouseButtonPress; - break; - case PointerEvent::Release: - type = QEvent::MouseButtonRelease; - break; - case PointerEvent::Move: - type = QEvent::MouseMove; - break; - default: - return; - } - - QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); -} - -void Web3DOverlay::setProperties(const QVariantMap& properties) { - Billboard3DOverlay::setProperties(properties); - - auto urlValue = properties["url"]; - if (urlValue.isValid()) { - QString newURL = urlValue.toString(); - if (newURL != _url) { - setURL(newURL); - } - } - - auto scriptURLValue = properties["scriptURL"]; - if (scriptURLValue.isValid()) { - QString newScriptURL = scriptURLValue.toString(); - if (newScriptURL != _scriptURL) { - setScriptURL(newScriptURL); - } - } - - auto dpi = properties["dpi"]; - if (dpi.isValid()) { - _dpi = dpi.toFloat(); - _mayNeedResize = true; - } - - auto maxFPS = properties["maxFPS"]; - if (maxFPS.isValid()) { - _desiredMaxFPS = maxFPS.toInt(); - } - - auto inputModeValue = properties["inputMode"]; - if (inputModeValue.isValid()) { - QString inputModeStr = inputModeValue.toString(); - if (inputModeStr == "Mouse") { - _inputMode = Mouse; - } else { - _inputMode = Touch; - } - } -} - -// Web3DOverlay overrides the meaning of Planar3DOverlay's dimensions property. -/**jsdoc - * These are the properties of a web3d {@link Overlays.OverlayType|OverlayType}. - * @typedef {object} Overlays.Web3DProperties - * - * @property {string} type=web3d - Has the value "web3d". Read-only. - * @property {Color} color=255,255,255 - The color of the overlay. - * @property {number} alpha=0.7 - The opacity of the overlay, 0.0 - 1.0. - * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. - * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. - * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from - * pulseMin to pulseMax, then pulseMax to pulseMin in one period. - * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the - * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 - * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise - * used.) - * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * - * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and - * start. - * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as position. - * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. - * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a - * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. - * Antonyms: isWire and wire. - * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. Deprecated. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. - * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't - * have drawInFront set to true, and in front of entities. - * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. - * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. - * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if - * parentID is an avatar skeleton. A value of 65535 means "no joint". - * - * @property {boolean} isFacingAvatar - If true, the overlay is rotated to face the user's camera about an axis - * parallel to the user's avatar's "up" direction. - * - * @property {string} url - The URL of the Web page to display. - * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. - * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. - * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: - * scale, size. - * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. - * @property {string} inputMode=Touch - The user input mode to use - either "Touch" or "Mouse". - */ -QVariant Web3DOverlay::getProperty(const QString& property) { - if (property == "url") { - return _url; - } - if (property == "scriptURL") { - return _scriptURL; - } - if (property == "dpi") { - return _dpi; - } - if (property == "maxFPS") { - return _desiredMaxFPS; - } - - if (property == "inputMode") { - if (_inputMode == Mouse) { - return QVariant("Mouse"); - } else { - return QVariant("Touch"); - } - } - return Billboard3DOverlay::getProperty(property); -} - -void Web3DOverlay::setURL(const QString& url) { - if (url != _url) { - bool wasWebContent = isWebContent(); - _url = url; - if (_webSurface) { - if (wasWebContent && isWebContent()) { - // If we're just targeting a new web URL, then switch to that without messing around - // with the underlying QML - AbstractViewStateInterface::instance()->postLambdaEvent([this] { - _webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, _url); - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); - }); - } else { - // If we're switching to or from web content, or between different QML content - // we need to destroy and rebuild the entire QML surface - AbstractViewStateInterface::instance()->postLambdaEvent([this] { - rebuildWebSurface(); - }); - } - } - } -} - -void Web3DOverlay::setScriptURL(const QString& scriptURL) { - _scriptURL = scriptURL; - if (_webSurface) { - AbstractViewStateInterface::instance()->postLambdaEvent([this, scriptURL] { - if (!_webSurface) { - return; - } - _webSurface->getRootItem()->setProperty("scriptURL", scriptURL); - }); - } -} - -Web3DOverlay* Web3DOverlay::createClone() const { - return new Web3DOverlay(this); -} - -void Web3DOverlay::emitScriptEvent(const QVariant& message) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); -} diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h deleted file mode 100644 index 4265c35699..0000000000 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ /dev/null @@ -1,99 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/08/31 -// Copyright 2015 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_Web3DOverlay_h -#define hifi_Web3DOverlay_h - -#include "Billboard3DOverlay.h" - -#include - -#include - -class OffscreenQmlSurface; - -class Web3DOverlay : public Billboard3DOverlay { - Q_OBJECT - using Parent = Billboard3DOverlay; - -public: - static QString const TYPE; - virtual QString getType() const override { return TYPE; } - - Web3DOverlay(); - Web3DOverlay(const Web3DOverlay* Web3DOverlay); - virtual ~Web3DOverlay(); - - void setMaxFPS(uint8_t maxFPS); - virtual void render(RenderArgs* args) override; - virtual const render::ShapeKey getShapeKey() override; - - virtual void update(float deltatime) override; - - QObject* getEventHandler(); - void setProxyWindow(QWindow* proxyWindow); - Q_INVOKABLE void hoverEnterOverlay(const PointerEvent& event); - Q_INVOKABLE void hoverLeaveOverlay(const PointerEvent& event); - Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); - void handlePointerEventAsTouch(const PointerEvent& event); - void handlePointerEventAsMouse(const PointerEvent& event); - - // setters - void setURL(const QString& url); - void setScriptURL(const QString& script); - - void setProperties(const QVariantMap& properties) override; - QVariant getProperty(const QString& property) override; - - virtual Web3DOverlay* createClone() const override; - - enum InputMode { - Touch, - Mouse - }; - - void buildWebSurface(bool overrideWeb = false); - void destroyWebSurface(); - void onResizeWebSurface(); - -public slots: - void emitScriptEvent(const QVariant& scriptMessage); - -signals: - void scriptEventReceived(const QVariant& message); - void webEventReceived(const QVariant& message); - void resizeWebSurface(); - void requestWebSurface(bool overrideWeb); - -protected: - Transform evalRenderTransform() override; - -private: - void rebuildWebSurface(); - bool isWebContent() const; - - InputMode _inputMode { Touch }; - QSharedPointer _webSurface; - bool _cachedWebSurface { false }; - gpu::TexturePointer _texture; - QString _url; - QString _scriptURL; - float _dpi { 30.0f }; - int _geometryId { 0 }; - - QTouchDevice _touchDevice; - - uint8_t _desiredMaxFPS { 10 }; - uint8_t _currentMaxFPS { 0 }; - - bool _mayNeedResize { false }; - - std::vector _connections; -}; - -#endif // hifi_Web3DOverlay_h diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 30addadcaa..1ab54ed342 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -4,5 +4,6 @@ link_hifi_libraries(shared graphics fbx) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(hfm) +include_hifi_library_headers(image) target_nsight() diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 07e1c17f77..28b8bb4a9f 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -151,10 +151,10 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn if (_phase < 0.0f) { _phase = 0.0f; } - + // detect loop trigger events if (_phase >= 1.0f) { - triggersOut.setTrigger(_id + "Loop"); + triggersOut.setTrigger(_id + "OnLoop"); _phase = glm::fract(_phase); } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 1adc04ee1b..71b876ff8c 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -140,10 +140,10 @@ void AnimClip::copyFromNetworkAnim() { postRot = animSkeleton.getPostRotationPose(animJoint); // cancel out scale - preRot.scale() = glm::vec3(1.0f); - postRot.scale() = glm::vec3(1.0f); + preRot.scale() = 1.0f; + postRot.scale() = 1.0f; - AnimPose rot(glm::vec3(1.0f), hfmAnimRot, glm::vec3()); + AnimPose rot(1.0f, hfmAnimRot, glm::vec3()); // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. @@ -155,7 +155,7 @@ void AnimClip::copyFromNetworkAnim() { boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(hfmZeroTrans); } - AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); + AnimPose trans = AnimPose(1.0f, glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a1809f3438..7af9e81889 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -552,7 +552,7 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const AnimPose accum = absolutePoses[_hipsIndex]; AnimPose baseParentPose = absolutePoses[_hipsIndex]; for (int i = (int)chainDepth - 1; i >= 0; i--) { - accum = accum * AnimPose(glm::vec3(1.0f), jointChainInfoOut.jointInfoVec[i].rot, jointChainInfoOut.jointInfoVec[i].trans); + accum = accum * AnimPose(1.0f, jointChainInfoOut.jointInfoVec[i].rot, jointChainInfoOut.jointInfoVec[i].trans); postAbsPoses[i] = accum; if (jointChainInfoOut.jointInfoVec[i].jointIndex == topJointIndex) { topChainIndex = i; @@ -734,7 +734,7 @@ void AnimInverseKinematics::computeAndCacheSplineJointInfosForIKTarget(const Ani glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose pose(glm::vec3(1.0f), rot, spline(t)); + AnimPose pose(1.0f, rot, spline(t)); AnimPose offsetPose = pose.inverse() * defaultPose; SplineJointInfo splineJointInfo = { index, ratio, offsetPose }; @@ -767,7 +767,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co const int baseIndex = _hipsIndex; // build spline from tip to base - AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); AnimPose basePose = absolutePoses[baseIndex]; CubicHermiteSplineFunctorWithArcLength spline; if (target.getIndex() == _headIndex) { @@ -815,7 +815,7 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co glm::mat3 m(u, v, glm::cross(u, v)); glm::quat rot = glm::normalize(glm::quat_cast(m)); - AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose; + AnimPose desiredAbsPose = AnimPose(1.0f, rot, trans) * splineJointInfo.offsetPose; // apply flex coefficent AnimPose flexedAbsPose; @@ -965,7 +965,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } _relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose; - _relativePoses[_hipsIndex].scale() = glm::vec3(1.0f); + _relativePoses[_hipsIndex].scale() = 1.0f; } // if there is an active jointChainInfo for the hips store the post shifted hips into it. @@ -1753,7 +1753,7 @@ void AnimInverseKinematics::setSecondaryTargets(const AnimContext& context) { AnimPose rigToGeometryPose = AnimPose(glm::inverse(context.getGeometryToRigMatrix())); for (auto& iter : _secondaryTargetsInRigFrame) { AnimPose absPose = rigToGeometryPose * iter.second; - absPose.scale() = glm::vec3(1.0f); + absPose.scale() = 1.0f; AnimPose parentAbsPose; int parentIndex = _skeleton->getParentIndex(iter.first); @@ -1825,7 +1825,7 @@ void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, co const int baseIndex = _hipsIndex; // build spline - AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation()); + AnimPose tipPose = AnimPose(1.0f, target.getRotation(), target.getTranslation()); AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses); CubicHermiteSplineFunctorWithArcLength spline; diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 1146cbb19a..c75c9865bb 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -172,5 +172,5 @@ AnimPose AnimManipulator::computeRelativePoseFromJointVar(const AnimVariantMap& break; } - return AnimPose(glm::vec3(1), relRot, relTrans); + return AnimPose(1.0f, relRot, relTrans); } diff --git a/libraries/animation/src/AnimPoleVectorConstraint.cpp b/libraries/animation/src/AnimPoleVectorConstraint.cpp index d575ddabe4..4b91985b0b 100644 --- a/libraries/animation/src/AnimPoleVectorConstraint.cpp +++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp @@ -99,7 +99,7 @@ const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& anim AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex); // Look up refVector from animVars, make sure to convert into geom space. - glm::vec3 refVector = midPose.xformVectorFast(_referenceVector); + glm::vec3 refVector = midPose.xformVector(_referenceVector); float refVectorLength = glm::length(refVector); glm::vec3 axis = basePose.trans() - tipPose.trans(); diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index d77514e691..366d863c3d 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -14,15 +14,14 @@ #include #include "AnimUtil.h" -const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), - glm::quat(), - glm::vec3(0.0f)); +const AnimPose AnimPose::identity = AnimPose(1.0f, glm::quat(), glm::vec3(0.0f)); AnimPose::AnimPose(const glm::mat4& mat) { static const float EPSILON = 0.0001f; - _scale = extractScale(mat); + glm::vec3 scale = extractScale(mat); // quat_cast doesn't work so well with scaled matrices, so cancel it out. - glm::mat4 tmp = glm::scale(mat, 1.0f / _scale); + glm::mat4 tmp = glm::scale(mat, 1.0f / scale); + _scale = extractUniformScale(scale); _rot = glm::quat_cast(tmp); float lengthSquared = glm::length2(_rot); if (glm::abs(lengthSquared - 1.0f) > EPSILON) { @@ -40,29 +39,22 @@ glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { return *this * rhs; } -// really slow, but accurate for transforms with non-uniform scale glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { - glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f); - glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale.z); - glm::mat3 mat(xAxis, yAxis, zAxis); - glm::mat3 transInvMat = glm::inverse(glm::transpose(mat)); - return transInvMat * rhs; -} - -// faster, but does not handle non-uniform scale correctly. -glm::vec3 AnimPose::xformVectorFast(const glm::vec3& rhs) const { return _rot * (_scale * rhs); } AnimPose AnimPose::operator*(const AnimPose& rhs) const { - glm::mat4 result; - glm_mat4u_mul(*this, rhs, result); - return AnimPose(result); + float scale = _scale * rhs._scale; + glm::quat rot = _rot * rhs._rot; + glm::vec3 trans = _trans + (_rot * (_scale * rhs._trans)); + return AnimPose(scale, rot, trans); } AnimPose AnimPose::inverse() const { - return AnimPose(glm::inverse(static_cast(*this))); + float invScale = 1.0f / _scale; + glm::quat invRot = glm::inverse(_rot); + glm::vec3 invTrans = invScale * (invRot * -_trans); + return AnimPose(invScale, invRot, invTrans); } // mirror about x-axis without applying negative scale. @@ -71,11 +63,10 @@ AnimPose AnimPose::mirror() const { } AnimPose::operator glm::mat4() const { - glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f); - glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale.z); - return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), - glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); + glm::vec3 xAxis = _rot * glm::vec3(_scale, 0.0f, 0.0f); + glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale, 0.0f); + glm::vec3 zAxis = _rot * glm::vec3(0.0f, 0.0f, _scale); + return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); } void AnimPose::blend(const AnimPose& srcPose, float alpha) { diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 1558a6b881..4d6dee1987 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -21,14 +21,13 @@ class AnimPose { public: AnimPose() {} explicit AnimPose(const glm::mat4& mat); - explicit AnimPose(const glm::quat& rotIn) : _scale(1.0f), _rot(rotIn), _trans(0.0f) {} - AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _scale(1.0f), _rot(rotIn), _trans(transIn) {} - AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _scale(scaleIn), _rot(rotIn), _trans(transIn) {} + explicit AnimPose(const glm::quat& rotIn) : _rot(rotIn), _trans(0.0f), _scale(1.0f) {} + AnimPose(const glm::quat& rotIn, const glm::vec3& transIn) : _rot(rotIn), _trans(transIn), _scale(1.0f) {} + AnimPose(float scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : _rot(rotIn), _trans(transIn), _scale(scaleIn) {} static const AnimPose identity; glm::vec3 xformPoint(const glm::vec3& rhs) const; glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow, but accurate for transforms with non-uniform scale - glm::vec3 xformVectorFast(const glm::vec3& rhs) const; // faster, but does not handle non-uniform scale correctly. glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint AnimPose operator*(const AnimPose& rhs) const; @@ -37,8 +36,8 @@ public: AnimPose mirror() const; operator glm::mat4() const; - const glm::vec3& scale() const { return _scale; } - glm::vec3& scale() { return _scale; } + float scale() const { return _scale; } + float& scale() { return _scale; } const glm::quat& rot() const { return _rot; } glm::quat& rot() { return _rot; } @@ -50,13 +49,13 @@ public: private: friend QDebug operator<<(QDebug debug, const AnimPose& pose); - glm::vec3 _scale { 1.0f }; glm::quat _rot; glm::vec3 _trans; + float _scale { 1.0f }; // uniform scale only. }; inline QDebug operator<<(QDebug debug, const AnimPose& pose) { - debug << "AnimPose, trans = (" << pose.trans().x << pose.trans().y << pose.trans().z << "), rot = (" << pose.rot().x << pose.rot().y << pose.rot().z << pose.rot().w << "), scale = (" << pose.scale().x << pose.scale().y << pose.scale().z << ")"; + debug << "AnimPose, trans = (" << pose.trans().x << pose.trans().y << pose.trans().z << "), rot = (" << pose.rot().x << pose.rot().y << pose.rot().z << pose.rot().w << "), scale =" << pose.scale(); return debug; } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index cc48308f17..03e3ac6ebd 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -76,7 +76,7 @@ int AnimSkeleton::getChainDepth(int jointIndex) const { int index = jointIndex; do { chainDepth++; - index = _joints[index].parentIndex; + index = _parentIndices[index]; } while (index != -1); return chainDepth; } else { @@ -102,17 +102,12 @@ const AnimPose& AnimSkeleton::getPostRotationPose(int jointIndex) const { return _relativePostRotationPoses[jointIndex]; } -int AnimSkeleton::getParentIndex(int jointIndex) const { - return _joints[jointIndex].parentIndex; -} - std::vector AnimSkeleton::getChildrenOfJoint(int jointIndex) const { // Children and grandchildren, etc. std::vector result; if (jointIndex != -1) { - for (int i = jointIndex + 1; i < (int)_joints.size(); i++) { - if (_joints[i].parentIndex == jointIndex - || (std::find(result.begin(), result.end(), _joints[i].parentIndex) != result.end())) { + for (int i = jointIndex + 1; i < (int)_parentIndices.size(); i++) { + if (_parentIndices[i] == jointIndex || (std::find(result.begin(), result.end(), _parentIndices[i]) != result.end())) { result.push_back(i); } } @@ -128,7 +123,7 @@ AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& relati if (jointIndex < 0 || jointIndex >= (int)relativePoses.size() || jointIndex >= _jointsSize) { return AnimPose::identity; } else { - return getAbsolutePose(_joints[jointIndex].parentIndex, relativePoses) * relativePoses[jointIndex]; + return getAbsolutePose(_parentIndices[jointIndex], relativePoses) * relativePoses[jointIndex]; } } @@ -136,7 +131,7 @@ void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const { // poses start off relative and leave in absolute frame int lastIndex = std::min((int)poses.size(), _jointsSize); for (int i = 0; i < lastIndex; ++i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { poses[i] = poses[parentIndex] * poses[i]; } @@ -147,7 +142,7 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { // poses start off absolute and leave in relative frame int lastIndex = std::min((int)poses.size(), _jointsSize); for (int i = lastIndex - 1; i >= 0; --i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { poses[i] = poses[parentIndex].inverse() * poses[i]; } @@ -158,7 +153,7 @@ void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& ro // poses start off absolute and leave in relative frame int lastIndex = std::min((int)rotations.size(), _jointsSize); for (int i = lastIndex - 1; i >= 0; --i) { - int parentIndex = _joints[i].parentIndex; + int parentIndex = _parentIndices[i]; if (parentIndex != -1) { rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i]; } @@ -197,6 +192,14 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets) { _joints = joints; + + // build a seperate vector of parentIndices for cache coherency + // AnimSkeleton::getParentIndex is called very frequently in tight loops. + _parentIndices.reserve(_joints.size()); + for (auto& joint : _joints) { + _parentIndices.push_back(joint.parentIndex); + } + _jointsSize = (int)joints.size(); // build a cache of bind poses @@ -289,8 +292,6 @@ void AnimSkeleton::dump(bool verbose) const { qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); if (verbose) { qCDebug(animation) << " hfmJoint ="; - qCDebug(animation) << " isFree =" << _joints[i].isFree; - qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; qCDebug(animation) << " translation =" << _joints[i].translation; qCDebug(animation) << " preTransform =" << _joints[i].preTransform; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 14f39eedbc..0eefbf973e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -43,7 +43,10 @@ public: // get post transform which might include FBX offset transformations const AnimPose& getPostRotationPose(int jointIndex) const; - int getParentIndex(int jointIndex) const; + int getParentIndex(int jointIndex) const { + return _parentIndices[jointIndex]; + } + std::vector getChildrenOfJoint(int jointIndex) const; AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const; @@ -69,6 +72,7 @@ protected: void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); std::vector _joints; + std::vector _parentIndices; int _jointsSize { 0 }; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0f921984b1..eb9ebd33dd 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -209,15 +209,14 @@ public: void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); } void unset(const QString& key) { _map.erase(key); } - void setTrigger(const QString& key) { _triggers.insert(key); } - void clearTriggers() { _triggers.clear(); } + void setTrigger(const QString& key) { _map[key] = AnimVariant(true); } void setRigToGeometryTransform(const glm::mat4& rigToGeometry) { _rigToGeometryMat = rigToGeometry; _rigToGeometryRot = glmExtractRotation(rigToGeometry); } - void clearMap() { _map.clear(); } + void clearMap() { _map.clear(); _triggers.clear(); } bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); } const AnimVariant& get(const QString& key) const { @@ -238,7 +237,7 @@ public: // For stat debugging. std::map toDebugMap() const; -#ifdef NDEBUG +#ifndef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; for (auto& pair : _map) { diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index f7a7dd861a..4e988334f9 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,12 +36,13 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer AnimationCache::createResource(const QUrl& url) { return QSharedPointer(new Animation(url), &Resource::deleter); } -Animation::Animation(const QUrl& url) : Resource(url) {} +QSharedPointer AnimationCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new Animation(*resource.staticCast().data()), &Resource::deleter); +} AnimationReader::AnimationReader(const QUrl& url, const QByteArray& data) : _url(url), diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 2f8168625e..41742c7485 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -34,9 +34,9 @@ public: Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); protected: + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; private: explicit AnimationCache(QObject* parent = NULL); virtual ~AnimationCache() { } @@ -50,6 +50,7 @@ Q_DECLARE_METATYPE(AnimationPointer) * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * @@ -62,7 +63,8 @@ class Animation : public Resource { public: - explicit Animation(const QUrl& url); + Animation(const Animation& other) : Resource(other), _hfmModel(other._hfmModel) {} + Animation(const QUrl& url) : Resource(url) {} QString getType() const override { return "Animation"; } diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.h b/libraries/animation/src/AnimationCacheScriptingInterface.h index 1f5735dd0f..06db5ef352 100644 --- a/libraries/animation/src/AnimationCacheScriptingInterface.h +++ b/libraries/animation/src/AnimationCacheScriptingInterface.h @@ -30,6 +30,7 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-assignment-client * * @property {number} numTotal - Total number of total resources. Read-only. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6c2851ec17..d25ea4669c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -39,14 +39,13 @@ static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; +static bool isEqual(const float p, float q) { + const float EPSILON = 0.00001f; + return fabsf(p - q) <= EPSILON; +} + static bool isEqual(const glm::vec3& u, const glm::vec3& v) { - const float EPSILON = 0.0001f; - float uLen = glm::length(u); - if (uLen == 0.0f) { - return glm::length(v) <= EPSILON; - } else { - return (glm::length(u - v) / uLen) <= EPSILON; - } + return isEqual(u.x, v.x) && isEqual(u.y, v.y) && isEqual(u.z, v.z); } static bool isEqual(const glm::quat& p, const glm::quat& q) { @@ -508,10 +507,8 @@ std::shared_ptr Rig::getAnimInverseKinematicsNode() const std::shared_ptr result; if (_animNode) { _animNode->traverse([&](AnimNode::Pointer node) { - // only report clip nodes as valid roles. - auto ikNode = std::dynamic_pointer_cast(node); - if (ikNode) { - result = ikNode; + if (node->getType() == AnimNodeType::InverseKinematics) { + result = std::dynamic_pointer_cast(node); return false; } else { return true; @@ -1234,9 +1231,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _lastAnimVars = _animVars; - _animVars.clearTriggers(); _animVars = triggersOut; - _networkVars.clearTriggers(); _networkVars = networkTriggersOut; _lastContext = context; } @@ -1360,7 +1355,7 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) { // we are within the k-dop so push the point along the minimum displacement found - displacementOut = shapePose.xformVectorFast(minDisplacement); + displacementOut = shapePose.xformVector(minDisplacement); return true; } else { // point is outside of kdop @@ -1369,7 +1364,7 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } else { // point is directly on top of shapeInfo.avgPoint. // push the point out along the x axis. - displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]); + displacementOut = shapePose.xformVector(shapeInfo.points[0]); return true; } } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b2cba2351f..60a95ff58a 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -170,6 +170,57 @@ static void channelDownmix(int16_t* source, int16_t* dest, int numSamples) { } } +static float computeLoudness(int16_t* samples, int numSamples, int numChannels, bool& isClipping) { + + const int32_t CLIPPING_THRESHOLD = 32392; // -0.1 dBFS + const int32_t CLIPPING_DETECTION = 3; // consecutive samples over threshold + + float scale = numSamples ? 1.0f / (numSamples * 32768.0f) : 0.0f; + + int32_t loudness = 0; + isClipping = false; + + if (numChannels == 2) { + int32_t oversLeft = 0; + int32_t oversRight = 0; + + for (int i = 0; i < numSamples/2; i++) { + int32_t left = std::abs((int32_t)samples[2*i+0]); + int32_t right = std::abs((int32_t)samples[2*i+1]); + + loudness += left; + loudness += right; + + if (left > CLIPPING_THRESHOLD) { + isClipping |= (++oversLeft >= CLIPPING_DETECTION); + } else { + oversLeft = 0; + } + if (right > CLIPPING_THRESHOLD) { + isClipping |= (++oversRight >= CLIPPING_DETECTION); + } else { + oversRight = 0; + } + } + } else { + int32_t overs = 0; + + for (int i = 0; i < numSamples; i++) { + int32_t sample = std::abs((int32_t)samples[i]); + + loudness += sample; + + if (sample > CLIPPING_THRESHOLD) { + isClipping |= (++overs >= CLIPPING_DETECTION); + } else { + overs = 0; + } + } + } + + return (float)loudness * scale; +} + static inline float convertToFloat(int16_t sample) { return (float)sample * (1 / 32768.0f); } @@ -1075,45 +1126,25 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { void AudioClient::handleAudioInput(QByteArray& audioBuffer) { if (!_audioPaused) { - if (_muted) { - _lastInputLoudness = 0.0f; - _timeSinceLastClip = 0.0f; - } else { + + bool audioGateOpen = false; + + if (!_muted) { int16_t* samples = reinterpret_cast(audioBuffer.data()); int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE; int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO); if (_isNoiseGateEnabled) { // The audio gate includes DC removal - _audioGate->render(samples, samples, numFrames); + audioGateOpen = _audioGate->render(samples, samples, numFrames); } else { - _audioGate->removeDC(samples, samples, numFrames); - } - - int32_t loudness = 0; - assert(numSamples < 65536); // int32_t loudness cannot overflow - bool didClip = false; - for (int i = 0; i < numSamples; ++i) { - const int32_t CLIPPING_THRESHOLD = (int32_t)(AudioConstants::MAX_SAMPLE_VALUE * 0.9f); - int32_t sample = std::abs((int32_t)samples[i]); - loudness += sample; - didClip |= (sample > CLIPPING_THRESHOLD); - } - _lastInputLoudness = (float)loudness / numSamples; - - if (didClip) { - _timeSinceLastClip = 0.0f; - } else if (_timeSinceLastClip >= 0.0f) { - _timeSinceLastClip += (float)numSamples / (float)AudioConstants::SAMPLE_RATE; + audioGateOpen = _audioGate->removeDC(samples, samples, numFrames); } emit inputReceived(audioBuffer); } - emit inputLoudnessChanged(_lastInputLoudness); - - // state machine to detect gate opening and closing - bool audioGateOpen = (_lastInputLoudness != 0.0f); + // detect gate opening and closing bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed _audioGateOpen = audioGateOpen; @@ -1186,10 +1217,27 @@ void AudioClient::handleMicAudioInput() { static int16_t networkAudioSamples[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; while (_inputRingBuffer.samplesAvailable() >= inputSamplesRequired) { - if (_muted) { - _inputRingBuffer.shiftReadPosition(inputSamplesRequired); - } else { - _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + _inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired); + + // detect loudness and clipping on the raw input + bool isClipping = false; + float inputLoudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping); + + float tc = (inputLoudness > _lastInputLoudness) ? 0.378f : 0.967f; // 10ms attack, 300ms release @ 100Hz + inputLoudness += tc * (_lastInputLoudness - inputLoudness); + _lastInputLoudness = inputLoudness; + + if (isClipping) { + _timeSinceLastClip = 0.0f; + } else if (_timeSinceLastClip >= 0.0f) { + _timeSinceLastClip += AudioConstants::NETWORK_FRAME_SECS; + } + isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time + + emit inputLoudnessChanged(_lastInputLoudness, isClipping); + + if (!_muted) { possibleResampling(_inputToNetworkResampler, inputAudioSamples.get(), networkAudioSamples, inputSamplesRequired, numNetworkSamples, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index f3e1ad9a52..94ed2ce132 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -248,7 +248,7 @@ signals: void noiseReductionChanged(bool noiseReductionEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); - void inputLoudnessChanged(float loudness); + void inputLoudnessChanged(float loudness, bool isClipping); void outputBytesToNetwork(int numBytes); void inputBytesFromNetwork(int numBytes); void noiseGateOpened(); diff --git a/libraries/audio-client/src/AudioIOStats.h b/libraries/audio-client/src/AudioIOStats.h index 45fcf365da..ffd7163586 100644 --- a/libraries/audio-client/src/AudioIOStats.h +++ b/libraries/audio-client/src/AudioIOStats.h @@ -44,6 +44,7 @@ class AudioStreamStatsInterface : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} lossRate Read-only. * @property {number} lossCount Read-only. @@ -192,6 +193,7 @@ class AudioStatsInterface : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} pingMs Read-only. * @property {number} inputReadMsMax Read-only. diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 4bc7957142..e090832510 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -25,6 +25,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index e9cdf832d2..0df46ac532 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -138,8 +138,8 @@ public: int32_t hysteresis(int32_t peak); int32_t envelope(int32_t attn); - virtual void process(int16_t* input, int16_t* output, int numFrames) = 0; - virtual void removeDC(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool process(int16_t* input, int16_t* output, int numFrames) = 0; + virtual bool removeDC(int16_t* input, int16_t* output, int numFrames) = 0; }; GateImpl::GateImpl(int sampleRate) { @@ -403,14 +403,15 @@ public: GateMono(int sampleRate) : GateImpl(sampleRate) {} // mono input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateMono::process(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -453,15 +454,21 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { x = MULQ31(x, attn); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -471,8 +478,13 @@ void GateMono::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x); // store 16-bit output - output[n] = (int16_t)saturateQ30(x); + x = saturateQ30(x); + output[n] = (int16_t)x; + + mask |= x; } + + return mask != 0; } // @@ -489,14 +501,15 @@ public: GateStereo(int sampleRate) : GateImpl(sampleRate) {} // interleaved stereo input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -541,16 +554,23 @@ void GateStereo::process(int16_t* input, int16_t* output, int numFrames) { x1 = MULQ31(x1, attn); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -561,9 +581,15 @@ void GateStereo::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1); // store 16-bit output - output[2*n+0] = (int16_t)saturateQ30(x0); - output[2*n+1] = (int16_t)saturateQ30(x1); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + output[2*n+0] = (int16_t)x0; + output[2*n+1] = (int16_t)x1; + + mask |= (x0 | x1); } + + return mask != 0; } // @@ -580,14 +606,15 @@ public: GateQuad(int sampleRate) : GateImpl(sampleRate) {} // interleaved quad input/output (in-place is allowed) - void process(int16_t* input, int16_t* output, int numFrames) override; - void removeDC(int16_t* input, int16_t* output, int numFrames) override; + bool process(int16_t* input, int16_t* output, int numFrames) override; + bool removeDC(int16_t* input, int16_t* output, int numFrames) override; }; template -void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::process(int16_t* input, int16_t* output, int numFrames) { clearHistogram(); + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -636,18 +663,27 @@ void GateQuad::process(int16_t* input, int16_t* output, int numFrames) { x3 = MULQ31(x3, attn); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } // update adaptive threshold processHistogram(numFrames); + return mask != 0; } template -void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { +bool GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { + + int32_t mask = 0; for (int n = 0; n < numFrames; n++) { @@ -660,11 +696,19 @@ void GateQuad::removeDC(int16_t* input, int16_t* output, int numFrames) { _dc.process(x0, x1, x2, x3); // store 16-bit output - output[4*n+0] = (int16_t)saturateQ30(x0); - output[4*n+1] = (int16_t)saturateQ30(x1); - output[4*n+2] = (int16_t)saturateQ30(x2); - output[4*n+3] = (int16_t)saturateQ30(x3); + x0 = saturateQ30(x0); + x1 = saturateQ30(x1); + x2 = saturateQ30(x2); + x3 = saturateQ30(x3); + output[4*n+0] = (int16_t)x0; + output[4*n+1] = (int16_t)x1; + output[4*n+2] = (int16_t)x2; + output[4*n+3] = (int16_t)x3; + + mask |= (x0 | x1 | x2 | x3); } + + return mask != 0; } // @@ -721,12 +765,12 @@ AudioGate::~AudioGate() { delete _impl; } -void AudioGate::render(int16_t* input, int16_t* output, int numFrames) { - _impl->process(input, output, numFrames); +bool AudioGate::render(int16_t* input, int16_t* output, int numFrames) { + return _impl->process(input, output, numFrames); } -void AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { - _impl->removeDC(input, output, numFrames); +bool AudioGate::removeDC(int16_t* input, int16_t* output, int numFrames) { + return _impl->removeDC(input, output, numFrames); } void AudioGate::setThreshold(float threshold) { diff --git a/libraries/audio/src/AudioGate.h b/libraries/audio/src/AudioGate.h index d4ae3c5fe8..6fc7ca83df 100644 --- a/libraries/audio/src/AudioGate.h +++ b/libraries/audio/src/AudioGate.h @@ -18,9 +18,12 @@ public: AudioGate(int sampleRate, int numChannels); ~AudioGate(); - // interleaved int16_t input/output (in-place is allowed) - void render(int16_t* input, int16_t* output, int numFrames); - void removeDC(int16_t* input, int16_t* output, int numFrames); + // + // Process interleaved int16_t input/output (in-place is allowed). + // Returns true when output is non-zero. + // + bool render(int16_t* input, int16_t* output, int numFrames); + bool removeDC(int16_t* input, int16_t* output, int numFrames); void setThreshold(float threshold); void setRelease(float release); diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 836e28d582..f5f47add32 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -61,7 +61,8 @@ class Sound : public Resource { public: Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false); - + Sound(const Sound& other) : Resource(other), _audioData(other._audioData), _numChannels(other._numChannels) {} + bool isReady() const { return (bool)_audioData; } bool isStereo() const { return _audioData ? _audioData->isStereo() : false; } @@ -132,6 +133,7 @@ typedef QSharedPointer SharedSoundPointer; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 845fd6ab4f..343de46e9a 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -33,9 +33,12 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer SoundCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer SoundCache::createResource(const QUrl& url) { auto resource = QSharedPointer(new Sound(url), &Resource::deleter); resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY); return resource; } + +QSharedPointer SoundCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new Sound(*resource.staticCast().data()), &Resource::deleter); +} \ No newline at end of file diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index 64d392a41d..48c3354877 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -24,8 +24,9 @@ public: Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + private: SoundCache(QObject* parent = NULL); }; diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index c985e8c211..9caa7a8066 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -30,6 +30,7 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index ca5a61b3d6..9882fd6371 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -305,11 +305,6 @@ void Avatar::setTargetScale(float targetScale) { } } -void Avatar::setAvatarEntityDataChanged(bool value) { - AvatarData::setAvatarEntityDataChanged(value); - _avatarEntityDataHashes.clear(); -} - void Avatar::removeAvatarEntitiesFromTree() { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; @@ -368,6 +363,13 @@ bool Avatar::applyGrabChanges() { target->removeGrab(grab); _avatarGrabs.erase(itr); grabAddedOrRemoved = true; + if (isMyAvatar()) { + const EntityItemPointer& entity = std::dynamic_pointer_cast(target); + if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) { + EntityItemProperties properties = entity->getProperties(); + sendPacket(entity->getID(), properties); + } + } } else { undeleted.push_back(id); } @@ -412,6 +414,9 @@ void Avatar::accumulateGrabPositions(std::map& g if (!grab || !grab->getActionID().isNull()) { continue; // the accumulated value isn't used, in this case. } + if (grab->getReleased()) { + continue; + } glm::vec3 jointTranslation = getAbsoluteJointTranslationInObjectFrame(grab->getParentJointIndex()); glm::quat jointRotation = getAbsoluteJointRotationInObjectFrame(grab->getParentJointIndex()); @@ -1735,15 +1740,17 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) { if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); - std::vector positions; - std::vector radiuses; - positions.reserve(data.size()); - radiuses.reserve(data.size()); - for (auto& sphere : data) { - positions.push_back(sphere._position); - radiuses.push_back(sphere._radius); + if (data.size() > 0) { + std::vector positions; + std::vector radiuses; + positions.reserve(data.size()); + radiuses.reserve(data.size()); + for (auto& sphere : data) { + positions.push_back(sphere._position); + radiuses.push_back(sphere._radius); + } + shapeInfo.setMultiSphere(positions, radiuses); } - shapeInfo.setMultiSphere(positions, radiuses); } } @@ -2007,12 +2014,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { auto& rig = _skeletonModel->getRig(); // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. - AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose modelOffsetWithoutAvatarScale(1.0f, rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + float scaleFactor = geomToRigWithoutAvatarScale.scale(); int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 9252b40fcf..11bf2986d1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -483,8 +484,6 @@ public: virtual void setModelScale(float scale) { _modelScale = scale; } virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); } - virtual void setAvatarEntityDataChanged(bool value) override; - // Show hide the model representation of the avatar virtual void setEnableMeshVisible(bool isEnabled); virtual bool getEnableMeshVisible() const; @@ -608,6 +607,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + virtual void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { } bool applyGrabChanges(); void relayJointDataToChildren(); @@ -652,9 +652,6 @@ protected: bool success { false }; }; - using MapOfAvatarEntityDataHashes = QMap; - MapOfAvatarEntityDataHashes _avatarEntityDataHashes; - uint64_t _lastRenderUpdateTime { 0 }; int _leftPointerGeometryID { 0 }; int _rightPointerGeometryID { 0 }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 7f2dbda3de..ea71ff128c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -249,14 +249,6 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getRightHandJointIndex(), position); } -bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { - return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position); -} - -bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { - return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position); -} - bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Head"), headPosition); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index ef0e1e0fae..99f6632306 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -57,17 +57,9 @@ public: /// \return true whether or not the position was found bool getRightHandPosition(glm::vec3& position) const; - /// Gets the position of the left shoulder. - /// \return whether or not the left shoulder joint was found - bool getLeftShoulderPosition(glm::vec3& position) const; - /// Returns the extended length from the left hand to its last free ancestor. float getLeftArmLength() const; - /// Gets the position of the right shoulder. - /// \return whether or not the right shoulder joint was found - bool getRightShoulderPosition(glm::vec3& position) const; - /// Returns the position of the head joint. /// \return whether or not the head was found bool getHeadPosition(glm::vec3& headPosition) const; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d071b74d6e..63396a59ac 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1147,7 +1147,7 @@ public: */ Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); - virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } + void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } AvatarEntityIDs getAndClearRecentlyRemovedIDs(); /**jsdoc diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 2516323c37..6407ce1846 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -154,7 +154,7 @@ void TextureBaker::processTexture() { gpu::BackendTarget::GLES32 }}; for (auto target : BACKEND_TARGETS) { - auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), + auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), image::ColorChannel::NONE, ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, target, _abortProcessing); if (!processedTexture) { @@ -197,7 +197,7 @@ void TextureBaker::processTexture() { // Uncompressed KTX if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { buffer->reset(); - auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), + auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), image::ColorChannel::NONE, ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing); if (!processedTexture) { handleError("Could not process texture " + _textureURL.toString()); diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 5bc7357dd7..845e19f6c3 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -57,6 +57,7 @@ class UserInputMapper; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ /**jsdoc diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 804709ebfa..eb610af78a 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -39,6 +39,7 @@ class ScriptingInterface; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ // TODO migrate functionality to a RouteBuilder class and make the proxy defer to that diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index e25d30109f..9c7d01082c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -178,6 +178,7 @@ private: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {boolean} allowMouseCapture * @property {number} depth diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index da226d146b..69590aae82 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -20,6 +20,7 @@ const QString& DisplayPlugin::MENU_PATH() { return value; } +#if !defined(CUSTOM_DISPLAY_PLUGINS) // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class DisplayPluginList getDisplayPlugins() { DisplayPlugin* PLUGIN_POOL[] = { @@ -49,3 +50,4 @@ DisplayPluginList getDisplayPlugins() { } return result; } +#endif \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6c59dbd861..e4deaf8f4b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -720,6 +720,8 @@ void OpenGLDisplayPlugin::present() { } gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); + } else { + internalPresent(); } _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index 9eafe381ab..b4527ff90c 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -51,15 +51,15 @@ bool DebugHmdDisplayPlugin::internalActivate() { }, true, _isAutoRotateEnabled); _ipd = 0.0327499993f * 2.0f; - // Would be nice to know why the left and right projection matrices are slightly dissymetrical - _eyeProjections[0][0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; - _eyeProjections[0][1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; - _eyeProjections[0][2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; - _eyeProjections[0][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; - _eyeProjections[1][0] = vec4{ 0.752847493, 0.000000000, 0.000000000, 0.000000000 }; - _eyeProjections[1][1] = vec4{ 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; - _eyeProjections[1][2] = vec4{ 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; - _eyeProjections[1][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + // Quest + _eyeProjections[0][0] = vec4{ 0.91729, 0.0, -0.17407, 0.0 }; + _eyeProjections[0][1] = vec4{ 0.0, 0.083354, -0.106141, 0.0 }; + _eyeProjections[0][2] = vec4{ 0.0, 0.0, -1.0, -0.2 }; + _eyeProjections[0][3] = vec4{ 0.0, 0.0, -1.0, 0.0 }; + _eyeProjections[1][0] = vec4{ 0.91729, 0.0, 0.17407, 0.0 }; + _eyeProjections[1][1] = vec4{ 0.0, 0.083354, -0.106141, 0.0 }; + _eyeProjections[1][2] = vec4{ 0.0, 0.0, -1.0, -0.2 }; + _eyeProjections[1][3] = vec4{ 0.0, 0.0, -1.0, 0.0 }; // No need to do so here as this will done in Parent::internalActivate //_eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); //_eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 4aeacbe05c..d8c0ce8e1d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -81,7 +81,6 @@ protected: mat4 presentPose; double sensorSampleTime { 0 }; double predictedDisplayTime { 0 }; - mat3 presentReprojection; }; QMap _frameInfos; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c71b296a74..319acc750f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -42,7 +42,6 @@ #include std::function EntityTreeRenderer::_entitiesShouldFadeFunction = []() { return true; }; -std::function EntityTreeRenderer::_getAvatarUpOperator = []() { return Vectors::UP; }; QString resolveScriptURL(const QString& scriptUrl) { auto normalizedScriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); @@ -84,38 +83,38 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity); // Forward mouse events to web entities - auto handlePointerEvent = [&](const EntityItemID& entityID, const PointerEvent& event) { + auto handlePointerEvent = [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->getType() == EntityTypes::Web) { thisEntity = std::static_pointer_cast(renderableForEntityId(entityID)); } if (thisEntity) { - QMetaObject::invokeMethod(thisEntity.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); + QMetaObject::invokeMethod(thisEntity.get(), "handlePointerEvent", Q_ARG(const PointerEvent&, event)); } }; connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const EntityItemID& entityID, const PointerEvent& event) { + connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->getType() == EntityTypes::Web) { thisEntity = std::static_pointer_cast(renderableForEntityId(entityID)); } if (thisEntity) { - QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(PointerEvent, event)); + QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(const PointerEvent&, event)); } }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const EntityItemID& entityID, const PointerEvent& event) { + connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->getType() == EntityTypes::Web) { thisEntity = std::static_pointer_cast(renderableForEntityId(entityID)); } if (thisEntity) { - QMetaObject::invokeMethod(thisEntity.get(), "hoverLeaveEntity", Q_ARG(PointerEvent, event)); + QMetaObject::invokeMethod(thisEntity.get(), "hoverLeaveEntity", Q_ARG(const PointerEvent&, event)); } }); } @@ -798,11 +797,11 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { +std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. if (!_tree || _shuttingDown) { - return; + return { FLT_MAX, UNKNOWN_ENTITY_ID }; } PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); @@ -833,9 +832,10 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { _lastPointerEvent = pointerEvent; _lastPointerEventValid = true; - } else { - emit entityScriptingInterface->mousePressOffEntity(); + return { rayPickResult.distance, rayPickResult.entityID }; } + emit entityScriptingInterface->mousePressOffEntity(); + return { FLT_MAX, UNKNOWN_ENTITY_ID }; } void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { @@ -1048,7 +1048,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool QString scriptUrl = entity->getScript(); if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) { if (_entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID); } entity->scriptHasUnloaded(); } @@ -1289,6 +1289,17 @@ CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriori return 0.0f; }; +std::pair EntityTreeRenderer::getZoneInteractionProperties() { + for (auto& zone : _layeredZones) { + // Only domain entities control flying allowed and ghosting allowed + if (zone.zone && zone.zone->isDomainEntity()) { + return { zone.zone->getFlyingAllowed(), zone.zone->getGhostingAllowed() }; + } + } + + return { true, true }; +} + bool EntityTreeRenderer::wantsKeyboardFocus(const EntityItemID& id) const { auto renderable = renderableForEntityId(id); if (!renderable) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index d9f594a20b..204dc50c45 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -93,8 +93,8 @@ public: void reloadEntityScripts(); // event handles which may generate entity related events + std::pair mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); - void mousePressEvent(QMouseEvent* event); void mouseDoublePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); @@ -105,7 +105,7 @@ public: // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } - std::shared_ptr myAvatarZone() { return _layeredZones.getZone(); } + std::pair getZoneInteractionProperties(); bool wantsKeyboardFocus(const EntityItemID& id) const; QObject* getEventHandler(const EntityItemID& id); @@ -118,9 +118,6 @@ public: // Access the workload Space workload::SpacePointer getWorkloadSpace() const { return _space; } - static void setGetAvatarUpOperator(std::function getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; } - static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); } - EntityEditPacketSender* getPacketSender(); signals: @@ -258,7 +255,6 @@ private: workload::SpacePointer _space{ new workload::Space() }; workload::Transaction::Updates _spaceUpdates; - static std::function _getAvatarUpOperator; }; diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index c565eb1f0a..96dd1733e7 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -96,12 +96,12 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _emissive = entity->getEmissive(); _keepAspectRatio = entity->getKeepAspectRatio(); - _billboardMode = entity->getBillboardMode(); _subImage = entity->getSubImage(); _color = entity->getColor(); _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); + _billboardMode = entity->getBillboardMode(); if (!_textureIsLoaded && _texture && _texture->isLoaded()) { _textureIsLoaded = true; @@ -118,6 +118,17 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce }); } +Item::Bound ImageEntityRenderer::getBound() { + auto bound = Parent::getBound(); + if (_billboardMode != BillboardMode::NONE) { + glm::vec3 dimensions = bound.getScale(); + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); + } + return bound; +} + ShapeKey ImageEntityRenderer::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); if (isTransparent()) { @@ -159,27 +170,7 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch* batch = args->_batch; - if (_billboardMode == BillboardMode::YAW) { - //rotate about vertical to face the camera - glm::vec3 dPosition = args->getViewFrustum().getPosition() - transform.getTranslation(); - // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees - float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); - glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); - transform.setRotation(orientation); - } else if (_billboardMode == BillboardMode::FULL) { - glm::vec3 billboardPos = transform.getTranslation(); - glm::vec3 cameraPos = args->getViewFrustum().getPosition(); - // use the referencial from the avatar, y isn't always up - glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); - // check to see if glm::lookAt will work / using glm::lookAt variable name - glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); - - // make sure s is not NaN for any component - if (glm::length2(s) > 0.0f) { - glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); - transform.setRotation(rotation); - } - } + transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode)); transform.postScale(dimensions); batch->setModelTransform(transform); diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h index a20533cc8c..d60b38fe65 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.h +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -23,6 +23,7 @@ public: ~ImageEntityRenderer(); protected: + Item::Bound getBound() override; ShapeKey getShapeKey() override; bool isTransparent() const override; @@ -39,12 +40,12 @@ private: bool _emissive; bool _keepAspectRatio; - BillboardMode _billboardMode; QRect _subImage; glm::u8vec3 _color; float _alpha; PulsePropertyGroup _pulseProperties; + BillboardMode _billboardMode; glm::vec3 _dimensions; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7e01af04dd..ae9fdf572a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -280,11 +280,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3 } void RenderableModelEntityItem::fetchCollisionGeometryResource() { - QUrl hullURL(getCollisionShapeURL()); - QUrlQuery queryArgs(hullURL); - queryArgs.addQueryItem("collision-hull", ""); - hullURL.setQuery(queryArgs); - _compoundShapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + _compoundShapeResource = DependencyManager::get()->getCollisionGeometryResource(getCollisionShapeURL()); } bool RenderableModelEntityItem::computeShapeFailedToLoad() { @@ -959,23 +955,6 @@ QStringList RenderableModelEntityItem::getJointNames() const { return result; } -void RenderableModelEntityItem::setAnimationURL(const QString& url) { - QString oldURL = getAnimationURL(); - ModelEntityItem::setAnimationURL(url); - if (oldURL != getAnimationURL()) { - _needsAnimationReset = true; - } -} - -bool RenderableModelEntityItem::needsAnimationReset() const { - return _needsAnimationReset; -} - -QString RenderableModelEntityItem::getAnimationURLAndReset() { - _needsAnimationReset = false; - return getAnimationURL(); -} - scriptable::ScriptableModelBase render::entities::ModelEntityRenderer::getScriptableModel() { auto model = resultWithReadLock([this]{ return _model; }); @@ -1264,7 +1243,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return false; } - if (_lastTextures != entity->getTextures()) { + if (_textures != entity->getTextures()) { return true; } @@ -1418,15 +1397,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce entity->_originalTexturesRead = true; } - if (_lastTextures != entity->getTextures()) { + if (_textures != entity->getTextures()) { + QVariantMap newTextures; withWriteLock([&] { _texturesLoaded = false; - _lastTextures = entity->getTextures(); + _textures = entity->getTextures(); + newTextures = parseTexturesToMap(_textures, entity->_originalTextures); }); - auto newTextures = parseTexturesToMap(_lastTextures, entity->_originalTextures); - if (newTextures != model->getTextures()) { - model->setTextures(newTextures); - } + model->setTextures(newTextures); } if (entity->_needsJointSimulation) { entity->copyAnimationJointDataToModel(); @@ -1494,11 +1472,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); - if (_animation && entity->needsAnimationReset()) { - //(_animation->getURL().toString() != entity->getAnimationURL())) { // bad check - // the joints have been mapped before but we have a new animation to load - _animation.reset(); - _jointMappingCompleted = false; + auto animationURL = entity->getAnimationURL(); + bool animationChanged = _animationURL != animationURL; + if (animationChanged) { + _animationURL = animationURL; + + if (_animation) { + //(_animation->getURL().toString() != entity->getAnimationURL())) { // bad check + // the joints have been mapped before but we have a new animation to load + _animation.reset(); + _jointMappingCompleted = false; + } } if (!_jointMappingCompleted) { @@ -1563,7 +1547,7 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const Mode } if (!_animation) { - _animation = DependencyManager::get()->getAnimation(entity->getAnimationURLAndReset()); + _animation = DependencyManager::get()->getAnimation(_animationURL); } if (_animation && _animation->isLoaded()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 16c3664f28..9adff9ca01 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -113,10 +113,6 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; - void setAnimationURL(const QString& url) override; - bool needsAnimationReset() const; - QString getAnimationURLAndReset(); - private: bool needsUpdateModelBounds() const; void autoResizeJointArrays(); @@ -131,7 +127,6 @@ private: bool _originalTexturesRead { false }; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; - bool _needsAnimationReset { false }; }; namespace render { namespace entities { @@ -181,7 +176,7 @@ private: bool _hasModel { false }; ModelPointer _model; - QString _lastTextures; + QString _textures; bool _texturesLoaded { false }; int _lastKnownCurrentFrame { -1 }; #ifdef MODEL_ENTITY_USE_FADE_EFFECT @@ -190,12 +185,12 @@ private: const void* _collisionMeshKey { nullptr }; - // used on client side + QUrl _parsedModelURL; bool _jointMappingCompleted { false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints AnimationPointer _animation; - QUrl _parsedModelURL; bool _animating { false }; + QString _animationURL; uint64_t _lastAnimated { 0 }; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 4ede5e5057..c139fbf320 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -104,8 +104,8 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi }); _emitting = entity->getIsEmitting(); - bool hasTexture = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); - if (hasTexture) { + bool textureEmpty = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); + if (textureEmpty) { if (_networkTexture) { withWriteLock([&] { _networkTexture.reset(); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 68371e4e13..64c05b576b 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -25,6 +25,7 @@ using namespace render; using namespace render::entities; gpu::PipelinePointer PolyLineEntityRenderer::_pipeline = nullptr; +gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); @@ -44,14 +45,26 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) void PolyLineEntityRenderer::buildPipeline() { // FIXME: opaque pipeline gpu::ShaderPointer program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setCullMode(gpu::State::CullMode::CULL_NONE); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - PrepareStencil::testMask(*state); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::Pipeline::create(program, state); + { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CullMode::CULL_NONE); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::Pipeline::create(program, state); + } + { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CullMode::CULL_NONE); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _glowPipeline = gpu::Pipeline::create(program, state); + } } ItemKey PolyLineEntityRenderer::getKey() { @@ -128,7 +141,8 @@ void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo } // Data - if (faceCamera != _faceCamera || glow != _glow) { + bool faceCameraChanged = faceCamera != _faceCamera; + if (faceCameraChanged || glow != _glow) { _faceCamera = faceCamera; _glow = glow; updateData(); @@ -148,7 +162,7 @@ void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo _colors = entity->getStrokeColors(); _color = toGlm(entity->getColor()); } - if (_isUVModeStretch != isUVModeStretch || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged) { + if (_isUVModeStretch != isUVModeStretch || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged || faceCameraChanged) { _isUVModeStretch = isUVModeStretch; updateGeometry(); } @@ -220,11 +234,17 @@ void PolyLineEntityRenderer::updateGeometry() { // For last point we can assume binormals are the same since it represents the last two vertices of quad if (i < maxNumVertices - 1) { glm::vec3 tangent = _points[i + 1] - point; - binormal = glm::normalize(glm::cross(tangent, normal)); - // Check to make sure binormal is not a NAN. If it is, don't add to vertices vector - if (binormal.x != binormal.x) { - continue; + if (_faceCamera) { + // In faceCamera mode, we actually pass the tangent, and recompute the binormal in the shader + binormal = tangent; + } else { + binormal = glm::normalize(glm::cross(tangent, normal)); + + // Check to make sure binormal is not a NAN. If it is, don't add to vertices vector + if (glm::any(glm::isnan(binormal))) { + continue; + } } } @@ -250,11 +270,11 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - if (!_pipeline) { + if (!_pipeline || !_glowPipeline) { buildPipeline(); } - batch.setPipeline(_pipeline); + batch.setPipeline(_glow ? _glowPipeline : _pipeline); batch.setModelTransform(_renderTransform); batch.setResourceTexture(0, _textureLoaded ? _texture->getGPUTexture() : DependencyManager::get()->getWhiteTexture()); batch.setResourceBuffer(0, _polylineGeometryBuffer); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index fd37a49598..b8a3ad3b3e 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -59,6 +59,7 @@ protected: gpu::BufferPointer _polylineDataBuffer; gpu::BufferPointer _polylineGeometryBuffer; static gpu::PipelinePointer _pipeline; + static gpu::PipelinePointer _glowPipeline; }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 5614e976b5..99912e9d91 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -23,6 +23,9 @@ using namespace render; using namespace render::entities; static const int FIXED_FONT_POINT_SIZE = 40; +const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 92.0f; // Determined through experimentation to fit font to line + // height. +const float LINE_SCALE_RATIO = 1.2f; TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) : Parent(entity), @@ -44,6 +47,17 @@ bool TextEntityRenderer::isTransparent() const { return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; } +Item::Bound TextEntityRenderer::getBound() { + auto bound = Parent::getBound(); + if (_billboardMode != BillboardMode::NONE) { + glm::vec3 dimensions = bound.getScale(); + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); + } + return bound; +} + ShapeKey TextEntityRenderer::getShapeKey() { auto builder = render::ShapeKey::Builder().withOwnPipeline(); if (isTransparent()) { @@ -167,41 +181,34 @@ void TextEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; auto transformToTopLeft = modelTransform; - if (_billboardMode == BillboardMode::YAW) { - //rotate about vertical to face the camera - glm::vec3 dPosition = args->getViewFrustum().getPosition() - modelTransform.getTranslation(); - // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees - float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); - glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); - transformToTopLeft.setRotation(orientation); - } else if (_billboardMode == BillboardMode::FULL) { - glm::vec3 billboardPos = transformToTopLeft.getTranslation(); - glm::vec3 cameraPos = args->getViewFrustum().getPosition(); - // use the referencial from the avatar, y isn't always up - glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); - // check to see if glm::lookAt will work / using glm::lookAt variable name - glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); - - // make sure s is not NaN for any component - if (glm::length2(s) > 0.0f) { - glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); - transformToTopLeft.setRotation(rotation); - } - } + transformToTopLeft.setRotation(EntityItem::getBillboardRotation(transformToTopLeft.getTranslation(), transformToTopLeft.getRotation(), _billboardMode)); transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed - batch.setModelTransform(transformToTopLeft); - auto geometryCache = DependencyManager::get(); - geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); - geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); + if (backgroundColor.a > 0.0f) { + batch.setModelTransform(transformToTopLeft); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); + geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); + } - // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline for a gpu performance increase. - float scale = _lineHeight / _textRenderer->getFontSize(); - transformToTopLeft.setScale(scale); // Scale to have the correct line height - batch.setModelTransform(transformToTopLeft); + if (textColor.a > 0.0f) { + // FIXME: Factor out textRenderer so that text parts can be grouped by pipeline for a gpu performance increase. + float scale = _lineHeight / _textRenderer->getFontSize(); + transformToTopLeft.setScale(scale); // Scale to have the correct line height + batch.setModelTransform(transformToTopLeft); - glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), - dimensions.y - (_topMargin + _bottomMargin)); - _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); + glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin)); + _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); + } } + +QSizeF TextEntityRenderer::textSize(const QString& text) const { + auto extents = _textRenderer->computeExtent(text); + extents.y *= 2.0f; + + float maxHeight = (float)_textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO; + float pointToWorldScale = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight; + + return QSizeF(extents.x, extents.y) * pointToWorldScale; +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 6c9e8324f8..e0306375a0 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -26,7 +26,11 @@ public: TextEntityRenderer(const EntityItemPointer& entity); ~TextEntityRenderer(); + QSizeF textSize(const QString& text) const; + +protected: bool isTransparent() const override; + Item::Bound getBound() override; ShapeKey getShapeKey() override; private: diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 2510e41780..bf7820fecd 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -115,35 +115,45 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } } - if (_color != entity->getColor()) { - return true; - } + if(resultWithReadLock([&] { + if (_color != entity->getColor()) { + return true; + } - if (_alpha != entity->getAlpha()) { - return true; - } + if (_alpha != entity->getAlpha()) { + return true; + } - if (_sourceURL != entity->getSourceUrl()) { - return true; - } + if (_billboardMode != entity->getBillboardMode()) { + return true; + } - if (_dpi != entity->getDPI()) { - return true; - } + if (_sourceURL != entity->getSourceUrl()) { + return true; + } - if (_scriptURL != entity->getScriptURL()) { - return true; - } + if (_dpi != entity->getDPI()) { + return true; + } - if (_maxFPS != entity->getMaxFPS()) { - return true; - } + if (_scriptURL != entity->getScriptURL()) { + return true; + } - if (_inputMode != entity->getInputMode()) { - return true; - } + if (_maxFPS != entity->getMaxFPS()) { + return true; + } - if (_pulseProperties != entity->getPulseProperties()) { + if (_inputMode != entity->getInputMode()) { + return true; + } + + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + + return false; + })) { return true; } @@ -185,17 +195,14 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene ContentType currentContentType; withReadLock([&] { urlChanged = _sourceURL != newSourceURL; - currentContentType = _contentType; }); + currentContentType = _contentType; if (urlChanged) { - withWriteLock([&] { - _contentType = newContentType; - }); - if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); } + _contentType = newContentType; } } @@ -206,6 +213,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _color = entity->getColor(); _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); + _billboardMode = entity->getBillboardMode(); if (_contentType == ContentType::NoContent) { return; @@ -216,12 +224,12 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene buildWebSurface(entity, newSourceURL); } - if (_webSurface && _webSurface->getRootItem()) { + if (_webSurface) { if (_webSurface->getRootItem()) { if (_contentType == ContentType::HtmlContent && urlChanged) { _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); - _sourceURL = newSourceURL; } + _sourceURL = newSourceURL; { auto scriptURL = entity->getScriptURL(); @@ -268,6 +276,17 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene }); } +Item::Bound WebEntityRenderer::getBound() { + auto bound = Parent::getBound(); + if (_billboardMode != BillboardMode::NONE) { + glm::vec3 dimensions = bound.getScale(); + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + bound.setScaleStayCentered(glm::vec3(SQRT_2 * max)); + } + return bound; +} + void WebEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("WebEntityRenderer::render"); withWriteLock([&] { @@ -295,14 +314,18 @@ void WebEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; glm::vec4 color; + Transform transform; withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); - batch.setModelTransform(_renderTransform); + transform = _renderTransform; }); batch.setResourceTexture(0, _texture); + transform.setRotation(EntityItem::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode)); + batch.setModelTransform(transform); + // Turn off jitter for these entities batch.pushProjectionJitter(); DependencyManager::get()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD); @@ -361,7 +384,6 @@ void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (_inputMode == WebInputMode::MOUSE) { handlePointerEvent(event); } else if (_webSurface) { - qDebug() << "boop5" << this << _webSurface << _webSurface->getRootItem(); PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverBeginEvent(webEvent, _touchDevice); @@ -450,5 +472,5 @@ QObject* WebEntityRenderer::getEventHandler() { } void WebEntityRenderer::emitScriptEvent(const QVariant& message) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); + emit scriptEventReceived(message); } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 5d97eb5b8e..30b63a72df 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -47,19 +47,20 @@ public: } } + virtual void setProxyWindow(QWindow* proxyWindow) override; + virtual QObject* getEventHandler() override; + protected: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; + Item::Bound getBound() override; virtual bool wantsHandControllerPointerEvents() const override { return true; } virtual bool wantsKeyboardFocus() const override { return true; } - virtual void setProxyWindow(QWindow* proxyWindow) override; - virtual QObject* getEventHandler() override; - void handlePointerEventAsTouch(const PointerEvent& event); void handlePointerEventAsMouse(const PointerEvent& event); @@ -85,6 +86,7 @@ private: glm::u8vec3 _color; float _alpha { 1.0f }; PulsePropertyGroup _pulseProperties; + BillboardMode _billboardMode; QString _sourceURL; uint16_t _dpi; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 57ff8ed8c2..631148c27a 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -198,24 +198,33 @@ void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DependencyManager::get()->updateZone(entity->getID()); + auto position = entity->getWorldPosition(); + auto rotation = entity->getWorldOrientation(); + auto dimensions = entity->getScaledDimensions(); + bool rotationChanged = rotation != _lastRotation; + bool transformChanged = rotationChanged || position != _lastPosition || dimensions != _lastDimensions; + + auto proceduralUserData = entity->getUserData(); + bool proceduralUserDataChanged = _proceduralUserData != proceduralUserData; + // FIXME one of the bools here could become true between being fetched and being reset, // resulting in a lost update - bool keyLightChanged = entity->keyLightPropertiesChanged(); - bool ambientLightChanged = entity->ambientLightPropertiesChanged(); - bool skyboxChanged = entity->skyboxPropertiesChanged(); + bool keyLightChanged = entity->keyLightPropertiesChanged() || rotationChanged; + bool ambientLightChanged = entity->ambientLightPropertiesChanged() || transformChanged; + bool skyboxChanged = entity->skyboxPropertiesChanged() || proceduralUserDataChanged; bool hazeChanged = entity->hazePropertiesChanged(); bool bloomChanged = entity->bloomPropertiesChanged(); - entity->resetRenderingPropertiesChanged(); - _lastPosition = entity->getWorldPosition(); - _lastRotation = entity->getWorldOrientation(); - _lastDimensions = entity->getScaledDimensions(); - _keyLightProperties = entity->getKeyLightProperties(); - _ambientLightProperties = entity->getAmbientLightProperties(); - _skyboxProperties = entity->getSkyboxProperties(); - _hazeProperties = entity->getHazeProperties(); - _bloomProperties = entity->getBloomProperties(); + if (transformChanged) { + _lastPosition = entity->getWorldPosition(); + _lastRotation = entity->getWorldOrientation(); + _lastDimensions = entity->getScaledDimensions(); + } + + if (proceduralUserDataChanged) { + _proceduralUserData = entity->getUserData(); + } #if 0 if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) { @@ -239,21 +248,29 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeyZoneItemFromEntity(entity); if (keyLightChanged) { + _keyLightProperties = entity->getKeyLightProperties(); updateKeySunFromEntity(entity); } if (ambientLightChanged) { + _ambientLightProperties = entity->getAmbientLightProperties(); updateAmbientLightFromEntity(entity); } - if (skyboxChanged || _proceduralUserData != entity->getUserData()) { + if (skyboxChanged) { + _skyboxProperties = entity->getSkyboxProperties(); updateKeyBackgroundFromEntity(entity); } if (hazeChanged) { + _hazeProperties = entity->getHazeProperties(); updateHazeFromEntity(entity); } + if (bloomChanged) { + _bloomProperties = entity->getBloomProperties(); + updateBloomFromEntity(entity); + } bool visuallyReady = true; uint32_t skyboxMode = entity->getSkyboxMode(); @@ -264,10 +281,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen } entity->setVisuallyReady(visuallyReady); - - if (bloomChanged) { - updateBloomFromEntity(entity); - } } void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { @@ -344,7 +357,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity // Set the keylight sunLight->setColor(ColorUtils::toVec3(_keyLightProperties.getColor())); sunLight->setIntensity(_keyLightProperties.getIntensity()); - sunLight->setDirection(entity->getTransform().getRotation() * _keyLightProperties.getDirection()); + sunLight->setDirection(_lastRotation * _keyLightProperties.getDirection()); sunLight->setCastShadows(_keyLightProperties.getCastShadows()); } @@ -356,7 +369,6 @@ void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& ambientLight->setPosition(_lastPosition); ambientLight->setOrientation(_lastRotation); - // Set the ambient light ambientLight->setAmbientIntensity(_ambientLightProperties.getAmbientIntensity()); @@ -395,8 +407,6 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) haze->setHazeAttenuateKeyLight(_hazeProperties.getHazeAttenuateKeyLight()); haze->setHazeKeyLightRangeFactor(graphics::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange())); haze->setHazeKeyLightAltitudeFactor(graphics::Haze::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude())); - - haze->setTransform(entity->getTransform().getMatrix()); } void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity) { @@ -414,13 +424,13 @@ void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& editBackground(); setSkyboxColor(toGlm(_skyboxProperties.getColor())); - setProceduralUserData(entity->getUserData()); + setProceduralUserData(_proceduralUserData); setSkyboxURL(_skyboxProperties.getURL()); } void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& entity) { // Update rotation values - editSkybox()->setOrientation(entity->getTransform().getRotation()); + editSkybox()->setOrientation(_lastRotation); /* TODO: Implement the sun model behavior / Keep this code here for reference, this is how we { @@ -540,9 +550,6 @@ void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { } void ZoneEntityRenderer::setProceduralUserData(const QString& userData) { - if (_proceduralUserData != userData) { - _proceduralUserData = userData; - std::dynamic_pointer_cast(editSkybox())->parse(_proceduralUserData); - } + std::dynamic_pointer_cast(editSkybox())->parse(userData); } diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index c033d2c247..f591370186 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -36,24 +36,23 @@ void main(void) { TransformObject obj = getTransformObject(); _distanceFromCenter = -1.0 + 2.0 * evenVertex; vec4 position = vec4(vertex.positionAndUCoord.xyz, 1.0); - vec3 normal = vertex.normal.xyz; vec3 binormal = vertex.binormalAndHalfWidth.xyz; if (_polylineData.faceCameraGlow.x != 0.0) { vec4 posEye; - vec3 normalEye; - vec3 binormalEye; + vec3 tangentEye; <$transformModelToEyePos(cam, obj, position, posEye)$> - <$transformModelToEyeDir(cam, obj, normal, normalEye)$> - <$transformModelToEyeDir(cam, obj, binormal, binormalEye)$> + // See comment in RenderablePolyLineEntityItem.cpp: we actually pass in the tangent in faceCamera mode + <$transformModelToEyeDir(cam, obj, binormal, tangentEye)$> - vec3 tangentEye = cross(binormalEye, normalEye); // new normal faces the camera - normalEye = normalize(posEye.xyz); - binormalEye = normalize(cross(normalEye, tangentEye)); + vec3 normalEye = normalize(posEye.xyz); + + vec3 binormalEye = normalize(cross(normalEye, tangentEye)); posEye.xyz += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormalEye; <$transformEyeToClipPos(cam, posEye, gl_Position)$> <$transformEyeToWorldDir(cam, normalEye, _normalWS)$> } else { + vec3 normal = vertex.normal.xyz; position.xyz += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormal; <$transformModelToClipPos(cam, obj, position, gl_Position)$> <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index fcbe563f88..e359c7132f 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") +target_include_directories(${TARGET_NAME} PRIVATE "${OPENSSL_INCLUDE_DIR}") include_hifi_library_headers(hfm) include_hifi_library_headers(fbx) include_hifi_library_headers(gpu) diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 36d1f41267..dc32419ed0 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -193,7 +193,8 @@ DiffTraversal::DiffTraversal() { _path.reserve(MIN_PATH_DEPTH); } -DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root, + bool forceFirstPass) { assert(root); // there are three types of traversal: // @@ -212,7 +213,7 @@ DiffTraversal::Type DiffTraversal::prepareNewTraversal(const DiffTraversal::View Type type; // If usesViewFrustum changes, treat it as a First traversal - if (_completedView.startTime == 0 || _currentView.usesViewFrustums() != _completedView.usesViewFrustums()) { + if (forceFirstPass || _completedView.startTime == 0 || _currentView.usesViewFrustums() != _completedView.usesViewFrustums()) { type = Type::First; _currentView.viewFrustums = view.viewFrustums; _currentView.lodScaleFactor = view.lodScaleFactor; diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index d62c7b8ee1..e1107ec930 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -61,7 +61,7 @@ public: DiffTraversal(); - Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root); + Type prepareNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root, bool forceFirstPass = false); const View& getCurrentView() const { return _currentView; } diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index 836dae2057..c911eda471 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -59,6 +59,7 @@ public: virtual bool isReadyForAdd() const { return true; } bool isActive() { return _active; } + void deactivate() { _active = false; } virtual void removeFromSimulation(EntitySimulationPointer simulation) const = 0; virtual EntityItemWeakPointer getOwnerEntity() const = 0; diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index a4ec2c45f9..99a5202986 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -56,6 +56,5 @@ private: private: std::mutex _mutex; AvatarData* _myAvatar { nullptr }; - QScriptEngine _scriptEngine; }; #endif // hifi_EntityEditPacketSender_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 41e4f43a5d..2c6d679b46 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -49,6 +49,8 @@ int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; QString EntityItem::_marketplacePublicKey; +std::function EntityItem::_getBillboardRotationOperator = [](const glm::vec3&, const glm::quat& rotation, BillboardMode) { return rotation; }; + EntityItem::EntityItem(const EntityItemID& entityItemID) : SpatiallyNestable(NestableType::Entity, entityItemID) { @@ -88,13 +90,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_CREATED; requestedProperties += PROP_LAST_EDITED_BY; - //requestedProperties += PROP_ENTITY_HOST_TYPE; // not sent over the wire - //requestedProperties += PROP_OWNING_AVATAR_ID; // not sent over the wire + requestedProperties += PROP_ENTITY_HOST_TYPE; + requestedProperties += PROP_OWNING_AVATAR_ID; requestedProperties += PROP_PARENT_ID; requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_CAN_CAST_SHADOW; - // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire + requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; requestedProperties += PROP_RENDER_LAYER; requestedProperties += PROP_PRIMITIVE_MODE; requestedProperties += PROP_IGNORE_PICK_INTERSECTION; @@ -180,6 +182,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); + // these properties are not sent over the wire + requestedProperties -= PROP_ENTITY_HOST_TYPE; + requestedProperties -= PROP_OWNING_AVATAR_ID; + requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA; + // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { @@ -2165,6 +2172,12 @@ void EntityItem::enableNoBootstrap() { if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) { _flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + + // NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here + // because most enableNoBootstrap() cases are already correctly handled outside this scope + // and I didn't want to add redundant work. + // TODO: cleanup Grabs & dirtySimulationFlags to be more efficient and make more sense. + forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); @@ -2179,11 +2192,19 @@ void EntityItem::disableNoBootstrap() { if (!stillHasGrabActions()) { _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + + EntityTreePointer entityTree = getTree(); + assert(entityTree); + EntitySimulationPointer simulation = entityTree->getSimulation(); + assert(simulation); + simulation->changeEntity(getThisPointer()); + forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + simulation->changeEntity(entity); } }); } @@ -2635,15 +2656,8 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts"; - foreach(const auto& property, jsonFilters.keys()) { - if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { - // check if this entity has a non-default value for serverScripts - if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) { - return true; - } else { - return false; - } - } + if (jsonFilters[SERVER_SCRIPTS_PROPERTY] == EntityQueryFilterSymbol::NonDefault) { + return _serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS; } // the json filter syntax did not match what we expected, return a match @@ -3440,7 +3454,7 @@ void EntityItem::addGrab(GrabPointer grab) { if (useAction) { EntityTreePointer entityTree = getTree(); assert(entityTree); - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + EntitySimulationPointer simulation = entityTree->getSimulation(); assert(simulation); auto actionFactory = DependencyManager::get(); @@ -3489,7 +3503,6 @@ void EntityItem::removeGrab(GrabPointer grab) { setLocalVelocity(glm::vec3(0.0f)); setAngularVelocity(glm::vec3(0.0f)); } - markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); QUuid actionID = grab->getActionID(); if (!actionID.isNull()) { @@ -3506,3 +3519,13 @@ void EntityItem::removeGrab(GrabPointer grab) { } disableNoBootstrap(); } + +void EntityItem::disableGrab(GrabPointer grab) { + QUuid actionID = grab->getActionID(); + if (!actionID.isNull()) { + EntityDynamicPointer action = _grabActions.value(actionID); + if (action) { + action->deactivate(); + } + } +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ec7ad78313..5d6627d461 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -561,6 +561,10 @@ public: virtual void addGrab(GrabPointer grab) override; virtual void removeGrab(GrabPointer grab) override; + virtual void disableGrab(GrabPointer grab) override; + + static void setBillboardRotationOperator(std::function getBillboardRotationOperator) { _getBillboardRotationOperator = getBillboardRotationOperator; } + static glm::quat getBillboardRotation(const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) { return _getBillboardRotationOperator(position, rotation, billboardMode); } signals: void requestRenderUpdate(); @@ -753,6 +757,8 @@ private: std::unordered_map _materials; std::mutex _materialsLock; + static std::function _getBillboardRotationOperator; + }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7cafaece7a..6738b1cedd 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -543,6 +543,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha); changedProperties += _pulse.getChangedProperties(); CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); + CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); // Particles CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); @@ -601,7 +602,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_TEXT_ALPHA, textAlpha); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_ALPHA, backgroundAlpha); - CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); CHECK_PROPERTY_CHANGE(PROP_LEFT_MARGIN, leftMargin); CHECK_PROPERTY_CHANGE(PROP_RIGHT_MARGIN, rightMargin); CHECK_PROPERTY_CHANGE(PROP_TOP_MARGIN, topMargin); @@ -642,6 +642,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_SCRIPT_URL, scriptURL); CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS); CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode); + CHECK_PROPERTY_CHANGE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); // Polyline CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); @@ -945,12 +946,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "Material" {@link Entities.EntityType|EntityType} modifies the existing materials on - * {@link Entities.EntityType|Model} entities, {@link Entities.EntityType|Shape} entities (albedo only), - * {@link Overlays.OverlayType|model overlays}, and avatars. + * The "Material" {@link Entities.EntityType|EntityType} modifies the existing materials on entities and avatars. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
- * To apply a material to an entity or overlay, set the material entity's parentID property to the entity or - * overlay's ID. + * To apply a material to an entity, set the material entity's parentID property to the entity ID. * To apply a material to an avatar, set the material entity's parentID property to the avatar's session UUID. * To apply a material to your avatar such that it persists across domains and log-ins, create the material as an avatar entity * by setting the entityHostType parameter in {@link Entities.addEntity} to "avatar". @@ -1339,6 +1337,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the web surface. * @property {number} alpha=1 - The alpha of the web surface. + * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be + * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated + * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set + * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate * on the Web entity. * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter @@ -1348,6 +1350,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. * @property {WebInputMode} inputMode="touch" - The user input mode to use. * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. + * @property {boolean} showKeyboardFocusHighlight - Whether or not to show the keyboard focus highlight when this entity has focus. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1414,8 +1417,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.Bloom} bloom - The bloom properties of the zone. * * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. + * Only works on domain entities. * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not - * collide with content in the zone; otherwise visitors will always collide with content in the zone. + * collide with content in the zone; otherwise visitors will always collide with content in the zone. Only works on domain entities. * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to @@ -1718,6 +1722,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Text only if (_type == EntityTypes::Text) { _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight); @@ -1725,7 +1730,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_ALPHA, textAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_BACKGROUND_COLOR, backgroundColor, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BACKGROUND_ALPHA, backgroundAlpha); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LEFT_MARGIN, leftMargin); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RIGHT_MARGIN, rightMargin); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TOP_MARGIN, topMargin); @@ -1761,12 +1765,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); } // PolyVoxel only @@ -1826,11 +1832,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEEP_ASPECT_RATIO, keepAspectRatio); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SUB_IMAGE, subImage); // Handle conversions to old 'textures' property from "imageURL" @@ -1926,7 +1932,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool properties.setProperty("localEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::LOCAL)); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FaceCamera)) { + if (_type != EntityTypes::PolyLine && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FaceCamera))) { properties.setProperty("faceCamera", convertScriptValue(engine, getBillboardMode() == BillboardMode::YAW)); } if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::IsFacingAvatar)) { @@ -2035,6 +2041,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); _pulse.copyFromScriptValue(object, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); // Particles COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, quint32, setMaxParticles); @@ -2093,7 +2100,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(textAlpha, float, setTextAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundColor, u8vec3Color, setBackgroundColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundAlpha, float, setBackgroundAlpha); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(leftMargin, float, setLeftMargin); COPY_PROPERTY_FROM_QSCRIPTVALUE(rightMargin, float, setRightMargin); COPY_PROPERTY_FROM_QSCRIPTVALUE(topMargin, float, setTopMargin); @@ -2134,6 +2140,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptURL, QString, setScriptURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(showKeyboardFocusHighlight, bool, setShowKeyboardFocusHighlight); // Polyline COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); @@ -2193,7 +2200,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } // Handle old "faceCamera" and "isFacingAvatar" props - { + if (_type != EntityTypes::PolyLine) { QScriptValue P = object.property("faceCamera"); if (P.isValid() && !object.property("billboardMode").isValid()) { bool newValue = P.toVariant().toBool(); @@ -2313,6 +2320,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(alpha); _pulse.merge(other._pulse); COPY_PROPERTY_IF_CHANGED(textures); + COPY_PROPERTY_IF_CHANGED(billboardMode); // Particles COPY_PROPERTY_IF_CHANGED(maxParticles); @@ -2371,7 +2379,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(textAlpha); COPY_PROPERTY_IF_CHANGED(backgroundColor); COPY_PROPERTY_IF_CHANGED(backgroundAlpha); - COPY_PROPERTY_IF_CHANGED(billboardMode); COPY_PROPERTY_IF_CHANGED(leftMargin); COPY_PROPERTY_IF_CHANGED(rightMargin); COPY_PROPERTY_IF_CHANGED(topMargin); @@ -2412,6 +2419,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(scriptURL); COPY_PROPERTY_IF_CHANGED(maxFPS); COPY_PROPERTY_IF_CHANGED(inputMode); + COPY_PROPERTY_IF_CHANGED(showKeyboardFocusHighlight); // Polyline COPY_PROPERTY_IF_CHANGED(linePoints); @@ -2631,6 +2639,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode); } ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString); + ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); // Particles ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, @@ -2724,7 +2733,6 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_TEXT_ALPHA, TextAlpha, textAlpha, float); ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color); ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_ALPHA, BackgroundAlpha, backgroundAlpha, float); - ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); ADD_PROPERTY_TO_MAP(PROP_LEFT_MARGIN, LeftMargin, leftMargin, float); ADD_PROPERTY_TO_MAP(PROP_RIGHT_MARGIN, RightMargin, rightMargin, float); ADD_PROPERTY_TO_MAP(PROP_TOP_MARGIN, TopMargin, topMargin, float); @@ -2796,6 +2804,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString); ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t); ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode); + ADD_PROPERTY_TO_MAP(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool); // Polyline ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -3133,6 +3142,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); @@ -3140,7 +3150,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_TEXT_ALPHA, properties.getTextAlpha()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, properties.getBackgroundColor()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, properties.getBackgroundAlpha()); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_LEFT_MARGIN, properties.getLeftMargin()); APPEND_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, properties.getRightMargin()); APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, properties.getTopMargin()); @@ -3198,12 +3207,14 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL()); APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode()); + APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, properties.getShowKeyboardFocusHighlight()); } if (properties.getType() == EntityTypes::Line) { @@ -3258,11 +3269,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticPulse.setProperties(properties); _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive()); APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, properties.getKeepAspectRatio()); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, properties.getSubImage()); } @@ -3604,6 +3615,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Text) { properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); @@ -3611,7 +3623,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_ALPHA, float, setTextAlpha); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_COLOR, u8vec3Color, setBackgroundColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_ALPHA, float, setBackgroundAlpha); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LEFT_MARGIN, float, setLeftMargin); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RIGHT_MARGIN, float, setRightMargin); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TOP_MARGIN, float, setTopMargin); @@ -3658,12 +3669,14 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); } if (properties.getType() == EntityTypes::Line) { @@ -3715,11 +3728,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SUB_IMAGE, QRect, setSubImage); } @@ -3934,11 +3947,12 @@ void EntityItemProperties::markAllChanged() { // Common _shapeTypeChanged = true; + _compoundShapeURLChanged = true; _colorChanged = true; _alphaChanged = true; _pulse.markAllChanged(); _texturesChanged = true; - _compoundShapeURLChanged = true; + _billboardModeChanged = true; // Particles _maxParticlesChanged = true; @@ -3997,7 +4011,6 @@ void EntityItemProperties::markAllChanged() { _textAlphaChanged = true; _backgroundColorChanged = true; _backgroundAlphaChanged = true; - _billboardModeChanged = true; _leftMarginChanged = true; _rightMarginChanged = true; _topMarginChanged = true; @@ -4038,6 +4051,7 @@ void EntityItemProperties::markAllChanged() { _scriptURLChanged = true; _maxFPSChanged = true; _inputModeChanged = true; + _showKeyboardFocusHighlightChanged = true; // Polyline _linePointsChanged = true; @@ -4409,6 +4423,9 @@ QList EntityItemProperties::listChangedProperties() { if (texturesChanged()) { out += "textures"; } + if (billboardModeChanged()) { + out += "billboardMode"; + } // Particles if (maxParticlesChanged()) { @@ -4565,9 +4582,6 @@ QList EntityItemProperties::listChangedProperties() { if (backgroundAlphaChanged()) { out += "backgroundAlpha"; } - if (billboardModeChanged()) { - out += "billboardMode"; - } if (leftMarginChanged()) { out += "leftMargin"; } @@ -4689,6 +4703,9 @@ QList EntityItemProperties::listChangedProperties() { if (faceCameraChanged()) { out += "faceCamera"; } + if (showKeyboardFocusHighlightChanged()) { + out += "showKeyboardFocusHighlight"; + } // Shape if (shapeChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index dcba60b004..712f2d120f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -242,6 +242,7 @@ public: DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, ENTITY_ITEM_DEFAULT_ALPHA); DEFINE_PROPERTY_GROUP(Pulse, pulse, PulsePropertyGroup); DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); + DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); // Particles DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, particle::DEFAULT_MAX_PARTICLES); @@ -300,7 +301,6 @@ public: DEFINE_PROPERTY_REF(PROP_TEXT_ALPHA, TextAlpha, textAlpha, float, TextEntityItem::DEFAULT_TEXT_ALPHA); DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color, TextEntityItem::DEFAULT_BACKGROUND_COLOR); DEFINE_PROPERTY_REF(PROP_BACKGROUND_ALPHA, BackgroundAlpha, backgroundAlpha, float, TextEntityItem::DEFAULT_TEXT_ALPHA); - DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); DEFINE_PROPERTY_REF(PROP_LEFT_MARGIN, LeftMargin, leftMargin, float, TextEntityItem::DEFAULT_MARGIN); DEFINE_PROPERTY_REF(PROP_RIGHT_MARGIN, RightMargin, rightMargin, float, TextEntityItem::DEFAULT_MARGIN); DEFINE_PROPERTY_REF(PROP_TOP_MARGIN, TopMargin, topMargin, float, TextEntityItem::DEFAULT_MARGIN); @@ -341,6 +341,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString, ""); DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS); DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH); + DEFINE_PROPERTY_REF(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool, true); // Polyline DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b11ecff5bb..093df92dc1 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -118,6 +118,7 @@ enum EntityPropertyList { PROP_PULSE_COLOR_MODE, PROP_PULSE_ALPHA_MODE, PROP_TEXTURES, + PROP_BILLBOARD_MODE, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new shared EntityItem properties to the list ABOVE this line @@ -232,11 +233,10 @@ enum EntityPropertyList { PROP_TEXT_ALPHA = PROP_DERIVED_3, PROP_BACKGROUND_COLOR = PROP_DERIVED_4, PROP_BACKGROUND_ALPHA = PROP_DERIVED_5, - PROP_BILLBOARD_MODE = PROP_DERIVED_6, - PROP_LEFT_MARGIN = PROP_DERIVED_7, - PROP_RIGHT_MARGIN = PROP_DERIVED_8, - PROP_TOP_MARGIN = PROP_DERIVED_9, - PROP_BOTTOM_MARGIN = PROP_DERIVED_10, + PROP_LEFT_MARGIN = PROP_DERIVED_6, + PROP_RIGHT_MARGIN = PROP_DERIVED_7, + PROP_TOP_MARGIN = PROP_DERIVED_8, + PROP_BOTTOM_MARGIN = PROP_DERIVED_9, // Zone // Keylight @@ -296,6 +296,7 @@ enum EntityPropertyList { PROP_SCRIPT_URL = PROP_DERIVED_2, PROP_MAX_FPS = PROP_DERIVED_3, PROP_INPUT_MODE = PROP_DERIVED_4, + PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT = PROP_DERIVED_5, // Polyline PROP_LINE_POINTS = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 286f0dd650..150aa6b0cf 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -57,11 +57,6 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership connect(nodeList.data(), &NodeList::canRezTmpCertifiedChanged, this, &EntityScriptingInterface::canRezTmpCertifiedChanged); connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged); - // If the user clicks somewhere where there is no entity at all, we will release focus - connect(this, &EntityScriptingInterface::mousePressOffEntity, [=]() { - setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); - }); - auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket"); } @@ -474,7 +469,7 @@ void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QSt } -QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) { +QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.addedEntityCount++; @@ -483,10 +478,10 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties const auto sessionID = nodeList->getSessionUUID(); EntityItemProperties propertiesWithSimID = properties; - propertiesWithSimID.setEntityHostTypeFromString(entityHostTypeString); - if (propertiesWithSimID.getEntityHostType() == entity::HostType::AVATAR) { + propertiesWithSimID.setEntityHostType(entityHostType); + if (entityHostType == entity::HostType::AVATAR) { propertiesWithSimID.setOwningAvatarID(sessionID); - } else if (propertiesWithSimID.getEntityHostType() == entity::HostType::LOCAL) { + } else if (entityHostType == entity::HostType::LOCAL) { // For now, local entities are always collisionless // TODO: create a separate, local physics simulation that just handles local entities (and MyAvatar?) propertiesWithSimID.setCollisionless(true); @@ -571,7 +566,7 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin return addEntity(properties); } -QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) { +QUuid EntityScriptingInterface::cloneEntity(const QUuid& entityIDToClone) { EntityItemID newEntityID; EntityItemProperties properties = getEntityProperties(entityIDToClone); bool cloneAvatarEntity = properties.getCloneAvatarEntity(); @@ -579,9 +574,9 @@ QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) { if (properties.getEntityHostType() == entity::HostType::LOCAL) { // Local entities are only cloned locally - return addEntity(properties, "local"); + return addEntityInternal(properties, entity::HostType::LOCAL); } else if (cloneAvatarEntity) { - return addEntity(properties, "avatar"); + return addEntityInternal(properties, entity::HostType::AVATAR); } else { // setLastEdited timestamp to 0 to ensure this entity gets updated with the properties // from the server-created entity, don't change this unless you know what you are doing @@ -596,12 +591,12 @@ QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) { } } -EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID) { +EntityItemProperties EntityScriptingInterface::getEntityProperties(const QUuid& entityID) { const EntityPropertyFlags noSpecificProperties; return getEntityProperties(entityID, noSpecificProperties); } -EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties) { +EntityItemProperties EntityScriptingInterface::getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); bool scalesWithParent { false }; @@ -775,7 +770,7 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri return finalResult; } -QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { +QUuid EntityScriptingInterface::editEntity(const QUuid& id, const EntityItemProperties& scriptSideProperties) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.editedEntityCount++; @@ -960,7 +955,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& return id; } -void EntityScriptingInterface::deleteEntity(QUuid id) { +void EntityScriptingInterface::deleteEntity(const QUuid& id) { PROFILE_RANGE(script_entities, __FUNCTION__); _activityTracking.deletedEntityCount++; @@ -1005,12 +1000,51 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { } } +QString EntityScriptingInterface::getEntityType(const QUuid& entityID) { + QString toReturn; + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (entity) { + toReturn = EntityTypes::getEntityTypeName(entity->getType()); + } + }); + return toReturn; +} + +QObject* EntityScriptingInterface::getEntityObject(const QUuid& id) { + return EntityTree::getEntityObject(id); +} + +bool EntityScriptingInterface::isLoaded(const QUuid& id) { + bool toReturn = false; + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(id); + if (entity) { + toReturn = entity->isVisuallyReady(); + } + }); + return toReturn; +} + +bool EntityScriptingInterface::isAddedEntity(const QUuid& id) { + bool toReturn = false; + _entityTree->withReadLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(id); + toReturn = (bool)entity; + }); + return toReturn; +} + +QSizeF EntityScriptingInterface::textSize(const QUuid& id, const QString& text) { + return EntityTree::textSize(id, text); +} + void EntityScriptingInterface::setEntitiesScriptEngine(QSharedPointer engine) { std::lock_guard lock(_entitiesScriptEngineLock); _entitiesScriptEngine = engine; } -void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) { +void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) { PROFILE_RANGE(script_entities, __FUNCTION__); std::lock_guard lock(_entitiesScriptEngineLock); @@ -1020,12 +1054,12 @@ void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, } } -void EntityScriptingInterface::callEntityServerMethod(QUuid id, const QString& method, const QStringList& params) { +void EntityScriptingInterface::callEntityServerMethod(const QUuid& id, const QString& method, const QStringList& params) { PROFILE_RANGE(script_entities, __FUNCTION__); DependencyManager::get()->callEntityServerMethod(id, method, params); } -void EntityScriptingInterface::callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, const QStringList& params) { +void EntityScriptingInterface::callEntityClientMethod(const QUuid& clientSessionID, const QUuid& entityID, const QString& method, const QStringList& params) { PROFILE_RANGE(script_entities, __FUNCTION__); auto scriptServerServices = DependencyManager::get(); @@ -1262,7 +1296,7 @@ ParabolaToEntityIntersectionResult EntityScriptingInterface::evalParabolaInterse return result; } -bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { +bool EntityScriptingInterface::reloadServerScripts(const QUuid& entityID) { auto client = DependencyManager::get(); return client->reloadServerScript(entityID); } @@ -1335,7 +1369,7 @@ bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScript return true; } -bool EntityScriptingInterface::queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName) { +bool EntityScriptingInterface::queryPropertyMetadata(const QUuid& entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName) { auto name = property.toString(); auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName); QPointer engine = dynamic_cast(handler.engine()); @@ -1379,7 +1413,7 @@ bool EntityScriptingInterface::queryPropertyMetadata(QUuid entityID, QScriptValu } } -bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) { +bool EntityScriptingInterface::getServerScriptStatus(const QUuid& entityID, QScriptValue callback) { auto client = DependencyManager::get(); auto request = client->createScriptStatusRequest(entityID); connect(request, &GetScriptStatusRequest::finished, callback.engine(), [callback](GetScriptStatusRequest* request) mutable { @@ -1518,14 +1552,14 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function& points) { +bool EntityScriptingInterface::setAllPoints(const QUuid& entityID, const QVector& points) { PROFILE_RANGE(script_entities, __FUNCTION__); EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); @@ -1580,7 +1614,7 @@ bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector(_entityTree->findEntityByEntityItemID(entityID)); @@ -2040,7 +2074,7 @@ QVector EntityScriptingInterface::getChildrenIDs(const QUuid& parentID) { return result; } -bool EntityScriptingInterface::isChildOfParent(QUuid childID, QUuid parentID) { +bool EntityScriptingInterface::isChildOfParent(const QUuid& childID, const QUuid& parentID) { bool isChild = false; if (!_entityTree) { @@ -2062,7 +2096,7 @@ bool EntityScriptingInterface::isChildOfParent(QUuid childID, QUuid parentID) { return isChild; } -QString EntityScriptingInterface::getNestableType(QUuid id) { +QString EntityScriptingInterface::getNestableType(const QUuid& id) { QSharedPointer parentFinder = DependencyManager::get(); if (!parentFinder) { return "unknown"; @@ -2114,8 +2148,8 @@ QUuid EntityScriptingInterface::getKeyboardFocusEntity() const { return result; } -void EntityScriptingInterface::setKeyboardFocusEntity(const EntityItemID& id) { - QMetaObject::invokeMethod(qApp, "setKeyboardFocusEntity", Qt::DirectConnection, Q_ARG(EntityItemID, id)); +void EntityScriptingInterface::setKeyboardFocusEntity(const QUuid& id) { + QMetaObject::invokeMethod(qApp, "setKeyboardFocusEntity", Qt::DirectConnection, Q_ARG(const QUuid&, id)); } void EntityScriptingInterface::sendMousePressOnEntity(const EntityItemID& id, const PointerEvent& event) { @@ -2154,7 +2188,7 @@ void EntityScriptingInterface::sendHoverLeaveEntity(const EntityItemID& id, cons emit hoverLeaveEntity(id, event); } -bool EntityScriptingInterface::wantsHandControllerPointerEvents(QUuid id) { +bool EntityScriptingInterface::wantsHandControllerPointerEvents(const QUuid& id) { bool result = false; if (_entityTree) { _entityTree->withReadLock([&] { @@ -2186,7 +2220,7 @@ bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, cons return aaBox.findCapsulePenetration(start, end, radius, penetration); } -void EntityScriptingInterface::getMeshes(QUuid entityID, QScriptValue callback) { +void EntityScriptingInterface::getMeshes(const QUuid& entityID, QScriptValue callback) { PROFILE_RANGE(script_entities, __FUNCTION__); EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 0e96cb2d25..0cf9070b08 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -109,6 +109,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * @@ -163,6 +164,9 @@ public: */ static QScriptValue getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine); QScriptValue getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector entityIDs, const QScriptValue& extendedDesiredProperties); + + QUuid addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType); + public slots: /**jsdoc @@ -265,7 +269,17 @@ public slots: * }); * print("Entity created: " + entityID); */ - Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString); + Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) { + entity::HostType entityHostType; + if (entityHostTypeString == "local") { + entityHostType = entity::HostType::LOCAL; + } else if (entityHostTypeString == "avatar") { + entityHostType = entity::HostType::AVATAR; + } else { + entityHostType = entity::HostType::DOMAIN; + } + return addEntityInternal(properties, entityHostType); + } /**jsdoc * Add a new entity with specified properties. @@ -275,8 +289,8 @@ public slots: * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}. */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) { - QString entityHostType = avatarEntity ? "avatar" : "domain"; - return addEntity(properties, entityHostType); + entity::HostType entityHostType = avatarEntity ? entity::HostType::AVATAR : entity::HostType::DOMAIN; + return addEntityInternal(properties, entityHostType); } /// temporary method until addEntity can be used from QJSEngine @@ -292,7 +306,7 @@ public slots: * @param {Uuid} entityID - The ID of the entity to clone. * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid|Uuid.NULL}. */ - Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone); + Q_INVOKABLE QUuid cloneEntity(const QUuid& entityID); /**jsdoc * Get the properties of an entity. @@ -311,8 +325,8 @@ public slots: * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); */ - Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID); - Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID, EntityPropertyFlags desiredProperties); + Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID); + Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); /**jsdoc * Update an entity with specified properties. @@ -336,7 +350,7 @@ public slots: * properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); */ - Q_INVOKABLE QUuid editEntity(QUuid entityID, const EntityItemProperties& properties); + Q_INVOKABLE QUuid editEntity(const QUuid& entityID, const EntityItemProperties& properties); /**jsdoc * Delete an entity. @@ -354,8 +368,50 @@ public slots: * Entities.deleteEntity(entityID); * }, 3000); */ - Q_INVOKABLE void deleteEntity(QUuid entityID); + Q_INVOKABLE void deleteEntity(const QUuid& entityID); + /**jsdoc + * Get an entities type as a string. + * @function Entities.deleteEntity + * @param {Uuid} id - The id of the entity to get the type of. + */ + Q_INVOKABLE QString getEntityType(const QUuid& entityID); + + /**jsdoc + * Get the entity script object. In particular, this is useful for accessing the event bridge for a Web + * entity. + * @function Entities.getEntityObject + * @param {Uuid} id - The ID of the entity to get the script object of. + * @returns {object} The script object for the entity if found. + */ + Q_INVOKABLE QObject* getEntityObject(const QUuid& id); + + /**jsdoc + * Check whether an entities's assets have been loaded. For example, for an Model entity the result indicates + * whether its textures have been loaded. + * @function Entities.isLoaded + * @param {Uuid} id - The ID of the entity to check. + * @returns {boolean} true if the entity's assets have been loaded, otherwise false. + */ + Q_INVOKABLE bool isLoaded(const QUuid& id); + + /**jsdoc + * Check if there is an object of a given ID. + * @function Entities.isAddedEntity + * @param {Uuid} id - The ID to check. + * @returns {boolean} true if an object with the given ID exists, false otherwise. + */ + Q_INVOKABLE bool isAddedEntity(const QUuid& id); + + /**jsdoc + * Calculates the size of the given text in the specified object if it is a text entity. + * @function Entities.textSize + * @param {Uuid} id - The ID of the entity to use for calculation. + * @param {string} text - The string to calculate the size of. + * @returns {Size} The size of the text in meters if the object is a text entity, otherwise + * { height: 0, width : 0 }. + */ + Q_INVOKABLE QSizeF textSize(const QUuid& id, const QString& text); /**jsdoc * Call a method in a client entity script from a client script or client entity script, or call a method in a server @@ -367,7 +423,7 @@ public slots: * @param {string} method - The name of the method to call. * @param {string[]} [parameters=[]] - The parameters to call the specified method with. */ - Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); + Q_INVOKABLE void callEntityMethod(const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc * Call a method in a server entity script from a client script or client entity script. The entity script method must be @@ -379,7 +435,7 @@ public slots: * @param {string} method - The name of the method to call. * @param {string[]} [parameters=[]] - The parameters to call the specified method with. */ - Q_INVOKABLE void callEntityServerMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); + Q_INVOKABLE void callEntityServerMethod(const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc * Call a method in a specific user's client entity script from a server entity script. The entity script method must be @@ -390,7 +446,7 @@ public slots: * @param {string} method - The name of the method to call. * @param {string[]} [parameters=[]] - The parameters to call the specified method with. */ - Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, + Q_INVOKABLE void callEntityClientMethod(const QUuid& clientSessionID, const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc @@ -520,7 +576,7 @@ public slots: * @returns {boolean} true if the reload request was successfully sent to the server, otherwise * false. */ - Q_INVOKABLE bool reloadServerScripts(QUuid entityID); + Q_INVOKABLE bool reloadServerScripts(const QUuid& entityID); /**jsdoc * Gets the status of server entity script attached to an entity @@ -539,7 +595,7 @@ public slots: * @param {string} errorInfo - "" if there is a server entity script running, otherwise it may contain extra * information on the error. */ - Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback); + Q_INVOKABLE bool getServerScriptStatus(const QUuid& entityID, QScriptValue callback); /**jsdoc * Get metadata for certain entity properties such as script and serverScripts. @@ -569,7 +625,7 @@ public slots: * @param {object} result - The metadata for the requested entity property if there was no error, otherwise * undefined. */ - Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, + Q_INVOKABLE bool queryPropertyMetadata(const QUuid& entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); @@ -653,7 +709,7 @@ public slots: * Entities.setVoxelSphere(polyVox, position, 0.9, 255); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); + Q_INVOKABLE bool setVoxelSphere(const QUuid& entityID, const glm::vec3& center, float radius, int value); /**jsdoc * Set the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityType|PolyVox} entity. @@ -677,7 +733,7 @@ public slots: * Entities.setVoxelCapsule(polyVox, startPosition, endPosition, 0.5, 255); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelCapsule(QUuid entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); + Q_INVOKABLE bool setVoxelCapsule(const QUuid& entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); /**jsdoc * Set the value of a particular voxels in a {@link Entities.EntityType|PolyVox} entity. @@ -699,7 +755,7 @@ public slots: * Entities.setVoxel(entity, { x: 0, y: 0, z: 0 }, 0); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); + Q_INVOKABLE bool setVoxel(const QUuid& entityID, const glm::vec3& position, int value); /**jsdoc * Set the values of all voxels in a {@link Entities.EntityType|PolyVox} entity. @@ -717,7 +773,7 @@ public slots: * Entities.setAllVoxels(entity, 1); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); + Q_INVOKABLE bool setAllVoxels(const QUuid& entityID, int value); /**jsdoc * Set the values of all voxels in a cubic portion of a {@link Entities.EntityType|PolyVox} entity. @@ -742,7 +798,7 @@ public slots: * Entities.setVoxelsInCuboid(polyVox, cuboidPosition, cuboidSize, 0); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); + Q_INVOKABLE bool setVoxelsInCuboid(const QUuid& entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); /**jsdoc * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to world coordinates. Voxel coordinates are @@ -858,7 +914,7 @@ public slots: * ]); * }, 2000); */ - Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); + Q_INVOKABLE bool setAllPoints(const QUuid& entityID, const QVector& points); /**jsdoc * Append a point to a {@link Entities.EntityType|Line} entity. @@ -886,7 +942,7 @@ public slots: * // Add a third point to create a "V". * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); */ - Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); + Q_INVOKABLE bool appendPoint(const QUuid& entityID, const glm::vec3& point); /**jsdoc * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about to the program log. @@ -1260,11 +1316,11 @@ public slots: /**jsdoc - * Get the IDs of entities, overlays, and avatars that are directly parented to an entity, overlay, or avatar model. Recurse on the IDs returned by the function to get all descendants of an entity, overlay, or avatar. + * Get the IDs of entities and avatars that are directly parented to an entity or avatar model. Recurse on the IDs returned by the function to get all descendants of an entity or avatar. * @function Entities.getChildrenIDs - * @param {Uuid} parentID - The ID of the entity, overlay, or avatar to get the children IDs of. - * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID - * entity, overlay, or avatar. Does not include children's children, etc. The array is empty if no children can be found or + * @param {Uuid} parentID - The ID of the entity or avatar to get the children IDs of. + * @returns {Uuid[]} An array of entity and avatar IDs that are parented directly to the parentID + * entity or avatar. Does not include children's children, etc. The array is empty if no children can be found or * parentID cannot be found. * @example Report the children of an entity. * function createEntity(description, position, parent) { @@ -1290,12 +1346,12 @@ public slots: Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); /**jsdoc - * Get the IDs of entities, overlays, and avatars that are directly parented to an entity, overlay, or avatar model's joint. + * Get the IDs of entities and avatars that are directly parented to an entity or avatar model's joint. * @function Entities.getChildrenIDsOfJoint - * @param {Uuid} parentID - The ID of the entity, overlay, or avatar to get the children IDs of. + * @param {Uuid} parentID - The ID of the entity or avatar to get the children IDs of. * @param {number} jointIndex - Integer number of the model joint to get the children IDs of. - * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID - * entity, overlay, or avatar at the jointIndex joint. Does not include children's children, etc. The + * @returns {Uuid[]} An array of entity and avatar IDs that are parented directly to the parentID + * entity or avatar at the jointIndex joint. Does not include children's children, etc. The * array is empty if no children can be found or parentID cannot be found. * @example Report the children of your avatar's right hand. * function createEntity(description, position, parent) { @@ -1325,11 +1381,11 @@ public slots: Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); /**jsdoc - * Check whether an entity or overlay has an entity as an ancestor (parent, parent's parent, etc.). + * Check whether an entity has an entity as an ancestor (parent, parent's parent, etc.). * @function Entities.isChildOfParent - * @param {Uuid} childID - The ID of the child entity or overlay to test for being a child, grandchild, etc. + * @param {Uuid} childID - The ID of the child entity to test for being a child, grandchild, etc. * @param {Uuid} parentID - The ID of the parent entity to test for being a parent, grandparent, etc. - * @returns {boolean} true if the childID entity or overlay has the parentID entity + * @returns {boolean} true if the childID entity has the parentID entity * as a parent or grandparent etc., otherwise false. * @example Check that a grandchild entity is a child of its grandparent. * function createEntity(description, position, parent) { @@ -1351,15 +1407,14 @@ public slots: * * print("grandChild has root as parent: " + Entities.isChildOfParent(grandChild, root)); // true */ - Q_INVOKABLE bool isChildOfParent(QUuid childID, QUuid parentID); + Q_INVOKABLE bool isChildOfParent(const QUuid& childID, const QUuid& parentID); /**jsdoc - * Get the type — entity, overlay, or avatar — of an in-world item. + * Get the type — entity or avatar — of an in-world item. * @function Entities.getNestableType - * @param {Uuid} entityID - The ID of the item to get the type of. - * @returns {string} The type of the item: "entity" if the item is an entity, "overlay" if the - * the item is an overlay, "avatar" if the item is an avatar; otherwise "unknown" if the item - * cannot be found. + * @param {Uuid} id - The ID of the item to get the type of. + * @returns {string} The type of the item: "entity" if the item is an entity, "avatar" + * if the item is an avatar; otherwise "unknown" if the item cannot be found. * @example Print some nestable types. * var entity = Entities.addEntity({ * type: "Sphere", @@ -1370,7 +1425,7 @@ public slots: * print(Entities.getNestableType(entity)); // "entity" * print(Entities.getNestableType(Uuid.generate())); // "unknown" */ - Q_INVOKABLE QString getNestableType(QUuid entityID); + Q_INVOKABLE QString getNestableType(const QUuid& id); /**jsdoc * Get the ID of the {@link Entities.EntityType|Web} entity that has keyboard focus. @@ -1382,11 +1437,10 @@ public slots: /**jsdoc * Set the {@link Entities.EntityType|Web} entity that has keyboard focus. * @function Entities.setKeyboardFocusEntity - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity to set keyboard focus to. Use + * @param {Uuid} id - The ID of the {@link Entities.EntityType|Web} entity to set keyboard focus to. Use * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an entity. */ - Q_INVOKABLE void setKeyboardFocusEntity(const EntityItemID& id); - + Q_INVOKABLE void setKeyboardFocusEntity(const QUuid& id); /**jsdoc * Emit a {@link Entities.mousePressOnEntity|mousePressOnEntity} event. @@ -1468,7 +1522,7 @@ public slots: * @returns {boolean} true if the entity can be found and it wants hand controller pointer events, otherwise * false. */ - Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id); + Q_INVOKABLE bool wantsHandControllerPointerEvents(const QUuid& id); /**jsdoc * Send a script event over a {@link Entities.EntityType|Web} entity's EventBridge to the Web page's scripts. @@ -1509,7 +1563,7 @@ public slots: * @deprecated Use the {@link Graphics} API instead. */ // FIXME move to a renderable entity interface - Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); + Q_INVOKABLE void getMeshes(const QUuid& entityID, QScriptValue callback); /**jsdoc * Get the object to world transform, excluding scale, of an entity. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 954462a9f2..cfae8e250b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2957,8 +2957,8 @@ std::function E std::function EntityTree::_removeMaterialFromEntityOperator = nullptr; std::function EntityTree::_addMaterialToAvatarOperator = nullptr; std::function EntityTree::_removeMaterialFromAvatarOperator = nullptr; -std::function EntityTree::_addMaterialToOverlayOperator = nullptr; -std::function EntityTree::_removeMaterialFromOverlayOperator = nullptr; +std::function EntityTree::_getEntityObjectOperator = nullptr; +std::function EntityTree::_textSizeOperator = nullptr; bool EntityTree::addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { if (_addMaterialToEntityOperator) { @@ -2988,18 +2988,18 @@ bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::Mater return false; } -bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { - if (_addMaterialToOverlayOperator) { - return _addMaterialToOverlayOperator(overlayID, material, parentMaterialName); +QObject* EntityTree::getEntityObject(const QUuid& id) { + if (_getEntityObjectOperator) { + return _getEntityObjectOperator(id); } - return false; + return nullptr; } -bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { - if (_removeMaterialFromOverlayOperator) { - return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialName); +QSizeF EntityTree::textSize(const QUuid& id, const QString& text) { + if (_textSizeOperator) { + return _textSizeOperator(id, text); } - return false; + return QSizeF(0.0f, 0.0f); } void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f9b7b8d67f..9df01267ea 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -272,10 +272,11 @@ public: static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); - static void setAddMaterialToOverlayOperator(std::function addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; } - static void setRemoveMaterialFromOverlayOperator(std::function removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; } - static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName); - static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName); + static void setGetEntityObjectOperator(std::function getEntityObjectOperator) { _getEntityObjectOperator = getEntityObjectOperator; } + static QObject* getEntityObject(const QUuid& id); + + static void setTextSizeOperator(std::function textSizeOperator) { _textSizeOperator = textSizeOperator; } + static QSizeF textSize(const QUuid& id, const QString& text); std::map getNamedPaths() const { return _namedPaths; } @@ -389,8 +390,8 @@ private: static std::function _removeMaterialFromEntityOperator; static std::function _addMaterialToAvatarOperator; static std::function _removeMaterialFromAvatarOperator; - static std::function _addMaterialToOverlayOperator; - static std::function _removeMaterialFromOverlayOperator; + static std::function _getEntityObjectOperator; + static std::function _textSizeOperator; std::vector _staleProxies; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 3b085457d1..ce6f20262f 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -139,16 +139,22 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 return false; } -bool checkFilterSettings(const EntityItemPointer& entity, PickFilter searchFilter) { +bool EntityTreeElement::checkFilterSettings(const EntityItemPointer& entity, PickFilter searchFilter) { bool visible = entity->isVisible(); - bool collidable = !entity->getCollisionless() && (entity->getShapeType() != SHAPE_TYPE_NONE); + entity::HostType hostType = entity->getEntityHostType(); if ((!searchFilter.doesPickVisible() && visible) || (!searchFilter.doesPickInvisible() && !visible) || - (!searchFilter.doesPickCollidable() && collidable) || (!searchFilter.doesPickNonCollidable() && !collidable) || - (!searchFilter.doesPickDomainEntities() && entity->isDomainEntity()) || - (!searchFilter.doesPickAvatarEntities() && entity->isAvatarEntity()) || - (!searchFilter.doesPickLocalEntities() && entity->isLocalEntity())) { + (!searchFilter.doesPickDomainEntities() && hostType == entity::HostType::DOMAIN) || + (!searchFilter.doesPickAvatarEntities() && hostType == entity::HostType::AVATAR) || + (!searchFilter.doesPickLocalEntities() && hostType == entity::HostType::LOCAL)) { return false; } + // We only check the collidable filters for non-local entities, because local entities are always collisionless + bool collidable = !entity->getCollisionless() && (entity->getShapeType() != SHAPE_TYPE_NONE); + if (hostType != entity::HostType::LOCAL) { + if ((collidable && !searchFilter.doesPickCollidable()) || (!collidable && !searchFilter.doesPickNonCollidable())) { + return false; + } + } return true; } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 3f1fda57bd..f82eaa7fb1 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -134,6 +134,7 @@ public: virtual bool isRendered() const override { return getShouldRender(); } virtual bool deleteApproved() const override { return !hasEntities(); } + static bool checkFilterSettings(const EntityItemPointer& entity, PickFilter searchFilter); virtual bool canPickIntersect() const override { return hasEntities(); } virtual EntityItemID evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8ad654c638..91b71513dc 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -84,8 +84,7 @@ public: * {@link Entities.EntityProperties-Light|EntityProperties-Light} * "Zone"A volume of lighting effects and avatar permissions. * {@link Entities.EntityProperties-Zone|EntityProperties-Zone} - * "Material"Modifies the existing materials on Model entities, Shape entities, - * {@link Overlays.OverlayType|model overlays}, and avatars. + * "Material"Modifies the existing materials on entities and avatars. * {@link Entities.EntityProperties-Material|EntityProperties-Material} * * diff --git a/libraries/entities/src/GizmoEntityItem.cpp b/libraries/entities/src/GizmoEntityItem.cpp index c9f205d289..f2bef0cb80 100644 --- a/libraries/entities/src/GizmoEntityItem.cpp +++ b/libraries/entities/src/GizmoEntityItem.cpp @@ -10,6 +10,8 @@ #include "EntityItemProperties.h" +#include + EntityItemPointer GizmoEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { Pointer entity(new GizmoEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); @@ -109,15 +111,14 @@ bool GizmoEntityItem::supportsDetailedIntersection() const { return _gizmoType == GizmoType::RING; } -#include - bool GizmoEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.z); - glm::quat rotation = glm::angleAxis((float)M_PI_2, Vectors::RIGHT) * getWorldOrientation(); + glm::quat rotation = getWorldOrientation(); + rotation = glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation; glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { @@ -150,7 +151,8 @@ bool GizmoEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, //// Scale the dimensions by the diameter glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.z); - glm::quat rotation = glm::angleAxis((float)M_PI_2, Vectors::RIGHT) * getWorldOrientation(); + glm::quat rotation = getWorldOrientation(); + rotation = glm::angleAxis(-(float)M_PI_2, rotation * Vectors::RIGHT) * rotation; glm::vec3 position = getWorldPosition(); glm::quat inverseRot = glm::inverse(rotation); diff --git a/libraries/entities/src/GrabPropertyGroup.h b/libraries/entities/src/GrabPropertyGroup.h index 33550fef3d..d76ac46a81 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -64,9 +64,9 @@ static const glm::vec3 INITIAL_EQUIPPABLE_INDICATOR_OFFSET { glm::vec3(0.0f) }; * @property {string} equippableIndicatorURL="" - If non-empty, this model will be used to indicate that an * entity is equippable, rather than the default. * @property {Vec3} equippableIndicatorScale=1,1,1 - If equippableIndicatorURL is non-empty, this controls the - scale of the displayed overlay. + scale of the displayed indicator. * @property {Vec3} equippableIndicatorOffset=0,0,0 - If equippableIndicatorURL is non-empty, this controls the - relative offset of the displayed overlay from the equippable entity. + relative offset of the displayed object from the equippable entity. */ diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 1e8e4511cf..837e824f4a 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -35,11 +35,11 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d withReadLock([&] { _pulseProperties.getProperties(properties); }); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); COPY_ENTITY_PROPERTY_TO_PROPERTIES(keepAspectRatio, getKeepAspectRatio); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(subImage, getSubImage); return properties; @@ -54,11 +54,11 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); somethingChanged |= pulsePropertiesChanged; }); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); SET_ENTITY_PROPERTY_FROM_PROPERTIES(keepAspectRatio, setKeepAspectRatio); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(subImage, setSubImage); if (somethingChanged) { @@ -91,11 +91,11 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); READ_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); - READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_SUB_IMAGE, QRect, setSubImage); return bytesRead; @@ -107,11 +107,11 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); + requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_IMAGE_URL; requestedProperties += PROP_EMISSIVE; requestedProperties += PROP_KEEP_ASPECT_RATIO; - requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_SUB_IMAGE; return requestedProperties; @@ -133,14 +133,24 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, getKeepAspectRatio()); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, getSubImage()); } +glm::vec3 ImageEntityItem::getRaycastDimensions() const { + glm::vec3 dimensions = getScaledDimensions(); + if (getBillboardMode() != BillboardMode::NONE) { + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + return glm::vec3(SQRT_2 * max); + } + return dimensions; +} + bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, @@ -149,6 +159,7 @@ bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { glm::vec3 forward = rotation * Vectors::FRONT; diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 05218016b3..a1be5a0554 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -43,6 +43,7 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + glm::vec3 getRaycastDimensions() const override; virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, @@ -77,15 +78,15 @@ public: PulsePropertyGroup getPulseProperties() const; protected: - QString _imageURL; - bool _emissive { false }; - bool _keepAspectRatio { true }; - BillboardMode _billboardMode; - QRect _subImage; - glm::u8vec3 _color; float _alpha; PulsePropertyGroup _pulseProperties; + BillboardMode _billboardMode; + + QString _imageURL; + bool _emissive { false }; + bool _keepAspectRatio { true }; + QRect _subImage; }; #endif // hifi_ImageEntityItem_h diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 12d1178690..acb5bc08f6 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -23,7 +23,6 @@ const int LineEntityItem::MAX_POINTS_PER_LINE = 70; - EntityItemPointer LineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new LineEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index cec602a5e1..1baa0b213a 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -310,7 +310,7 @@ void MaterialEntityItem::removeMaterial() { return; } - // Our parent could be an entity, an avatar, or an overlay + // Our parent could be an entity or an avatar if (EntityTree::removeMaterialFromEntity(parentID, material, getParentMaterialName().toStdString())) { return; } @@ -319,10 +319,6 @@ void MaterialEntityItem::removeMaterial() { return; } - if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName().toStdString())) { - return; - } - // if a remove fails, our parent is gone, so we don't need to retry } @@ -349,7 +345,7 @@ void MaterialEntityItem::applyMaterial() { graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, getPriority()); - // Our parent could be an entity, an avatar, or an overlay + // Our parent could be an entity or an avatar if (EntityTree::addMaterialToEntity(parentID, materialLayer, getParentMaterialName().toStdString())) { return; } @@ -358,10 +354,6 @@ void MaterialEntityItem::applyMaterial() { return; } - if (EntityTree::addMaterialToOverlay(parentID, materialLayer, getParentMaterialName().toStdString())) { - return; - } - // if we've reached this point, we couldn't find our parent, so we need to try again later _retryApply = true; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index ddbb028b6e..e365d0a7b6 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -43,14 +43,15 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( } const QString ModelEntityItem::getTextures() const { - QReadLocker locker(&_texturesLock); - auto textures = _textures; - return textures; + return resultWithReadLock([&] { + return _textures; + }); } void ModelEntityItem::setTextures(const QString& textures) { - QWriteLocker locker(&_texturesLock); - _textures = textures; + withWriteLock([&] { + _textures = textures; + }); } EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 8c9fbdc45f..649a6cb50f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -163,7 +163,6 @@ protected: AnimationPropertyGroup _animationProperties; - mutable QReadWriteLock _texturesLock; QString _textures; ShapeType _shapeType = SHAPE_TYPE_NONE; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index a743d0a7a9..bc98c61ff7 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -52,6 +52,7 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de withReadLock([&] { _pulseProperties.getProperties(properties); }); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(text, getText); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); @@ -59,7 +60,6 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(textAlpha, getTextAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundColor, getBackgroundColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundAlpha, getBackgroundAlpha); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(leftMargin, getLeftMargin); COPY_ENTITY_PROPERTY_TO_PROPERTIES(rightMargin, getRightMargin); COPY_ENTITY_PROPERTY_TO_PROPERTIES(topMargin, getTopMargin); @@ -75,6 +75,7 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); somethingChanged |= pulsePropertiesChanged; }); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(text, setText); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); @@ -82,7 +83,6 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(textAlpha, setTextAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundColor, setBackgroundColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundAlpha, setBackgroundAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(leftMargin, setLeftMargin); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rightMargin, setRightMargin); SET_ENTITY_PROPERTY_FROM_PROPERTIES(topMargin, setTopMargin); @@ -117,6 +117,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); @@ -124,7 +125,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXT_ALPHA, float, setTextAlpha); READ_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, glm::u8vec3, setBackgroundColor); READ_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, float, setBackgroundAlpha); - READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_LEFT_MARGIN, float, setLeftMargin); READ_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, float, setRightMargin); READ_ENTITY_PROPERTY(PROP_TOP_MARGIN, float, setTopMargin); @@ -137,13 +137,14 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += _pulseProperties.getEntityProperties(params); + requestedProperties += PROP_BILLBOARD_MODE; + requestedProperties += PROP_TEXT; requestedProperties += PROP_LINE_HEIGHT; requestedProperties += PROP_TEXT_COLOR; requestedProperties += PROP_TEXT_ALPHA; requestedProperties += PROP_BACKGROUND_COLOR; requestedProperties += PROP_BACKGROUND_ALPHA; - requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_LEFT_MARGIN; requestedProperties += PROP_RIGHT_MARGIN; requestedProperties += PROP_TOP_MARGIN; @@ -166,6 +167,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_TEXT, getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); @@ -173,12 +175,20 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_TEXT_ALPHA, getTextAlpha()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, getBackgroundColor()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, getBackgroundAlpha()); - APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_LEFT_MARGIN, getLeftMargin()); APPEND_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, getRightMargin()); APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, getTopMargin()); APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, getBottomMargin()); - +} + +glm::vec3 TextEntityItem::getRaycastDimensions() const { + glm::vec3 dimensions = getScaledDimensions(); + if (getBillboardMode() != BillboardMode::NONE) { + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + return glm::vec3(SQRT_2 * max); + } + return dimensions; } bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -189,6 +199,7 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { glm::vec3 forward = rotation * Vectors::FRONT; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 5ca6823052..1ead9d3e15 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -47,6 +47,7 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + glm::vec3 getRaycastDimensions() const override; virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, @@ -99,6 +100,8 @@ public: PulsePropertyGroup getPulseProperties() const; private: + BillboardMode _billboardMode; + QString _text; float _lineHeight; glm::u8vec3 _textColor; @@ -106,7 +109,6 @@ private: glm::u8vec3 _backgroundColor; float _backgroundAlpha; PulsePropertyGroup _pulseProperties; - BillboardMode _billboardMode; float _leftMargin; float _rightMargin; float _topMargin; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 459d512311..5a948fbfd4 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -48,12 +48,14 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des withReadLock([&] { _pulseProperties.getProperties(properties); }); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI); COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptURL, getScriptURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS); COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(showKeyboardFocusHighlight, getShowKeyboardFocusHighlight); return properties; } @@ -67,12 +69,14 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); somethingChanged |= pulsePropertiesChanged; }); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI); SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptURL, setScriptURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS); SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(showKeyboardFocusHighlight, setShowKeyboardFocusHighlight); if (somethingChanged) { bool wantDebug = false; @@ -105,12 +109,14 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i bytesRead += bytesFromPulse; dataAt += bytesFromPulse; }); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI); READ_ENTITY_PROPERTY(PROP_SCRIPT_URL, QString, setScriptURL); READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); + READ_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); return bytesRead; } @@ -120,12 +126,14 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); + requestedProperties += PROP_BILLBOARD_MODE; requestedProperties += PROP_SOURCE_URL; requestedProperties += PROP_DPI; requestedProperties += PROP_SCRIPT_URL; requestedProperties += PROP_MAX_FPS; requestedProperties += PROP_INPUT_MODE; + requestedProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; return requestedProperties; } @@ -144,12 +152,24 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, getScriptURL()); APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); + APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, getShowKeyboardFocusHighlight()); +} + +glm::vec3 WebEntityItem::getRaycastDimensions() const { + glm::vec3 dimensions = getScaledDimensions(); + if (getBillboardMode() != BillboardMode::NONE) { + float max = glm::max(dimensions.x, glm::max(dimensions.y, dimensions.z)); + const float SQRT_2 = 1.41421356237f; + return glm::vec3(SQRT_2 * max); + } + return dimensions; } bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -160,6 +180,7 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + rotation = EntityItem::getBillboardRotation(position, rotation, _billboardMode); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { glm::vec3 forward = rotation * Vectors::FRONT; @@ -230,17 +251,21 @@ float WebEntityItem::getAlpha() const { }); } +BillboardMode WebEntityItem::getBillboardMode() const { + return resultWithReadLock([&] { + return _billboardMode; + }); +} + +void WebEntityItem::setBillboardMode(BillboardMode value) { + withWriteLock([&] { + _billboardMode = value; + }); +} + void WebEntityItem::setSourceUrl(const QString& value) { withWriteLock([&] { - if (_sourceUrl != value) { - auto newURL = QUrl::fromUserInput(value); - - if (newURL.isValid()) { - _sourceUrl = newURL.toDisplayString(); - } else { - qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL."; - } - } + _sourceUrl = value; }); } @@ -270,7 +295,7 @@ void WebEntityItem::setScriptURL(const QString& value) { if (newURL.isValid()) { _scriptURL = newURL.toDisplayString(); } else { - qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL."; + qCDebug(entities) << "Not setting web entity script URL since" << value << "cannot be parsed to a valid URL."; } } }); @@ -306,6 +331,14 @@ WebInputMode WebEntityItem::getInputMode() const { }); } +void WebEntityItem::setShowKeyboardFocusHighlight(bool value) { + _showKeyboardFocusHighlight = value; +} + +bool WebEntityItem::getShowKeyboardFocusHighlight() const { + return _showKeyboardFocusHighlight; +} + PulsePropertyGroup WebEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index a0a2d65253..bb1e527712 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -44,6 +44,7 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + glm::vec3 getRaycastDimensions() const override; virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, @@ -60,6 +61,9 @@ public: float getAlpha() const; void setAlpha(float alpha); + void setBillboardMode(BillboardMode value); + BillboardMode getBillboardMode() const; + static const QString DEFAULT_SOURCE_URL; void setSourceUrl(const QString& value); QString getSourceUrl() const; @@ -77,18 +81,23 @@ public: void setInputMode(const WebInputMode& value); WebInputMode getInputMode() const; + bool getShowKeyboardFocusHighlight() const; + void setShowKeyboardFocusHighlight(bool value); + PulsePropertyGroup getPulseProperties() const; protected: glm::u8vec3 _color; float _alpha { 1.0f }; PulsePropertyGroup _pulseProperties; + BillboardMode _billboardMode; QString _sourceUrl; uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; WebInputMode _inputMode; + bool _showKeyboardFocusHighlight; }; #endif // hifi_WebEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 7f7f6170d4..7b0491dbc0 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -119,7 +119,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || - _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; + _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; return somethingChanged; } @@ -394,7 +394,6 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; _bloomPropertiesChanged = false; - _stagePropertiesChanged = false; }); } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 813115add9..11c85dab89 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,8 +102,6 @@ public: bool hazePropertiesChanged() const { return _hazePropertiesChanged; } bool bloomPropertiesChanged() const { return _bloomPropertiesChanged; } - bool stagePropertiesChanged() const { return _stagePropertiesChanged; } - void resetRenderingPropertiesChanged(); virtual bool supportsDetailedIntersection() const override { return true; } @@ -155,7 +153,6 @@ protected: bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _bloomPropertiesChanged { false }; - bool _stagePropertiesChanged { false }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 38465d2809..207ee2982d 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -384,43 +384,6 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } -QMap getJointNameMapping(const QVariantHash& mapping) { - static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; - QMap hfmToHifiJointNameMap; - if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { - auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); - for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) { - hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString()); - qCDebug(modelformat) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()]; - } - } - return hfmToHifiJointNameMap; -} - -QMap getJointRotationOffsets(const QVariantHash& mapping) { - QMap jointRotationOffsets; - static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; - if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { - auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); - for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { - QString jointName = itr.key(); - QString line = itr.value().toString(); - auto quatCoords = line.split(','); - if (quatCoords.size() == 4) { - float quatX = quatCoords[0].mid(1).toFloat(); - float quatY = quatCoords[1].toFloat(); - float quatZ = quatCoords[2].toFloat(); - float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); - if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { - glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); - jointRotationOffsets.insert(jointName, rotationOffset); - } - } - } - } - return jointRotationOffsets; -} - HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap meshes; @@ -444,8 +407,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr std::map lights; - QVariantHash joints = mapping.value("joint").toHash(); - QVariantHash blendshapeMappings = mapping.value("bs").toHash(); QMultiHash blendshapeIndices; @@ -473,8 +434,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr HFMModel& hfmModel = *hfmModelPtr; hfmModel.originalURL = url; - hfmModel.hfmToHifiJointNameMapping.clear(); - hfmModel.hfmToHifiJointNameMapping = getJointNameMapping(mapping); float unitScaleFactor = 1.0f; glm::vec3 ambientColor; @@ -1287,26 +1246,14 @@ 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; - joint.isFree = freeJoints.contains(fbxModel.name); joint.parentIndex = fbxModel.parentIndex; - - // get the indices of all ancestors starting with the first free one (if any) int jointIndex = hfmModel.joints.size(); - joint.freeLineage.append(jointIndex); - int lastFreeIndex = joint.isFree ? 0 : -1; - for (int index = joint.parentIndex; index != -1; index = hfmModel.joints.at(index).parentIndex) { - if (hfmModel.joints.at(index).isFree) { - lastFreeIndex = joint.freeLineage.size(); - } - joint.freeLineage.append(index); - } - joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); + joint.translation = fbxModel.translation; // these are usually in centimeters joint.preTransform = fbxModel.preTransform; joint.preRotation = fbxModel.preRotation; @@ -1341,14 +1288,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = fbxModel.name; - if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) { - joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); - } joint.bindTransformFoundInCluster = false; hfmModel.joints.append(joint); - hfmModel.jointIndices.insert(joint.name, hfmModel.joints.size()); QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); @@ -1704,7 +1647,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr generateBoundryLinesForDop14(joint.shapeInfo.dots, joint.shapeInfo.avgPoint, joint.shapeInfo.debugLines); } } - hfmModel.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); // attempt to map any meshes to a named model for (QHash::const_iterator m = meshIDsToMeshIndices.constBegin(); @@ -1722,21 +1664,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - auto offsets = getJointRotationOffsets(mapping); - hfmModel.jointRotationOffsets.clear(); - for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { - QString jointName = itr.key(); - glm::quat rotationOffset = itr.value(); - int jointIndex = hfmModel.getJointIndex(jointName); - if (hfmModel.hfmToHifiJointNameMapping.contains(jointName)) { - jointIndex = hfmModel.getJointIndex(jointName); - } - if (jointIndex != -1) { - hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset); - } - qCDebug(modelformat) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; - } - return hfmModelPtr; } diff --git a/libraries/fbx/src/FBXSerializer_Material.cpp b/libraries/fbx/src/FBXSerializer_Material.cpp index 0a1d15b72a..9caf713e75 100644 --- a/libraries/fbx/src/FBXSerializer_Material.cpp +++ b/libraries/fbx/src/FBXSerializer_Material.cpp @@ -76,13 +76,12 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID, const QString& ma } void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) { - - QString materialMapString = mapping.value("materialMap").toString(); - QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8()); - QJsonObject materialMap = materialMapDocument.object(); - if (!materialMapString.isEmpty()) { - if (materialMapDocument.isEmpty() || materialMap.isEmpty()) { - qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapString; + QJsonObject materialMap; + if (mapping.contains("materialMap")) { + QByteArray materialMapValue = mapping.value("materialMap").toByteArray(); + materialMap = QJsonDocument::fromJson(materialMapValue).object(); + if (materialMap.isEmpty()) { + qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapValue; } } for (QHash::iterator it = _hfmMaterials.begin(); it != _hfmMaterials.end(); it++) { diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp index 7828037c74..b6f109c217 100644 --- a/libraries/fbx/src/FST.cpp +++ b/libraries/fbx/src/FST.cpp @@ -82,11 +82,6 @@ FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePat } mapping.insert(JOINT_INDEX_FIELD, jointIndices); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); - // If there are no blendshape mappings, and we detect that this is likely a mixamo file, // then we can add the default mixamo to "faceshift" mappings diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 43806560dc..41b660f722 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -84,15 +84,15 @@ void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD - << MARKETPLACE_ID_FIELD << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << MARKETPLACE_ID_FIELD << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); - + for (auto key : PREFERED_ORDER) { auto it = mapping.find(key); if (it != mapping.constEnd()) { - if (key == FREE_JOINT_FIELD || key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. + if (key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. for (auto multi : mapping.values(key)) { buffer.write(key.toUtf8()); buffer.write(" = "); @@ -104,7 +104,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { } } } - + for (auto it = mapping.constBegin(); it != mapping.constEnd(); it++) { if (!PREFERED_ORDER.contains(it.key())) { writeVariant(buffer, it); diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 993d7c3148..ad952c4ed7 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -27,7 +27,6 @@ static const QString TRANSLATION_X_FIELD = "tx"; static const QString TRANSLATION_Y_FIELD = "ty"; static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; -static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString BLENDSHAPE_FIELD = "bs"; static const QString SCRIPT_FIELD = "script"; static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp old mode 100644 new mode 100755 index 96c236f703..82a4361723 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "FBXSerializer.h" @@ -124,6 +125,31 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString& return _defined; } +QByteArray GLTFSerializer::setGLBChunks(const QByteArray& data) { + int byte = 4; + int jsonStart = data.indexOf("JSON", Qt::CaseSensitive); + int binStart = data.indexOf("BIN", Qt::CaseSensitive); + int jsonLength, binLength; + QByteArray jsonLengthChunk, binLengthChunk; + + jsonLengthChunk = data.mid(jsonStart - byte, byte); + QDataStream tempJsonLen(jsonLengthChunk); + tempJsonLen.setByteOrder(QDataStream::LittleEndian); + tempJsonLen >> jsonLength; + QByteArray jsonChunk = data.mid(jsonStart + byte, jsonLength); + + if (binStart != -1) { + binLengthChunk = data.mid(binStart - byte, byte); + + QDataStream tempBinLen(binLengthChunk); + tempBinLen.setByteOrder(QDataStream::LittleEndian); + tempBinLen >> binLength; + + _glbBinary = data.mid(binStart + byte, binLength); + } + return jsonChunk; +} + int GLTFSerializer::getMeshPrimitiveRenderingMode(const QString& type) { if (type == "POINTS") { @@ -309,6 +335,14 @@ bool GLTFSerializer::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); + + if (_url.toString().endsWith("glb")) { + if (!_glbBinary.isEmpty()) { + buffer.blob = _glbBinary; + } else { + return false; + } + } if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { if (!readBinary(buffer.uri, buffer.blob)) { return false; @@ -352,9 +386,14 @@ bool GLTFSerializer::addImage(const QJsonObject& object) { QString mime; getStringVal(object, "uri", image.uri, image.defined); + if (image.uri.contains("data:image/png;base64,")) { + image.mimeType = getImageMimeType("image/png"); + } else if (image.uri.contains("data:image/jpeg;base64,")) { + image.mimeType = getImageMimeType("image/jpeg"); + } if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); - } + } getIntVal(object, "bufferView", image.bufferView, image.defined); _file.images.push_back(image); @@ -530,9 +569,16 @@ bool GLTFSerializer::addTexture(const QJsonObject& object) { bool GLTFSerializer::parseGLTF(const QByteArray& data) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - - QJsonDocument d = QJsonDocument::fromJson(data); + + QByteArray jsonChunk = data; + + if (_url.toString().endsWith("glb") && data.indexOf("glTF") == 0 && data.contains("JSON")) { + jsonChunk = setGLBChunks(data); + } + + QJsonDocument d = QJsonDocument::fromJson(jsonChunk); QJsonObject jsFile = d.object(); + bool success = setAsset(jsFile); if (success) { QJsonArray accessors; @@ -719,7 +765,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { //Build default joints hfmModel.joints.resize(1); - hfmModel.joints[0].isFree = false; hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; hfmModel.joints[0].translation = glm::vec3(0, 0, 0); @@ -831,6 +876,22 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { for (int n = 0; n < normals.size(); n = n + 3) { mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); } + } else if (key == "COLOR_0") { + QVector colors; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + colors, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url; + continue; + } + int stride = (accessor.type == GLTFAccessorType::VEC4) ? 4 : 3; + for (int n = 0; n < colors.size() - 3; n += stride) { + mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2])); + } } else if (key == "TEXCOORD_0") { QVector texcoords; success = addArrayOfType(buffer.blob, @@ -904,6 +965,10 @@ MediaType GLTFSerializer::getMediaType() const { MediaType mediaType("gltf"); mediaType.extensions.push_back("gltf"); mediaType.webMediaTypes.push_back("model/gltf+json"); + + mediaType.extensions.push_back("glb"); + mediaType.webMediaTypes.push_back("model/gltf-binary"); + return mediaType; } @@ -912,9 +977,9 @@ std::unique_ptr GLTFSerializer::getFactory() const { } HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) { - - _url = url; + _url = url; + // Normalize url for local files QUrl normalizeUrl = DependencyManager::get()->normalizeURL(_url); if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { @@ -926,7 +991,6 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas //_file.dump(); auto hfmModelPtr = std::make_shared(); HFMModel& hfmModel = *hfmModelPtr; - buildGeometry(hfmModel, _url); //hfmDebugDump(data); @@ -939,10 +1003,15 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas } bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { - QUrl binaryUrl = _url.resolved(url); - bool success; - std::tie(success, outdata) = requestData(binaryUrl); + + if (url.contains("data:application/octet-stream;base64,")) { + outdata = requestEmbeddedData(url); + success = !outdata.isEmpty(); + } else { + QUrl binaryUrl = _url.resolved(url); + std::tie(success, outdata) = requestData(binaryUrl); + } return success; } @@ -975,6 +1044,11 @@ std::tuple GLTFSerializer::requestData(QUrl& url) { } } +QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) { + QString binaryUrl = url.split(",")[1]; + return binaryUrl.isEmpty() ? QByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8()); +} + QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) { if (!qApp) { @@ -1003,14 +1077,30 @@ QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) { HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) { HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; + QString fname = QUrl(url).fileName(); QUrl textureUrl = _url.resolved(url); qCDebug(modelformat) << "fname: " << fname; fbxtex.name = fname; fbxtex.filename = textureUrl.toEncoded(); + + if (_url.toString().endsWith("glb") && !_glbBinary.isEmpty()) { + int bufferView = _file.images[texture.source].bufferView; + + GLTFBufferView& imagesBufferview = _file.bufferviews[bufferView]; + int offset = imagesBufferview.byteOffset; + int length = imagesBufferview.byteLength; + + fbxtex.content = _glbBinary.mid(offset, length); + fbxtex.filename = textureUrl.toEncoded().append(texture.source); + } + + if (url.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) { + fbxtex.content = requestEmbeddedData(url); + } } return fbxtex; } @@ -1057,8 +1147,10 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat } if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { fbxmat.roughnessTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.roughnessTexture.sourceChannel = image::ColorChannel::GREEN; fbxmat.useRoughnessMap = true; fbxmat.metallicTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.metallicTexture.sourceChannel = image::ColorChannel::BLUE; fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { @@ -1187,8 +1279,6 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; qCDebug(modelformat) << " offset =" << hfmModel.offset; - qCDebug(modelformat) << " palmDirection = " << hfmModel.palmDirection; - qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; qCDebug(modelformat) << " bindExtents.size() = " << hfmModel.bindExtents.size(); @@ -1301,8 +1391,6 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; - qCDebug(modelformat) << " isFree =" << joint.isFree; - qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; qCDebug(modelformat) << " translation" << joint.translation; diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h old mode 100644 new mode 100755 index 5fca77c4fd..a361e09fa6 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -709,6 +709,7 @@ public: private: GLTFFile _file; QUrl _url; + QByteArray _glbBinary; glm::mat4 getModelTransform(const GLTFNode& node); @@ -731,6 +732,8 @@ private: QVector& values, QMap& defined); bool getObjectArrayVal(const QJsonObject& object, const QString& fieldname, QJsonArray& objects, QMap& defined); + + QByteArray setGLBChunks(const QByteArray& data); int getMaterialAlphaMode(const QString& type); int getAccessorType(const QString& type); @@ -772,6 +775,8 @@ private: QVector& out_vertices, QVector& out_normals); std::tuple requestData(QUrl& url); + QByteArray requestEmbeddedData(const QString& url); + QNetworkReply* request(QUrl& url, bool isTest); bool doesResourceExist(const QString& url); diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index 9d4b1f16a1..91d3fc7cc0 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -687,7 +687,6 @@ HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash mesh.meshIndex = 0; hfmModel.joints.resize(1); - hfmModel.joints[0].isFree = false; hfmModel.joints[0].parentIndex = -1; hfmModel.joints[0].distanceToParent = 0; hfmModel.joints[0].translation = glm::vec3(0, 0, 0); @@ -1048,8 +1047,7 @@ void hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); foreach (HFMJoint joint, hfmModel.joints) { - qCDebug(modelformat) << " isFree =" << joint.isFree; - qCDebug(modelformat) << " freeLineage" << joint.freeLineage; + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; qCDebug(modelformat) << " translation" << joint.translation; diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index 925cf9b288..855452e8f0 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gl) -setup_hifi_library(Gui Widgets Qml Quick) +setup_hifi_library(Gui Widgets) link_hifi_libraries(shared) target_opengl() diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 7d27b42909..a0d52ee223 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -195,6 +195,21 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +#if defined(GL_CUSTOM_CONTEXT) +bool Context::makeCurrent() { + BOOL result = wglMakeCurrent(_hdc, _hglrc); + assert(result); + updateSwapchainMemoryCounter(); + return result; +} + void Context::swapBuffers() { + SwapBuffers(_hdc); +} + void Context::doneCurrent() { + wglMakeCurrent(0, 0); +} +#endif + void Context::create(QOpenGLContext* shareContext) { if (!shareContext) { shareContext = qt_gl_global_share_context(); @@ -297,7 +312,11 @@ void Context::create(QOpenGLContext* shareContext) { contextAttribs.push_back(0); } contextAttribs.push_back(0); - HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext); + HGLRC shareHglrc = nullptr; + if (shareContext) { + auto nativeContextPointer = QOpenGLContextWrapper(shareContext).getNativeContext(); + shareHglrc = (HGLRC)nativeContextPointer->context(); + } _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index 82724dfd62..24ae29e4ca 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -61,12 +61,12 @@ void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { switch (severity) { case QOpenGLDebugMessage::NotificationSeverity: case QOpenGLDebugMessage::LowSeverity: + qCDebug(glLogging) << debugMessage; return; default: + qCWarning(glLogging) << debugMessage; break; } - qWarning(glLogging) << debugMessage; - return; } void Context::setupDebugLogging(QOpenGLContext *context) { @@ -82,6 +82,8 @@ void Context::setupDebugLogging(QOpenGLContext *context) { } } + +#if !defined(GL_CUSTOM_CONTEXT) bool Context::makeCurrent() { updateSwapchainMemoryCounter(); bool result = _qglContext->makeCurrent(_window); @@ -98,6 +100,7 @@ void Context::doneCurrent() { _qglContext->doneCurrent(); } } +#endif Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index f05acb50e9..69b41da821 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -65,21 +65,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); - - // Due to a https://bugreports.qt.io/browse/QTBUG-65125 we can't rely on `isValid` - // to determine if the offscreen surface was successfully created, so we use - // makeCurrent as a proxy test. Bug is fixed in Qt 5.9.4 -#if defined(Q_OS_ANDROID) - if (!_context->makeCurrent(_offscreenSurface)) { - qFatal("Unable to make offscreen surface current"); - } - _context->doneCurrent(); -#else if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); } -#endif - return true; } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index fbebb1128d..842c7abd08 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -17,6 +17,22 @@ #include #endif +QOpenGLContextWrapper::Pointer QOpenGLContextWrapper::currentContextWrapper() { + return std::make_shared(QOpenGLContext::currentContext()); +} + + +QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeContext() const { + QOpenGLContextWrapper::NativeContextPointer result; + auto nativeHandle = _context->nativeHandle(); + if (nativeHandle.canConvert()) { + result = std::make_shared(); + *result = nativeHandle.value(); + } + return result; +} + + uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { @@ -49,19 +65,6 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { _context->setFormat(format); } -#ifdef Q_OS_WIN -void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) { - HGLRC result = 0; - if (context != nullptr) { - auto nativeHandle = context->nativeHandle(); - if (nativeHandle.canConvert()) { - result = nativeHandle.value().context(); - } - } - return result; -} -#endif - bool QOpenGLContextWrapper::create() { return _context->create(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index 32ba7f22e8..1fade3e7fa 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -12,19 +12,31 @@ #ifndef hifi_QOpenGLContextWrapper_h #define hifi_QOpenGLContextWrapper_h -#include #include +#include class QOpenGLContext; class QSurface; class QSurfaceFormat; class QThread; +#if defined(Q_OS_ANDROID) +#include +#include +using QGLNativeContext = QEGLNativeContext; +#elif defined(Q_OS_WIN) +class QWGLNativeContext; +using QGLNativeContext = QWGLNativeContext; +#else +using QGLNativeContext = void*; +#endif + class QOpenGLContextWrapper { public: -#ifdef Q_OS_WIN - static void* nativeContext(QOpenGLContext* context); -#endif + using Pointer = std::shared_ptr; + using NativeContextPointer = std::shared_ptr; + static Pointer currentContextWrapper(); + QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); @@ -37,6 +49,8 @@ public: void setShareContext(QOpenGLContext* otherContext); void moveToThread(QThread* thread); + NativeContextPointer getNativeContext() const; + static QOpenGLContext* currentContext(); static uint32_t currentContextVersion(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 82f4f97e3b..fef458f536 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -418,6 +418,10 @@ public: #endif }; +#if defined(GPU_STEREO_DRAWCALL_INSTANCED) && !defined(GL_CLIP_DISTANCE0) +#define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT +#endif + #define GL_PROFILE_RANGE(category, name) \ PROFILE_RANGE(category, name); \ GlDuration glProfileRangeThis(name); @@ -426,6 +430,9 @@ void GLBackend::render(const Batch& batch) { GL_PROFILE_RANGE(render_gpu_gl, batch.getName().c_str()); _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); + // FIXME move this to between the transfer and draw passes, so that + // framebuffer setup can see the proper stereo state and enable things + // like foveation // Allow the batch to override the rendering stereo settings // for things like full framebuffer copy operations (deferred lighting passes) bool savedStereo = _stereo._enable; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index b5a279a54c..ffd0466b79 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -32,7 +32,7 @@ // Different versions for the stereo drawcall // Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only -#if defined(USE_GLES) +#if defined(USE_GLES) && !defined(HAVE_EXT_clip_cull_distance) #define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE #else //#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER @@ -95,7 +95,7 @@ public: // Shutdown rendering and persist any required resources void shutdown() override; - void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false); + void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) override; void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp index 61423bf970..7f61ca78f6 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp @@ -24,6 +24,17 @@ const uint32_t MAX_RANGE_QUERY_DEPTH = 10000; static bool timeElapsed = false; #endif +#if defined(USE_GLES) +static bool hasTimerExtension() { + static std::once_flag once; + static bool result = false; + std::call_once(once, [&] { + result = glGetQueryObjectui64vEXT != nullptr; + }); + return result; +} +#endif + void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); @@ -33,7 +44,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { ++_queryStage._rangeQueryDepth; glquery->_batchElapsedTimeBegin = std::chrono::high_resolution_clock::now(); -#if !defined(USE_GLES) +#if defined(USE_GLES) + if (hasTimerExtension()) { + glQueryCounterEXT(glquery->_beginqo, GL_TIMESTAMP_EXT); + } +#else if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo); @@ -52,7 +67,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { -#if !defined(USE_GLES) +#if defined(USE_GLES) + if (hasTimerExtension()) { + glQueryCounterEXT(glquery->_endqo, GL_TIMESTAMP_EXT); + } +#else if (timeElapsed) { if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) { glEndQuery(GL_TIME_ELAPSED); @@ -79,7 +98,21 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { if (glquery->_rangeQueryDepth > MAX_RANGE_QUERY_DEPTH) { query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } else { -#if !defined(USE_GLES) +#if defined(USE_GLES) + glquery->_result = 0; + if (hasTimerExtension()) { + glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); + if (glquery->_result == GL_TRUE) { + GLuint64 start, end; + glGetQueryObjectui64vEXT(glquery->_beginqo, GL_QUERY_RESULT, &start); + glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT, &end); + glquery->_result = end - start; + query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); + } + } else { + query->triggerReturnHandler(0, glquery->_batchElapsedTime); + } +#else glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); if (glquery->_result == GL_TRUE) { if (timeElapsed) { @@ -92,9 +125,6 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { } query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } -#else - // gles3 is not supporting true time query returns just the batch elapsed time - query->triggerReturnHandler(0, glquery->_batchElapsedTime); #endif (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp index e9494a1271..082edd47bc 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp @@ -60,9 +60,17 @@ GLenum GLTexture::getGLTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: if (!texture.isArray()) { - return GL_TEXTURE_2D; + if (!texture.isMultisample()) { + return GL_TEXTURE_2D; + } else { + return GL_TEXTURE_2D_MULTISAMPLE; + } } else { - return GL_TEXTURE_2D_ARRAY; + if (!texture.isMultisample()) { + return GL_TEXTURE_2D_ARRAY; + } else { + return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + } } break; @@ -81,7 +89,9 @@ GLenum GLTexture::getGLTextureType(const Texture& texture) { uint8_t GLTexture::getFaceCount(GLenum target) { switch (target) { case GL_TEXTURE_2D: + case GL_TEXTURE_2D_MULTISAMPLE: case GL_TEXTURE_2D_ARRAY: + case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: return TEXTURE_2D_NUM_FACES; case GL_TEXTURE_CUBE_MAP: return TEXTURE_CUBE_NUM_FACES; @@ -96,15 +106,20 @@ const std::vector& GLTexture::getFaceTargets(GLenum target) { GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; - static const std::vector faceTargets { + static const std::vector face2DTargets { GL_TEXTURE_2D }; - static const std::vector arrayFaceTargets{ + static const std::vector face2DMSTargets{ + GL_TEXTURE_2D_MULTISAMPLE + }; + static const std::vector arrayFaceTargets{ GL_TEXTURE_2D_ARRAY }; switch (target) { case GL_TEXTURE_2D: - return faceTargets; + return face2DTargets; + case GL_TEXTURE_2D_MULTISAMPLE: + return face2DMSTargets; case GL_TEXTURE_2D_ARRAY: return arrayFaceTargets; case GL_TEXTURE_CUBE_MAP: @@ -114,7 +129,7 @@ const std::vector& GLTexture::getFaceTargets(GLenum target) { break; } Q_UNREACHABLE(); - return faceTargets; + return face2DTargets; } GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) : diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp index 1d512103bd..0f50b0724e 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -66,6 +66,8 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + } else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D_MULTISAMPLE, gltexture->_texture, 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0, b._subresource); @@ -98,6 +100,8 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D_MULTISAMPLE, gltexture->_texture, 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, attachement, gltexture->_texture, 0, _gpuObject.getDepthStencilBufferSubresource()); diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 4068865274..f47211555a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -216,19 +216,29 @@ void GL41FixedAllocationTexture::allocateStorage() const { const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const auto numMips = _gpuObject.getNumMips(); const auto numSlices = _gpuObject.getNumSlices(); + const auto numSamples = _gpuObject.getNumSamples(); // glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); - for (GLint level = 0; level < numMips; level++) { - Vec3u dimensions = _gpuObject.evalMipDimensions(level); - for (GLenum target : getFaceTargets(_target)) { - if (!_gpuObject.isArray()) { - glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, - texelFormat.type, nullptr); - } else { - glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, - texelFormat.format, texelFormat.type, nullptr); + if (!_gpuObject.isMultisample()) { + for (GLint level = 0; level < numMips; level++) { + Vec3u dimensions = _gpuObject.evalMipDimensions(level); + for (GLenum target : getFaceTargets(_target)) { + if (!_gpuObject.isArray()) { + glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, + texelFormat.type, nullptr); + } else { + glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, + texelFormat.format, texelFormat.type, nullptr); + } } } + } else { + const auto dimensions = _gpuObject.getDimensions(); + if (!_gpuObject.isArray()) { + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, numSamples, texelFormat.internalFormat, dimensions.x, dimensions.y, GL_FALSE); + } else { + glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, numSamples, texelFormat.internalFormat, dimensions.x, dimensions.y, dimensions.z, GL_FALSE); + } } glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp index 86332558e3..7a299e792b 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp @@ -62,6 +62,8 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); + } else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); } else { glNamedFramebufferTextureLayer(_id, colorAttachments[unit], gltexture->_texture, 0, b._subresource); } @@ -93,6 +95,9 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0); + } + else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0); } else { glNamedFramebufferTextureLayer(_id, attachement, gltexture->_texture, 0, _gpuObject.getDepthStencilBufferSubresource()); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index e02f12819e..4aff76df21 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -24,6 +24,10 @@ #include #include +static const QString FORCE_MOBILE_TEXTURES_STRING{ "HIFI_FORCE_MOBILE_TEXTURES" }; +static bool FORCE_MOBILE_TEXTURES = QProcessEnvironment::systemEnvironment().contains(FORCE_MOBILE_TEXTURES_STRING); + + using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; @@ -45,9 +49,10 @@ bool GL45Backend::supportedTextureFormat(const gpu::Element& format) { case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED: case gpu::Semantic::COMPRESSED_EAC_XY: case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED: - return false; + return FORCE_MOBILE_TEXTURES; + default: - return true; + return FORCE_MOBILE_TEXTURES ? !format.isCompressed() : true; } } @@ -375,11 +380,22 @@ void GL45FixedAllocationTexture::allocateStorage() const { const auto dimensions = _gpuObject.getDimensions(); const auto mips = _gpuObject.getNumMips(); const auto numSlices = _gpuObject.getNumSlices(); + const auto numSamples = _gpuObject.getNumSamples(); - if (!_gpuObject.isArray()) { - glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + + if (!_gpuObject.isMultisample()) { + if (!_gpuObject.isArray()) { + glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + } else { + glTextureStorage3D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices); + } } else { - glTextureStorage3D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices); + if (!_gpuObject.isArray()) { + glTextureStorage2DMultisample(_id, numSamples, texelFormat.internalFormat, dimensions.x, dimensions.y, GL_FALSE); + } + else { + glTextureStorage3DMultisample(_id, numSamples, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, GL_FALSE); + } } glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index aaa1be5892..636518c85a 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -48,6 +48,7 @@ public: class GLESTexture : public GLTexture { using Parent = GLTexture; friend class GLESBackend; + friend class GLESFramebuffer; GLuint allocate(const Texture& texture); protected: GLESTexture(const std::weak_ptr& backend, const Texture& buffer); diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index 9c3a83ce13..36b37083cb 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -17,6 +17,34 @@ namespace gpu { namespace gles { + +// returns the FOV from the projection matrix +static inline vec4 extractFov( const glm::mat4& m) { + static const std::array CLIPS{ { + { 1, 0, 0, 1 }, + { -1, 0, 0, 1 }, + { 0, 1, 0, 1 }, + { 0, -1, 0, 1 } + } }; + + glm::mat4 mt = glm::transpose(m); + vec4 v, result; + // Left + v = mt * CLIPS[0]; + result.x = -atanf(v.z / v.x); + // Right + v = mt * CLIPS[1]; + result.y = atanf(v.z / v.x); + // Down + v = mt * CLIPS[2]; + result.z = -atanf(v.z / v.y); + // Up + v = mt * CLIPS[3]; + result.w = atanf(v.z / v.y); + return result; +} + + class GLESFramebuffer : public gl::GLFramebuffer { using Parent = gl::GLFramebuffer; static GLuint allocate() { @@ -29,6 +57,24 @@ public: GLint currentFBO = -1; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + + vec2 focalPoint{ -1.0f }; + +#if 0 + { + auto backend = _backend.lock(); + if (backend && backend->isStereo()) { + glm::mat4 projections[2]; + backend->getStereoProjections(projections); + vec4 fov = extractFov(projections[0]); + float fovwidth = fov.x + fov.y; + float fovheight = fov.z + fov.w; + focalPoint.x = fov.y / fovwidth; + focalPoint.y = (fov.z / fovheight) - 0.5f; + } + } +#endif + gl::GLTexture* gltexture = nullptr; TexturePointer surface; if (_gpuObject.getColorStamps() != _colorStamps) { @@ -58,7 +104,7 @@ public: surface = b._texture; if (surface) { Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); - gltexture = backend->syncGPUObject(surface); + gltexture = backend->syncGPUObject(surface); } else { gltexture = nullptr; } @@ -66,6 +112,26 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); +#if 0 + if (glTextureFoveationParametersQCOM && focalPoint.x != -1.0f) { + static GLint FOVEATION_QUERY = 0; + static std::once_flag once; + std::call_once(once, [&]{ + glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_QUERY_QCOM, &FOVEATION_QUERY); + }); + static const float foveaArea = 4.0f; + static const float gain = 16.0f; + GLESBackend::GLESTexture* glestexture = static_cast(gltexture); + glestexture->withPreservedTexture([=]{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM, GL_FOVEATION_ENABLE_BIT_QCOM | GL_FOVEATION_SCALED_BIN_METHOD_BIT_QCOM); + glTextureFoveationParametersQCOM(_id, 0, 0, -focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea); + glTextureFoveationParametersQCOM(_id, 0, 1, focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea); + }); + + } +#endif + } else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D_MULTISAMPLE, gltexture->_texture, 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0, b._subresource); @@ -98,6 +164,8 @@ public: if (gltexture) { if (gltexture->_target == GL_TEXTURE_2D) { glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else if (gltexture->_target == GL_TEXTURE_2D_MULTISAMPLE) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D_MULTISAMPLE, gltexture->_texture, 0); } else { glFramebufferTextureLayer(GL_FRAMEBUFFER, attachement, gltexture->_texture, 0, _gpuObject.getDepthStencilBufferSubresource()); diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index 23dc271af9..4b2d4d09e6 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -272,28 +272,40 @@ void GLESFixedAllocationTexture::allocateStorage() const { const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const auto numMips = _gpuObject.getNumMips(); const auto numSlices = _gpuObject.getNumSlices(); + const auto numSamples = _gpuObject.getNumSamples(); // glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); - for (GLint level = 0; level < numMips; level++) { - Vec3u dimensions = _gpuObject.evalMipDimensions(level); - for (GLenum target : getFaceTargets(_target)) { - if (texelFormat.isCompressed()) { - auto size = getCompressedImageSize(dimensions.x, dimensions.y, texelFormat.internalFormat); - if (!_gpuObject.isArray()) { - glCompressedTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, size, nullptr); + if (!_gpuObject.isMultisample()) { + for (GLint level = 0; level < numMips; level++) { + Vec3u dimensions = _gpuObject.evalMipDimensions(level); + for (GLenum target : getFaceTargets(_target)) { + if (texelFormat.isCompressed()) { + auto size = getCompressedImageSize(dimensions.x, dimensions.y, texelFormat.internalFormat); + if (!_gpuObject.isArray()) { + glCompressedTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, size, nullptr); + } else { + glCompressedTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, size * numSlices, nullptr); + } } else { - glCompressedTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, size * numSlices, nullptr); - } - } else { - if (!_gpuObject.isArray()) { - glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, - texelFormat.type, nullptr); - } else { - glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, - texelFormat.format, texelFormat.type, nullptr); + if (!_gpuObject.isArray()) { + glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, + texelFormat.type, nullptr); + } else { + glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, + texelFormat.format, texelFormat.type, nullptr); + } } } } + } else { + const auto dimensions = _gpuObject.getDimensions(); + if (!_gpuObject.isArray()) { + glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, numSamples, + texelFormat.internalFormat, dimensions.x, dimensions.y, + GL_FALSE); + } else { + // NOT SUPPORTED (yet) + } } glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index b080b0ceac..7109b3dfeb 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -66,6 +66,7 @@ public: virtual void syncProgram(const gpu::ShaderPointer& program) = 0; virtual void recycle() const = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; + virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) {} virtual bool supportedTextureFormat(const gpu::Element& format) = 0; @@ -117,7 +118,6 @@ public: static ContextMetricSize textureResourcePopulatedGPUMemSize; static ContextMetricSize textureResourceIdealGPUMemSize; -protected: virtual bool isStereo() const { return _stereo.isStereo(); } @@ -127,6 +127,7 @@ protected: eyeProjections[i] = _stereo._eyeProjections[i]; } } +protected: void getStereoViews(mat4* eyeViews) const { for (int i = 0; i < 2; ++i) { diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index c63eaf51c3..6e39a38097 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -34,11 +34,26 @@ public: return filename; } + static std::string getBaseDir(const std::string& filename) { + std::string result; + if (0 == filename.find("assets:")) { + auto lastSlash = filename.rfind('/'); + result = filename.substr(0, lastSlash + 1); + } else { + std::string result = QFileInfo(filename.c_str()).absoluteDir().canonicalPath().toStdString(); + if (*result.rbegin() != '/') { + result += '/'; + } + } + return result; + } + Deserializer(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) : - basename(getBaseName(filename)), externalTexture(externalTexture), textureLoader(loader) {} + basename(getBaseName(filename)), basedir(getBaseDir(filename)), externalTexture(externalTexture), textureLoader(loader) { + } const std::string basename; - std::string basedir; + const std::string basedir; std::string binaryFile; const uint32_t externalTexture; TextureLoader textureLoader; @@ -302,6 +317,21 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { return nullptr; } + std::string source; + readOptional(source, node, keys::source); + + std::string ktxFile; + readOptional(ktxFile, node, keys::ktxFile); + Element ktxTexelFormat, ktxMipFormat; + if (!ktxFile.empty()) { + if (QFileInfo(ktxFile.c_str()).isRelative()) { + ktxFile = basedir + ktxFile; + } + ktx::StoragePointer ktxStorage{ new storage::FileStorage(ktxFile.c_str()) }; + auto ktxObject = ktx::KTX::create(ktxStorage); + Texture::evalTextureFormat(ktxObject->getHeader(), ktxTexelFormat, ktxMipFormat); + } + TextureUsageType usageType = node[keys::usageType]; Texture::Type type = node[keys::type]; glm::u16vec4 dims; @@ -312,6 +342,9 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { uint16 mips = node[keys::mips]; uint16 samples = node[keys::samples]; Element texelFormat = readElement(node[keys::texelFormat]); + if (!ktxFile.empty() && (ktxMipFormat.isCompressed() != texelFormat.isCompressed())) { + texelFormat = ktxMipFormat; + } Sampler sampler; readOptionalTransformed(sampler, node, keys::sampler, [](const json& node) { return readSampler(node); }); TexturePointer result; @@ -325,8 +358,6 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { 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; @@ -359,6 +390,7 @@ ShaderPointer Deserializer::readShader(const json& node) { // FIXME support procedural shaders Shader::Type type = node[keys::type]; + std::string name = node[keys::name]; uint32_t id = node[keys::id]; ShaderPointer result; switch (type) { @@ -374,6 +406,9 @@ ShaderPointer Deserializer::readShader(const json& node) { default: throw std::runtime_error("not implemented"); } + if (result->getSource().name != name) { + throw std::runtime_error("Bad name match"); + } return result; } @@ -747,12 +782,6 @@ StereoState readStereoState(const json& node) { 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())); } @@ -808,7 +837,7 @@ FramePointer Deserializer::deserializeFrame() { swapchains = readArray(frameNode, keys::swapchains, [this](const json& node) { return readSwapchain(node); }); - queries = readArray(frameNode, keys::queries, [this](const json& node) { return readQuery(node); }); + queries = readArray(frameNode, keys::queries, [](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]); diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index c6f3cd9b9a..5c2e181810 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -176,10 +176,18 @@ TexturePointer Texture::createRenderBuffer(const Element& texelFormat, uint16 wi return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler); } +TexturePointer Texture::createRenderBufferMultisample(const Element& texelFormat, uint16 width, uint16 height, uint16 numSamples, const Sampler& sampler) { + return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, numSamples, 0, gpu::Texture::SINGLE_MIP, sampler); +} + TexturePointer Texture::createRenderBufferArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips, const Sampler& sampler) { return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, numSlices, numMips, sampler); } +TexturePointer Texture::createRenderBufferMultisampleArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numSamples, const Sampler& sampler) { + return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, numSamples, numSlices, gpu::Texture::SINGLE_MIP, sampler); +} + TexturePointer Texture::create1D(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) { return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, numMips, sampler); } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 23bfff6873..26ff86af9c 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -387,7 +387,9 @@ public: static TexturePointer create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer createCube(const Element& texelFormat, uint16 width, uint16 numMips = 1, const Sampler& sampler = Sampler()); static TexturePointer createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); + static TexturePointer createRenderBufferMultisample(const Element& texelFormat, uint16 width, uint16 height, uint16 numSamples, const Sampler& sampler = Sampler()); static TexturePointer createRenderBufferArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); + static TexturePointer createRenderBufferMultisampleArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numSamples, const Sampler& sampler = Sampler()); static TexturePointer createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler()); @@ -395,8 +397,6 @@ public: bool isDefined() const { return _defined; } Texture(TextureUsageType usageType); - Texture(const Texture& buf); // deep copy of the sysmem texture - Texture& operator=(const Texture& buf); // deep copy of the sysmem texture ~Texture(); Stamp getStamp() const { return _stamp; } @@ -435,6 +435,7 @@ public: uint16 getNumSamples() const { return _numSamples; } // NumSamples can only have certain values based on the hw static uint16 evalNumSamplesUsed(uint16 numSamplesTried); + bool isMultisample() const { return _numSamples > 1; } // max mip is in the range [ 0 if no sub mips, log2(max(width, height, depth))] // It is defined at creation time (immutable) @@ -690,8 +691,10 @@ class TextureSource { public: TextureSource(const QUrl& url, int type = 0) : _imageUrl(url), _type(type) {} + void setUrl(const QUrl& url) { _imageUrl = url; } const QUrl& getUrl() const { return _imageUrl; } const gpu::TexturePointer getGPUTexture() const { return _gpuTexture; } + void setType(int type) { _type = type; } int getType() const { return _type; } void resetTexture(gpu::TexturePointer texture); diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 43b0daf407..747788aef8 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -157,7 +157,7 @@ namespace scriptable { // QVariantMap armature; }; - // mixin class for Avatar/Entity/Overlay Rendering that expose their in-memory graphics::Meshes + // mixin class for Avatar + Entity Rendering that expose their in-memory graphics::Meshes class ModelProvider { public: NestableType modelProviderType; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 3293d294d8..848f9d42ac 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -36,12 +36,12 @@ void GraphicsScriptingInterface::jsThrowError(const QString& error) { } } -bool GraphicsScriptingInterface::canUpdateModel(QUuid uuid, int meshIndex, int partNumber) { +bool GraphicsScriptingInterface::canUpdateModel(const QUuid& uuid, int meshIndex, int partNumber) { auto provider = getModelProvider(uuid); return provider && provider->canReplaceModelMeshPart(meshIndex, partNumber); } -bool GraphicsScriptingInterface::updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model) { +bool GraphicsScriptingInterface::updateModel(const QUuid& uuid, const scriptable::ScriptableModelPointer& model) { if (!model) { jsThrowError("null model argument"); } @@ -69,7 +69,7 @@ bool GraphicsScriptingInterface::updateModel(QUuid uuid, const scriptable::Scrip return provider->replaceScriptableModelMeshPart(base, -1, -1); } -scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(QUuid uuid) { +scriptable::ModelProviderPointer GraphicsScriptingInterface::getModelProvider(const QUuid& uuid) { QString error; if (auto appProvider = DependencyManager::get()) { if (auto provider = appProvider->lookupModelProvider(uuid)) { @@ -107,7 +107,7 @@ scriptable::ScriptableModelPointer GraphicsScriptingInterface::newModel(const sc return modelWrapper; } -scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModel(QUuid uuid) { +scriptable::ScriptableModelPointer GraphicsScriptingInterface::getModel(const QUuid& uuid) { QString error; bool success; QString providerType = "unknown"; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 1ec60c4244..a72c3be14b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -27,6 +27,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class GraphicsScriptingInterface : public QObject, public QScriptable, public Dependency { @@ -38,13 +39,13 @@ public: public slots: /**jsdoc - * Returns a model reference object associated with the specified UUID ({@link EntityID}, {@link OverlayID}, or {@link AvatarID}). + * Returns a model reference object associated with the specified UUID ({@link EntityID} or {@link AvatarID}). * * @function Graphics.getModel * @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved. * @returns {Graphics.Model} the resulting Model object */ - scriptable::ScriptableModelPointer getModel(QUuid uuid); + scriptable::ScriptableModelPointer getModel(const QUuid& uuid); /**jsdoc * @function Graphics.updateModel @@ -52,7 +53,7 @@ public slots: * @param {Graphics.Model} model * @returns {boolean} */ - bool updateModel(QUuid uuid, const scriptable::ScriptableModelPointer& model); + bool updateModel(const QUuid& uuid, const scriptable::ScriptableModelPointer& model); /**jsdoc * @function Graphics.canUpdateModel @@ -61,7 +62,7 @@ public slots: * @param {number} [partNumber=-1] * @returns {boolean} */ - bool canUpdateModel(QUuid uuid, int meshIndex = -1, int partNumber = -1); + bool canUpdateModel(const QUuid& uuid, int meshIndex = -1, int partNumber = -1); /**jsdoc * @function Graphics.newModel @@ -94,7 +95,7 @@ public slots: QString exportModelToOBJ(const scriptable::ScriptableModel& in); private: - scriptable::ModelProviderPointer getModelProvider(QUuid uuid); + scriptable::ModelProviderPointer getModelProvider(const QUuid& uuid); void jsThrowError(const QString& error); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMeshPointer meshProxy); scriptable::MeshPointer getMeshPointer(scriptable::ScriptableMesh& meshProxy); diff --git a/libraries/graphics/src/graphics/Haze.cpp b/libraries/graphics/src/graphics/Haze.cpp index ded48429ba..d9bee7507f 100644 --- a/libraries/graphics/src/graphics/Haze.cpp +++ b/libraries/graphics/src/graphics/Haze.cpp @@ -182,12 +182,3 @@ void Haze::setHazeBackgroundBlend(const float hazeBackgroundBlend) { _hazeParametersBuffer.edit().hazeBackgroundBlend = newBlend; } } - -void Haze::setTransform(const glm::mat4& transform) { - auto& params = _hazeParametersBuffer.get(); - - if (params.transform != transform) { - _hazeParametersBuffer.edit().transform = transform; - } -} - diff --git a/libraries/graphics/src/graphics/Haze.h b/libraries/graphics/src/graphics/Haze.h index 59138319f4..25004f098f 100644 --- a/libraries/graphics/src/graphics/Haze.h +++ b/libraries/graphics/src/graphics/Haze.h @@ -92,8 +92,6 @@ namespace graphics { void setHazeBackgroundBlend(const float hazeBackgroundBlend); - void setTransform(const glm::mat4& transform); - using UniformBufferView = gpu::BufferView; UniformBufferView getHazeParametersBuffer() const { return _hazeParametersBuffer; } @@ -101,30 +99,32 @@ namespace graphics { class Parameters { public: // DO NOT CHANGE ORDER HERE WITHOUT UNDERSTANDING THE std140 LAYOUT - glm::vec3 hazeColor{ INITIAL_HAZE_COLOR }; - float hazeGlareBlend{ convertGlareAngleToPower(INITIAL_HAZE_GLARE_ANGLE) }; + glm::vec3 hazeColor { INITIAL_HAZE_COLOR }; + float hazeGlareBlend { convertGlareAngleToPower(INITIAL_HAZE_GLARE_ANGLE) }; - glm::vec3 hazeGlareColor{ INITIAL_HAZE_GLARE_COLOR }; - float hazeBaseReference{ INITIAL_HAZE_BASE_REFERENCE }; + glm::vec3 hazeGlareColor { INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference { INITIAL_HAZE_BASE_REFERENCE }; glm::vec3 colorModulationFactor; - int hazeMode{ 0 }; // bit 0 - set to activate haze attenuation of fragment color + int hazeMode { 0 }; // bit 0 - set to activate haze attenuation of fragment color // bit 1 - set to add the effect of altitude to the haze attenuation // bit 2 - set to activate directional light attenuation mode // bit 3 - set to blend between blend-in and blend-out colours - glm::mat4 transform; + // Padding required to align the struct +#if defined(__clang__) + __attribute__((unused)) +#endif + vec3 __padding; // Amount of background (skybox) to display, overriding the haze effect for the background - float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND }; + float hazeBackgroundBlend { INITIAL_HAZE_BACKGROUND_BLEND }; // The haze attenuation exponents used by both fragment and directional light attenuation - float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; - float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; - float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; + float hazeRangeFactor { convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; + float hazeHeightFactor { convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; + float hazeKeyLightRangeFactor { convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; - float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; - // Padding required to align the structure to sizeof(vec4) - vec3 __padding; + float hazeKeyLightAltitudeFactor { convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; Parameters() {} }; diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 7befb7e053..3ef407b7d9 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -87,7 +87,7 @@ void Material::setUnlit(bool value) { } void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) { - _key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f)))); + _key.setAlbedo(true); _albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } @@ -105,7 +105,7 @@ void Material::setMetallic(float metallic) { void Material::setScattering(float scattering) { scattering = glm::clamp(scattering, 0.0f, 1.0f); - _key.setMetallic(scattering > 0.0f); + _key.setScattering(scattering > 0.0f); _scattering = scattering; } diff --git a/libraries/hfm/CMakeLists.txt b/libraries/hfm/CMakeLists.txt index 553fd935d9..be3d866b70 100644 --- a/libraries/hfm/CMakeLists.txt +++ b/libraries/hfm/CMakeLists.txt @@ -5,3 +5,4 @@ link_hifi_libraries(shared) include_hifi_library_headers(gpu) include_hifi_library_headers(graphics) +include_hifi_library_headers(image) diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 1bd87332a1..9f3de3302c 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -25,6 +25,8 @@ #include #include +#include + #if defined(Q_OS_ANDROID) #define HFM_PACK_NORMALS 0 #else @@ -75,8 +77,6 @@ struct JointShapeInfo { class Joint { public: JointShapeInfo shapeInfo; - QVector freeLineage; - bool isFree; int parentIndex; float distanceToParent; @@ -121,10 +121,12 @@ public: /// A texture map. class Texture { public: + QString id; QString name; QByteArray filename; QByteArray content; + image::ColorChannel sourceChannel { image::ColorChannel::NONE }; Transform transform; int maxNumPixels { MAX_NUM_PIXELS_FOR_FBX_TEXTURE }; @@ -291,8 +293,6 @@ public: glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file - glm::vec3 palmDirection; - glm::vec3 neckPivot; Extents bindExtents; @@ -319,7 +319,6 @@ public: QList blendshapeChannelNames; QMap jointRotationOffsets; - QMap hfmToHifiJointNameMapping; }; }; diff --git a/libraries/image/src/image/ColorChannel.h b/libraries/image/src/image/ColorChannel.h new file mode 100644 index 0000000000..e1d107018b --- /dev/null +++ b/libraries/image/src/image/ColorChannel.h @@ -0,0 +1,26 @@ +// +// ColorChannel.h +// libraries/image/src/image +// +// Created by Sabrina Shanman on 2019/02/12. +// 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_image_ColorChannel_h +#define hifi_image_ColorChannel_h + +namespace image { + enum class ColorChannel { + NONE, + RED, + GREEN, + BLUE, + ALPHA, + COUNT + }; +}; + +#endif // hifi_image_ColorChannel_h diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index ac2813667f..a2161caec9 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -221,7 +222,45 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { return QImage(); } -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, +void mapToRedChannel(QImage& image, ColorChannel sourceChannel) { + // Change format of image so we know exactly how to process it + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + + for (int i = 0; i < image.height(); i++) { + QRgb* pixel = reinterpret_cast(image.scanLine(i)); + // Past end pointer + QRgb* lineEnd = pixel + image.width(); + + // Transfer channel data from source to target + for (; pixel < lineEnd; pixel++) { + int colorValue; + switch (sourceChannel) { + case ColorChannel::RED: + colorValue = qRed(*pixel); + break; + case ColorChannel::GREEN: + colorValue = qGreen(*pixel); + break; + case ColorChannel::BLUE: + colorValue = qBlue(*pixel); + break; + case ColorChannel::ALPHA: + colorValue = qAlpha(*pixel); + break; + default: + colorValue = qRed(*pixel); + break; + } + + // Dump the color in the red channel, ignore the rest + *pixel = qRgba(colorValue, 0, 0, 255); + } + } +} + +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, ColorChannel sourceChannel, int maxNumPixels, TextureUsage::Type textureType, bool compress, BackendTarget target, const std::atomic& abortProcessing) { @@ -252,6 +291,11 @@ gpu::TexturePointer processImage(std::shared_ptr content, const std:: QSize(originalWidth, originalHeight) << " to " << QSize(imageWidth, imageHeight) << ")"; } + + // Re-map to image with single red channel texture if requested + if (sourceChannel != ColorChannel::NONE) { + mapToRedChannel(image, sourceChannel); + } auto loader = TextureUsage::getTextureLoaderForType(textureType); auto texture = loader(std::move(image), filename, compress, target, abortProcessing); diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index ae72a183b3..40c31eeeff 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -16,6 +16,8 @@ #include +#include "ColorChannel.h" + class QByteArray; class QImage; @@ -81,7 +83,7 @@ gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const st const QStringList getSupportedFormats(); -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, ColorChannel sourceChannel, int maxNumPixels, TextureUsage::Type textureType, bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 6f94e7592c..c18c4cde65 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -16,6 +16,7 @@ #include "TouchscreenDevice.h" #include "TouchscreenVirtualPadDevice.h" +#if !defined(CUSTOM_INPUT_PLUGINS) // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { @@ -37,6 +38,7 @@ InputPluginList getInputPlugins() { } return result; } +#endif void saveInputPluginSettings(const InputPluginList& plugins) { foreach (auto inputPlugin, plugins) { diff --git a/libraries/midi/src/Midi.h b/libraries/midi/src/Midi.h index e5c44c6b7e..081a44f7b6 100644 --- a/libraries/midi/src/Midi.h +++ b/libraries/midi/src/Midi.h @@ -25,6 +25,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class Midi : public QObject, public Dependency { diff --git a/libraries/model-baker/CMakeLists.txt b/libraries/model-baker/CMakeLists.txt index 6fa7c1815a..aabd6eba3a 100644 --- a/libraries/model-baker/CMakeLists.txt +++ b/libraries/model-baker/CMakeLists.txt @@ -2,3 +2,5 @@ set(TARGET_NAME model-baker) setup_hifi_library() link_hifi_libraries(shared task gpu graphics hfm) + +include_hifi_library_headers(image) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 8d1a82518d..1c2a2f5c63 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -19,13 +19,14 @@ #include "CalculateMeshTangentsTask.h" #include "CalculateBlendshapeNormalsTask.h" #include "CalculateBlendshapeTangentsTask.h" +#include "PrepareJointsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash>; + using Output = VaryingSet6, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash, std::vector>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { @@ -39,6 +40,7 @@ namespace baker { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } output.edit4() = hfmModelIn->materials; + output.edit5() = hfmModelIn->joints.toStdVector(); } }; @@ -99,23 +101,29 @@ namespace baker { class BuildModelTask { public: - using Input = VaryingSet2>; + using Input = VaryingSet5, std::vector, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; using Output = hfm::Model::Pointer; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { auto hfmModelOut = input.get0(); hfmModelOut->meshes = QVector::fromStdVector(input.get1()); + hfmModelOut->joints = QVector::fromStdVector(input.get2()); + hfmModelOut->jointRotationOffsets = input.get3(); + hfmModelOut->jointIndices = input.get4(); output = hfmModelOut; } }; class BakerEngineBuilder { public: - using Input = hfm::Model::Pointer; + using Input = VaryingSet2; using Output = hfm::Model::Pointer; using JobModel = Task::ModelIO; - void build(JobModel& model, const Varying& hfmModelIn, Varying& hfmModelOut) { + void build(JobModel& model, const Varying& input, Varying& hfmModelOut) { + const auto& hfmModelIn = input.getN(0); + const auto& mapping = input.getN(1); + // Split up the inputs from hfm::Model const auto modelPartsIn = model.addJob("GetModelParts", hfmModelIn); const auto meshesIn = modelPartsIn.getN(0); @@ -123,6 +131,7 @@ namespace baker { const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); const auto materials = modelPartsIn.getN(4); + const auto jointsIn = modelPartsIn.getN(5); // 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. @@ -138,19 +147,27 @@ namespace baker { const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); + // Prepare joint information + const auto prepareJointsInputs = PrepareJointsTask::Input(jointsIn, mapping).asVarying(); + const auto jointInfoOut = model.addJob("PrepareJoints", prepareJointsInputs); + const auto jointsOut = jointInfoOut.getN(0); + const auto jointRotationOffsets = jointInfoOut.getN(1); + const auto jointIndices = jointInfoOut.getN(2); + // Combine the outputs into a new hfm::Model 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(); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); } }; - Baker::Baker(const hfm::Model::Pointer& hfmModel) : + Baker::Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping) : _engine(std::make_shared(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared())) { - _engine->feedInput(hfmModel); + _engine->feedInput(0, hfmModel); + _engine->feedInput(1, mapping); } void Baker::run() { diff --git a/libraries/model-baker/src/model-baker/Baker.h b/libraries/model-baker/src/model-baker/Baker.h index 7fb3f420e0..41989d73df 100644 --- a/libraries/model-baker/src/model-baker/Baker.h +++ b/libraries/model-baker/src/model-baker/Baker.h @@ -12,6 +12,8 @@ #ifndef hifi_baker_Baker_h #define hifi_baker_Baker_h +#include + #include #include "Engine.h" @@ -19,7 +21,7 @@ namespace baker { class Baker { public: - Baker(const hfm::Model::Pointer& hfmModel); + Baker(const hfm::Model::Pointer& hfmModel, const QVariantHash& mapping); void run(); diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index e94e15507e..6e12ec546d 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -13,6 +13,17 @@ #include "ModelMath.h" +bool needTangents(const hfm::Mesh& mesh, const QHash& materials) { + // Check if we actually need to calculate the tangents + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + return true; + } + } + return false; +} + void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& normalsPerMesh = input.get0(); const std::vector& meshes = input.get1(); @@ -28,38 +39,20 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; // Check if we already have tangents and therefore do not need to do any calculation + // Otherwise confirm if we have the normals needed, and need to calculate the tangents if (!tangentsIn.empty()) { tangentsOut = tangentsIn.toStdVector(); - continue; + } else if (!normals.empty() && needTangents(mesh, materials)) { + 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]); + }); } - - // 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/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp new file mode 100644 index 0000000000..3b1a57cb43 --- /dev/null +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -0,0 +1,86 @@ +// +// PrepareJointsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/25. +// 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 "PrepareJointsTask.h" + +#include "ModelBakerLogging.h" + +QMap getJointNameMapping(const QVariantHash& mapping) { + static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; + QMap hfmToHifiJointNameMap; + if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); + for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) { + hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString()); + qCDebug(model_baker) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()]; + } + } + return hfmToHifiJointNameMap; +} + +QMap getJointRotationOffsets(const QVariantHash& mapping) { + QMap jointRotationOffsets; + static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; + if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { + auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + QString line = itr.value().toString(); + auto quatCoords = line.split(','); + if (quatCoords.size() == 4) { + float quatX = quatCoords[0].mid(1).toFloat(); + float quatY = quatCoords[1].toFloat(); + float quatZ = quatCoords[2].toFloat(); + float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); + if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { + glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); + jointRotationOffsets.insert(jointName, rotationOffset); + } + } + } + } + return jointRotationOffsets; +} + +void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& jointsIn = input.get0(); + const auto& mapping = input.get1(); + auto& jointsOut = output.edit0(); + auto& jointRotationOffsets = output.edit1(); + auto& jointIndices = output.edit2(); + + // Get joint renames + auto jointNameMapping = getJointNameMapping(mapping); + // Apply joint metadata from FST file mappings + for (const auto& jointIn : jointsIn) { + jointsOut.push_back(jointIn); + auto& jointOut = jointsOut.back(); + + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + jointOut.name = jointNameMapKey; + } + + jointIndices.insert(jointOut.name, (int)jointsOut.size()); + } + + // Get joint rotation offsets from FST file mappings + auto offsets = getJointRotationOffsets(mapping); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + int jointIndex = jointIndices.value(jointName) - 1; + if (jointIndex != -1) { + glm::quat rotationOffset = itr.value(); + jointRotationOffsets.insert(jointIndex, rotationOffset); + qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; + } + } +} diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.h b/libraries/model-baker/src/model-baker/PrepareJointsTask.h new file mode 100644 index 0000000000..e12d8ffd2c --- /dev/null +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.h @@ -0,0 +1,30 @@ +// +// PrepareJointsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/25. +// 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_PrepareJointsTask_h +#define hifi_PrepareJointsTask_h + +#include + +#include + +#include "Engine.h" + +class PrepareJointsTask { +public: + using Input = baker::VaryingSet2, QVariantHash /*mapping*/>; + using Output = baker::VaryingSet3, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_PrepareJointsTask_h \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index b6550a5e9e..aaa9767397 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -417,9 +417,13 @@ MaterialCache& MaterialCache::instance() { } NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); + return ResourceCache::getResource(url).staticCast(); } -QSharedPointer MaterialCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { +QSharedPointer MaterialCache::createResource(const QUrl& url) { return QSharedPointer(new NetworkMaterialResource(url), &Resource::deleter); +} + +QSharedPointer MaterialCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkMaterialResource(*resource.staticCast().data()), &Resource::deleter); } \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h index 074cd6c98d..6abadfc030 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.h +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -53,7 +53,8 @@ public: NetworkMaterialResourcePointer getMaterial(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; }; #endif diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 8c541040a7..90925d17c3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -40,6 +40,50 @@ public: bool combineParts; }; +// From: https://stackoverflow.com/questions/41145012/how-to-hash-qvariant +class QVariantHasher { +public: + QVariantHasher() : buff(&bb), ds(&buff) { + bb.reserve(1000); + buff.open(QIODevice::WriteOnly); + } + uint hash(const QVariant& v) { + buff.seek(0); + ds << v; + return qHashBits(bb.constData(), buff.pos()); + } +private: + QByteArray bb; + QBuffer buff; + QDataStream ds; +}; + +namespace std { + template <> + struct hash { + size_t operator()(const QVariantHash& a) const { + QVariantHasher hasher; + return hasher.hash(a); + } + }; + + template <> + struct hash { + size_t operator()(const QUrl& a) const { + return qHash(a); + } + }; + + template <> + struct hash { + size_t operator()(const GeometryExtra& a) const { + size_t result = 0; + hash_combine(result, a.mapping, a.textureBaseUrl, a.combineParts); + return result; + } + }; +} + QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) { return textureBaseUrl.isValid() ? textureBaseUrl : url; } @@ -107,10 +151,10 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } auto modelCache = DependencyManager::get(); - GeometryExtra extra{ _mapping, _textureBaseUrl, false }; + GeometryExtra extra { _mapping, _textureBaseUrl, false }; // Get the raw GeometryResource - _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); + _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -233,7 +277,7 @@ void GeometryReader::run() { } QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(HFMModel::Pointer, hfmModel)); + Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(QVariantHash, _mapping)); } catch (const std::exception&) { auto resource = _resource.toStrongRef(); if (resource) { @@ -253,15 +297,21 @@ void GeometryReader::run() { class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: - GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) : - GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _modelLoader(modelLoader), _mapping(mapping), _combineParts(combineParts) {} + GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url) : GeometryResource(url), _modelLoader(modelLoader) {} + GeometryDefinitionResource(const GeometryDefinitionResource& other) : + GeometryResource(other), + _modelLoader(other._modelLoader), + _mapping(other._mapping), + _combineParts(other._combineParts) {} QString getType() const override { return "GeometryDefinition"; } virtual void downloadFinished(const QByteArray& data) override; + void setExtra(void* extra) override; + protected: - Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel); + Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping); private: ModelLoader _modelLoader; @@ -269,6 +319,13 @@ private: bool _combineParts; }; +void GeometryDefinitionResource::setExtra(void* extra) { + const GeometryExtra* geometryExtra = static_cast(extra); + _mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); + _textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl(); + _combineParts = geometryExtra ? geometryExtra->combineParts : true; +} + void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { if (_url != _effectiveBaseURL) { _url = _effectiveBaseURL; @@ -277,9 +334,9 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType())); } -void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) { +void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, QVariantHash mapping) { // Do processing on the model - baker::Baker modelBaker(hfmModel); + baker::Baker modelBaker(hfmModel, mapping); modelBaker.run(); // Assume ownership of the processed HFMModel @@ -323,27 +380,26 @@ ModelCache::ModelCache() { modelFormatRegistry->addFormat(GLTFSerializer()); } -QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer ModelCache::createResource(const QUrl& url) { Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); } else { - const GeometryExtra* geometryExtra = static_cast(extra); - auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); - auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); - bool combineParts = geometryExtra ? geometryExtra->combineParts : true; - resource = new GeometryDefinitionResource(_modelLoader, url, mapping, textureBaseUrl, combineParts); + resource = new GeometryDefinitionResource(_modelLoader, url); } return QSharedPointer(resource, &Resource::deleter); } +QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new GeometryDefinitionResource(*resource.staticCast().data()), &Resource::deleter); +} + GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { bool combineParts = true; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -356,7 +412,7 @@ GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& u const QVariantHash& mapping, const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -543,7 +599,7 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl } const auto url = getTextureUrl(baseUrl, hfmTexture); - const auto texture = DependencyManager::get()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels); + const auto texture = DependencyManager::get()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels, hfmTexture.sourceChannel); _textures[channel] = Texture { hfmTexture.name, texture }; auto map = std::make_shared(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 1018bdecd5..497cae86a3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -82,8 +82,12 @@ class GeometryResource : public Resource, public Geometry { public: using Pointer = QSharedPointer; - GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : - Resource(url), _textureBaseUrl(textureBaseUrl) {} + GeometryResource(const QUrl& url) : Resource(url) {} + GeometryResource(const GeometryResource& other) : + Resource(other), + Geometry(other), + _textureBaseUrl(other._textureBaseUrl), + _isCacheable(other._isCacheable) {} virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); } @@ -153,8 +157,8 @@ public: protected: friend class GeometryMappingResource; - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: ModelCache(); diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h index 5ac7ac1e50..16532fafc3 100644 --- a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h @@ -30,6 +30,7 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index bf7ade07f7..8d060c42f2 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -21,11 +21,13 @@ ShaderCache& ShaderCache::instance() { } NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); + return ResourceCache::getResource(url).staticCast(); } -QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { +QSharedPointer ShaderCache::createResource(const QUrl& url) { return QSharedPointer(new NetworkShader(url), &Resource::deleter); } +QSharedPointer ShaderCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkShader(*resource.staticCast().data()), &Resource::deleter); +} diff --git a/libraries/model-networking/src/model-networking/ShaderCache.h b/libraries/model-networking/src/model-networking/ShaderCache.h index bd78e6e7e3..fe9edd7ddf 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.h +++ b/libraries/model-networking/src/model-networking/ShaderCache.h @@ -14,6 +14,7 @@ class NetworkShader : public Resource { public: NetworkShader(const QUrl& url); + NetworkShader(const NetworkShader& other) : Resource(other), _source(other._source) {} QString getType() const override { return "NetworkShader"; } @@ -31,8 +32,8 @@ public: NetworkShaderPointer getShader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; }; #endif diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 4c30dc6d93..a268f4ad0a 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -192,15 +192,34 @@ public: image::TextureUsage::Type type; const QByteArray& content; int maxNumPixels; + image::ColorChannel sourceChannel; }; -ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels) { - auto byteArray = QByteArray(); - TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels }; - return ResourceCache::prefetch(url, &extra); +namespace std { + template <> + struct hash { + size_t operator()(const QByteArray& a) const { + return qHash(a); + } + }; + + template <> + struct hash { + size_t operator()(const TextureExtra& a) const { + size_t result = 0; + hash_combine(result, (int)a.type, a.content, a.maxNumPixels, (int)a.sourceChannel); + return result; + } + }; } -NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) { +ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNumPixels, image::ColorChannel sourceChannel) { + auto byteArray = QByteArray(); + TextureExtra extra = { (image::TextureUsage::Type)type, byteArray, maxNumPixels, sourceChannel }; + return ResourceCache::prefetch(url, &extra, std::hash()(extra)); +} + +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels, image::ColorChannel sourceChannel) { if (url.scheme() == RESOURCE_SCHEME) { return getResourceTexture(url); } @@ -210,8 +229,8 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs query.addQueryItem("skybox", ""); modifiedUrl.setQuery(query.toString()); } - TextureExtra extra = { type, content, maxNumPixels }; - return ResourceCache::getResource(modifiedUrl, QUrl(), &extra).staticCast(); + TextureExtra extra = { type, content, maxNumPixels, sourceChannel }; + return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash()(extra)).staticCast(); } gpu::TexturePointer TextureCache::getTextureByHash(const std::string& hash) { @@ -305,26 +324,44 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } -QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) { - const TextureExtra* textureExtra = static_cast(extra); - auto type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE; - auto content = textureExtra ? textureExtra->content : QByteArray(); - auto maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; - NetworkTexture* texture = new NetworkTexture(url, type, content, maxNumPixels); - return QSharedPointer(texture, &Resource::deleter); +QSharedPointer TextureCache::createResource(const QUrl& url) { + return QSharedPointer(new NetworkTexture(url), &Resource::deleter); +} + +QSharedPointer TextureCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkTexture(*resource.staticCast().data()), &Resource::deleter); } int networkTexturePointerMetaTypeId = qRegisterMetaType>(); -NetworkTexture::NetworkTexture(const QUrl& url) : -Resource(url), -_type(), -_maxNumPixels(100) +NetworkTexture::NetworkTexture(const QUrl& url, bool resourceTexture) : + Resource(url), + Texture(), + _maxNumPixels(100) { - _textureSource = std::make_shared(url); - _lowestRequestedMipLevel = 0; - _loaded = true; + if (resourceTexture) { + _textureSource = std::make_shared(url); + _loaded = true; + } +} + +NetworkTexture::NetworkTexture(const NetworkTexture& other) : + Resource(other), + Texture(other), + _type(other._type), + _sourceChannel(other._sourceChannel), + _currentlyLoadingResourceType(other._currentlyLoadingResourceType), + _originalWidth(other._originalWidth), + _originalHeight(other._originalHeight), + _width(other._width), + _height(other._height), + _maxNumPixels(other._maxNumPixels) +{ + if (_width == 0 || _height == 0 || + other._currentlyLoadingResourceType == ResourceType::META || + (other._currentlyLoadingResourceType == ResourceType::KTX && other._ktxResourceState != KTXResourceState::WAITING_FOR_MIP_REQUEST)) { + _startedLoading = false; + } } static bool isLocalUrl(const QUrl& url) { @@ -332,15 +369,21 @@ static bool isLocalUrl(const QUrl& url) { return (scheme == HIFI_URL_SCHEME_FILE || scheme == URL_SCHEME_QRC || scheme == RESOURCE_SCHEME); } -NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) : - Resource(url), - _type(type), - _maxNumPixels(maxNumPixels) -{ - _textureSource = std::make_shared(url, (int)type); +void NetworkTexture::setExtra(void* extra) { + const TextureExtra* textureExtra = static_cast(extra); + _type = textureExtra ? textureExtra->type : image::TextureUsage::DEFAULT_TEXTURE; + _maxNumPixels = textureExtra ? textureExtra->maxNumPixels : ABSOLUTE_MAX_TEXTURE_NUM_PIXELS; + _sourceChannel = textureExtra ? textureExtra->sourceChannel : image::ColorChannel::NONE; + + if (_textureSource) { + _textureSource->setUrl(_url); + _textureSource->setType((int)_type); + } else { + _textureSource = std::make_shared(_url, (int)_type); + } _lowestRequestedMipLevel = 0; - auto fileNameLowercase = url.fileName().toLower(); + auto fileNameLowercase = _url.fileName().toLower(); if (fileNameLowercase.endsWith(TEXTURE_META_EXTENSION)) { _currentlyLoadingResourceType = ResourceType::META; } else if (fileNameLowercase.endsWith(".ktx")) { @@ -351,17 +394,18 @@ NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, _shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX; - if (type == image::TextureUsage::CUBE_TEXTURE) { + if (_type == image::TextureUsage::CUBE_TEXTURE) { setLoadPriority(this, SKYBOX_LOAD_PRIORITY); } else if (_currentlyLoadingResourceType == ResourceType::KTX) { setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY); } - if (!url.isValid()) { + if (!_url.isValid()) { _loaded = true; } // if we have content, load it after we have our self pointer + auto content = textureExtra ? textureExtra->content : QByteArray(); if (!content.isEmpty()) { _startedLoading = true; QMetaObject::invokeMethod(this, "downloadFinished", Qt::QueuedConnection, Q_ARG(const QByteArray&, content)); @@ -396,7 +440,8 @@ gpu::TexturePointer NetworkTexture::getFallbackTexture() const { class ImageReader : public QRunnable { public: ImageReader(const QWeakPointer& resource, const QUrl& url, - const QByteArray& data, int maxNumPixels); + const QByteArray& data, size_t extraHash, int maxNumPixels, + image::ColorChannel sourceChannel); void run() override final; void read(); @@ -406,7 +451,9 @@ private: QWeakPointer _resource; QUrl _url; QByteArray _content; + size_t _extraHash; int _maxNumPixels; + image::ColorChannel _sourceChannel; }; NetworkTexture::~NetworkTexture() { @@ -493,7 +540,6 @@ void NetworkTexture::makeRequest() { } else { qWarning(networking) << "NetworkTexture::makeRequest() called while not in a valid state: " << _ktxResourceState; } - } void NetworkTexture::handleLocalRequestCompleted() { @@ -1039,7 +1085,7 @@ void NetworkTexture::loadTextureContent(const QByteArray& content) { return; } - QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _maxNumPixels)); + QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _extraHash, _maxNumPixels, _sourceChannel)); } void NetworkTexture::refresh() { @@ -1064,11 +1110,13 @@ void NetworkTexture::refresh() { Resource::refresh(); } -ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, int maxNumPixels) : +ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, image::ColorChannel sourceChannel) : _resource(resource), _url(url), _content(data), - _maxNumPixels(maxNumPixels) + _extraHash(extraHash), + _maxNumPixels(maxNumPixels), + _sourceChannel(sourceChannel) { DependencyManager::get()->incrementStat("PendingProcessing"); listSupportedImageFormats(); @@ -1123,11 +1171,12 @@ void ImageReader::read() { } auto networkTexture = resource.staticCast(); - // Hash the source image to for KTX caching + // Hash the source image and extraHash for KTX caching std::string hash; { QCryptographicHash hasher(QCryptographicHash::Md5); hasher.addData(_content); + hasher.addData(std::to_string(_extraHash).c_str()); hash = hasher.result().toHex().toStdString(); } @@ -1175,7 +1224,7 @@ void ImageReader::read() { constexpr bool shouldCompress = false; #endif auto target = getBackendTarget(); - texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _sourceChannel, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!texture) { QMetaObject::invokeMethod(resource.data(), "setImage", @@ -1216,11 +1265,11 @@ void ImageReader::read() { Q_ARG(int, texture->getHeight())); } -NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) { +NetworkTexturePointer TextureCache::getResourceTexture(const QUrl& resourceTextureUrl) { gpu::TexturePointer texture; if (resourceTextureUrl == SPECTATOR_CAMERA_FRAME_URL) { if (!_spectatorCameraNetworkTexture) { - _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); + _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl, true)); } if (!_spectatorCameraFramebuffer) { getSpectatorCameraFramebuffer(); // initialize frame buffer @@ -1231,7 +1280,7 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) // FIXME: Generalize this, DRY up this code if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) { if (!_hmdPreviewNetworkTexture) { - _hmdPreviewNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); + _hmdPreviewNetworkTexture.reset(new NetworkTexture(resourceTextureUrl, true)); } if (_hmdPreviewFramebuffer) { texture = _hmdPreviewFramebuffer->getRenderBuffer(0); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 3933e3ae56..acca916acc 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -45,8 +46,8 @@ class NetworkTexture : public Resource, public Texture { Q_OBJECT public: - NetworkTexture(const QUrl& url); - NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels); + NetworkTexture(const QUrl& url, bool resourceTexture = false); + NetworkTexture(const NetworkTexture& other); ~NetworkTexture() override; QString getType() const override { return "NetworkTexture"; } @@ -63,6 +64,8 @@ public: Q_INVOKABLE void setOriginalDescriptor(ktx::KTXDescriptor* descriptor) { _originalKtxDescriptor.reset(descriptor); } + void setExtra(void* extra) override; + signals: void networkTextureCreated(const QWeakPointer& self); @@ -94,6 +97,7 @@ private: friend class ImageReader; image::TextureUsage::Type _type; + image::ColorChannel _sourceChannel; enum class ResourceType { META, @@ -176,12 +180,13 @@ public: /// Loads a texture from the specified URL. NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE, - const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, + image::ColorChannel sourceChannel = image::ColorChannel::NONE); gpu::TexturePointer getTextureByHash(const std::string& hash); gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture); - NetworkTexturePointer getResourceTexture(QUrl resourceTextureUrl); + NetworkTexturePointer getResourceTexture(const QUrl& resourceTextureUrl); const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height); @@ -199,10 +204,10 @@ signals: protected: // Overload ResourceCache::prefetch to allow specifying texture type for loads - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, image::ColorChannel sourceChannel = image::ColorChannel::NONE); - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: friend class ImageReader; diff --git a/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h index 4120840759..1cc4f4f948 100644 --- a/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h +++ b/libraries/model-networking/src/model-networking/TextureCacheScriptingInterface.h @@ -30,6 +30,7 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} numTotal - Total number of total resources. Read-only. * @property {number} numCached - Total number of cached resource. Read-only. diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index f74b337ee7..4647c50496 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -217,15 +217,21 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth:: uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); QUrl requestURL = _authURL; - + if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. requestURL = getMetaverseServerURL(); } + int queryStringLocation = path.indexOf("?"); if (path.startsWith("/")) { - requestURL.setPath(path); + requestURL.setPath(path.left(queryStringLocation)); } else { - requestURL.setPath("/" + path); + requestURL.setPath("/" + path.left(queryStringLocation)); + } + + if (queryStringLocation >= 0) { + QUrlQuery query(path.mid(queryStringLocation+1)); + requestURL.setQuery(query); } if (authType != AccountManagerAuth::None ) { @@ -252,8 +258,7 @@ void AccountManager::sendRequest(const QString& path, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap, - QUrlQuery query) { + const QVariantMap& propertyMap) { if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendRequest", diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 477488031d..8732042e93 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -68,8 +68,7 @@ public: const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), QHttpMultiPart* dataMultiPart = NULL, - const QVariantMap& propertyMap = QVariantMap(), - QUrlQuery query = QUrlQuery()); + const QVariantMap& propertyMap = QVariantMap()); void setIsAgent(bool isAgent) { _isAgent = isAgent; } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index e6957728e8..9145b4a79e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,7 +30,8 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; +const QString DEFAULT_HOME_ADDRESS = "file:///~/serverless/tutorial.json"; const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 5318822cdc..3b95923634 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -24,6 +24,7 @@ extern const QString DEFAULT_HIFI_ADDRESS; extern const QString REDIRECT_HIFI_ADDRESS; +extern const QString DEFAULT_HOME_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; @@ -42,6 +43,7 @@ const QString GET_PLACE = "/api/v1/places/%1"; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-assignment-client * * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index f2ccfe33f4..255487f0bb 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -40,6 +40,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 20fe05e7b8..7345081380 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -158,8 +158,8 @@ void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) { _resourceCache->updateTotalSize(deltaSize); } -ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra) { - return _resourceCache->prefetch(url, extra); +ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { + return _resourceCache->prefetch(url, extra, extraHash); } @@ -211,20 +211,20 @@ void ScriptableResource::disconnectHelper() { } } -ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { +ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { ScriptableResource* result = nullptr; if (QThread::currentThread() != thread()) { // Must be called in thread to ensure getResource returns a valid pointer BLOCKING_INVOKE_METHOD(this, "prefetch", Q_RETURN_ARG(ScriptableResource*, result), - Q_ARG(QUrl, url), Q_ARG(void*, extra)); + Q_ARG(QUrl, url), Q_ARG(void*, extra), Q_ARG(size_t, extraHash)); return result; } result = new ScriptableResource(url); - auto resource = getResource(url, QUrl(), extra); + auto resource = getResource(url, QUrl(), extra, extraHash); result->_resource = resource; result->setObjectName(url.toString()); @@ -265,16 +265,17 @@ ResourceCache::~ResourceCache() { void ResourceCache::clearATPAssets() { { QWriteLocker locker(&_resourcesLock); - for (auto& url : _resources.keys()) { + QList urls = _resources.keys(); + for (auto& url : urls) { // If this is an ATP resource if (url.scheme() == URL_SCHEME_ATP) { - - // Remove it from the resource hash - auto resource = _resources.take(url); - if (auto strongRef = resource.lock()) { - // Make sure the resource won't reinsert itself - strongRef->setCache(nullptr); - _totalResourcesSize -= strongRef->getBytes(); + auto resourcesWithExtraHash = _resources.take(url); + for (auto& resource : resourcesWithExtraHash) { + if (auto strongRef = resource.lock()) { + // Make sure the resource won't reinsert itself + strongRef->setCache(nullptr); + _totalResourcesSize -= strongRef->getBytes(); + } } } } @@ -297,16 +298,20 @@ void ResourceCache::refreshAll() { clearUnusedResources(); resetUnusedResourceCounter(); - QHash> resources; + QHash>> allResources; { QReadLocker locker(&_resourcesLock); - resources = _resources; + allResources = _resources; } // Refresh all remaining resources in use - foreach (QSharedPointer resource, resources) { - if (resource) { - resource->refresh(); + // FIXME: this will trigger multiple refreshes for the same resource if they have different hashes + for (auto& resourcesWithExtraHash : allResources) { + for (auto& resourceWeak : resourcesWithExtraHash) { + auto resource = resourceWeak.lock(); + if (resource) { + resource->refresh(); + } } } } @@ -338,39 +343,53 @@ void ResourceCache::setRequestLimit(uint32_t limit) { } } -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra) { +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) { QSharedPointer resource; { QReadLocker locker(&_resourcesLock); - resource = _resources.value(url).lock(); + auto& resourcesWithExtraHash = _resources[url]; + auto resourcesWithExtraHashIter = resourcesWithExtraHash.find(extraHash); + if (resourcesWithExtraHashIter != resourcesWithExtraHash.end()) { + // We've seen this extra info before + resource = resourcesWithExtraHashIter.value().lock(); + } else if (resourcesWithExtraHash.size() > 0.0f) { + // We haven't seen this extra info before, but we've already downloaded the resource. We need a new copy of this object (with any old hash). + resource = createResourceCopy(resourcesWithExtraHash.begin().value().lock()); + resource->setExtra(extra); + resource->setExtraHash(extraHash); + resource->setSelf(resource); + resource->setCache(this); + resource->moveToThread(qApp->thread()); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + resourcesWithExtraHash.insert(extraHash, resource); + resource->ensureLoading(); + } } if (resource) { removeUnusedResource(resource); } - if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) { - resource = getResource(fallback, QUrl()); + if (!resource && (!url.isValid() || url.isEmpty()) && fallback.isValid()) { + resource = getResource(fallback, QUrl(), extra, extraHash); } if (!resource) { - resource = createResource( - url, - fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), - extra); + resource = createResource(url); + resource->setExtra(extra); + resource->setExtraHash(extraHash); resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); - _resources.insert(url, resource); + _resources[url].insert(extraHash, resource); } removeUnusedResource(resource); resource->ensureLoading(); } - DependencyManager::get()->update( - resource->getURL(), -1, "ResourceCache::getResource"); + DependencyManager::get()->update(resource->getURL(), -1, "ResourceCache::getResource"); return resource; } @@ -384,7 +403,7 @@ void ResourceCache::addUnusedResource(const QSharedPointer& resource) // If it doesn't fit or its size is unknown, remove it from the cache. if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) { resource->setCache(nullptr); - removeResource(resource->getURL(), resource->getBytes()); + removeResource(resource->getURL(), resource->getExtraHash(), resource->getBytes()); resetTotalResourceCounter(); return; } @@ -423,7 +442,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { auto size = it.value()->getBytes(); locker.unlock(); - removeResource(it.value()->getURL(), size); + removeResource(it.value()->getURL(), it.value()->getExtraHash(), size); locker.relock(); _unusedResourcesSize -= size; @@ -469,9 +488,13 @@ void ResourceCache::resetResourceCounters() { emit dirty(); } -void ResourceCache::removeResource(const QUrl& url, qint64 size) { +void ResourceCache::removeResource(const QUrl& url, size_t extraHash, qint64 size) { QWriteLocker locker(&_resourcesLock); - _resources.remove(url); + auto& resources = _resources[url]; + resources.remove(extraHash); + if (resources.size() == 0) { + _resources.remove(url); + } _totalResourcesSize -= size; } @@ -527,6 +550,27 @@ bool ResourceCache::attemptHighestPriorityRequest() { static int requestID = 0; +Resource::Resource(const Resource& other) : + QObject(), + _url(other._url), + _effectiveBaseURL(other._effectiveBaseURL), + _activeUrl(other._activeUrl), + _requestByteRange(other._requestByteRange), + _shouldFailOnRedirect(other._shouldFailOnRedirect), + _startedLoading(other._startedLoading), + _failedToLoad(other._failedToLoad), + _loaded(other._loaded), + _loadPriorities(other._loadPriorities), + _bytesReceived(other._bytesReceived), + _bytesTotal(other._bytesTotal), + _bytes(other._bytes), + _requestID(++requestID), + _extraHash(other._extraHash) { + if (!other._loaded) { + _startedLoading = false; + } +} + Resource::Resource(const QUrl& url) : _url(url), _activeUrl(url), @@ -623,7 +667,7 @@ void Resource::allReferencesCleared() { } else { if (_cache) { // remove from the cache - _cache->removeResource(getURL(), getBytes()); + _cache->removeResource(getURL(), getExtraHash(), getBytes()); _cache->resetTotalResourceCounter(); } @@ -678,7 +722,7 @@ void Resource::setSize(const qint64& bytes) { void Resource::reinsert() { QWriteLocker locker(&_cache->_resourcesLock); - _cache->_resources.insert(_url, _self); + _cache->_resources[_url].insert(_extraHash, _self); } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 275684f73e..f3967d8ffc 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -95,6 +95,7 @@ class ScriptableResource : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * @@ -231,16 +232,16 @@ protected slots: // Prefetches a resource to be held by the QScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). - ScriptableResource* prefetch(const QUrl& url, void* extra); + ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); // FIXME: The return type is not recognized by JavaScript. /// Loads a resource from the specified URL and returns it. /// If the caller is on a different thread than the ResourceCache, /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable - /// \param extra extra data to pass to the creator, if appropriate - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), - void* extra = NULL); + // FIXME: std::numeric_limits::max() could be a valid extraHash + QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits::max()); } + QSharedPointer getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash); private slots: void clearATPAssets(); @@ -251,11 +252,11 @@ protected: // which should be a QScriptEngine with ScriptableResource registered, so that // the QScriptEngine will delete the pointer when it is garbage collected. // JSDoc is provided on more general function signature. - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } /// Creates a new resource. - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra) = 0; + virtual QSharedPointer createResource(const QUrl& url) = 0; + virtual QSharedPointer createResourceCopy(const QSharedPointer& resource) = 0; void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); @@ -271,14 +272,14 @@ private: friend class ScriptableResourceCache; void reserveUnusedResource(qint64 resourceSize); - void removeResource(const QUrl& url, qint64 size = 0); + void removeResource(const QUrl& url, size_t extraHash, qint64 size = 0); void resetTotalResourceCounter(); void resetUnusedResourceCounter(); void resetResourceCounters(); // Resources - QHash> _resources; + QHash>> _resources; QReadWriteLock _resourcesLock { QReadWriteLock::Recursive }; int _lastLRUKey = 0; @@ -332,10 +333,10 @@ public: * Prefetches a resource. * @function ResourceCache.prefetch * @param {string} url - URL of the resource to prefetch. - * @param {object} [extra=null] * @returns {ResourceObject} */ - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra = nullptr); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); signals: @@ -359,7 +360,8 @@ class Resource : public QObject { Q_OBJECT public: - + + Resource(const Resource& other); Resource(const QUrl& url); virtual ~Resource(); @@ -415,6 +417,10 @@ public: unsigned int getDownloadAttempts() { return _attempts; } unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; } + virtual void setExtra(void* extra) {}; + void setExtraHash(size_t extraHash) { _extraHash = extraHash; } + size_t getExtraHash() const { return _extraHash; } + signals: /// Fired when the resource begins downloading. void loading(); @@ -469,7 +475,7 @@ protected: virtual bool handleFailedRequest(ResourceRequest::Result result); QUrl _url; - QUrl _effectiveBaseURL{ _url }; + QUrl _effectiveBaseURL { _url }; QUrl _activeUrl; ByteRange _requestByteRange; bool _shouldFailOnRedirect { false }; @@ -492,6 +498,8 @@ protected: int _requestID; ResourceRequest* _request{ nullptr }; + size_t _extraHash; + public slots: void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); void handleReplyFinished(); diff --git a/libraries/networking/src/ResourceScriptingInterface.h b/libraries/networking/src/ResourceScriptingInterface.h index cc3f12f990..5f81537e99 100644 --- a/libraries/networking/src/ResourceScriptingInterface.h +++ b/libraries/networking/src/ResourceScriptingInterface.h @@ -22,6 +22,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index d898c03597..5f55c189ce 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -260,6 +260,8 @@ enum class EntityVersion : PacketVersion { MissingWebEntityProperties, PulseProperties, RingGizmoEntities, + ShowKeyboardFocusHighlight, + WebBillboardMode, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index a79f0a0c2b..8ab502e951 100755 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -729,12 +729,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -744,14 +738,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - const unsigned char *start = dataBytes; for (int i = 0; i < length; i++) { dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]); @@ -764,12 +751,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -779,14 +760,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - int bit = 0; unsigned char current = 0; const unsigned char *start = dataBytes; @@ -797,7 +771,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto result[i] = (bool)(current & (1 << bit)); bit = (bit + 1) % BITS_IN_BYTE; } - return (dataBytes - start) + (int)sizeof(uint16_t); } diff --git a/libraries/oculusMobile/CMakeLists.txt b/libraries/oculusMobile/CMakeLists.txt new file mode 100644 index 0000000000..213721722c --- /dev/null +++ b/libraries/oculusMobile/CMakeLists.txt @@ -0,0 +1,11 @@ +if (ANDROID) + set(TARGET_NAME oculusMobile) + # don't use the setup_hifi_library macro, we don't want ANY qt dependencies + file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc") + add_library(${TARGET_NAME} SHARED ${LIB_SRCS}) + target_glm() + target_egl() + target_glad() + target_oculus_mobile() + target_link_libraries(${TARGET_NAME} android log) +endif() diff --git a/libraries/oculusMobile/src/ovr/Forward.h b/libraries/oculusMobile/src/ovr/Forward.h new file mode 100644 index 0000000000..5881dde9a7 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Forward.h @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2018/11/23 +// 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 +#include +#include + +namespace ovr { + using Mutex = std::mutex; + using Condition = std::condition_variable; + using Lock = std::unique_lock; + using Task = std::function; +} \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.cpp b/libraries/oculusMobile/src/ovr/Framebuffer.cpp new file mode 100644 index 0000000000..4c4fd2a983 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Framebuffer.cpp @@ -0,0 +1,93 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// 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 "Framebuffer.h" + +#include +#include +#include + +#include +#include + +using namespace ovr; + +void Framebuffer::updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix ) const { + auto& layerTexture = layer.Textures[eye]; + layerTexture.ColorSwapChain = _swapChain; + layerTexture.SwapChainIndex = _index; + if (projectionMatrix) { + layerTexture.TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection( projectionMatrix ); + } + layerTexture.TextureRect = { 0, 0, 1, 1 }; +} + +void Framebuffer::create(const glm::uvec2& size) { + _size = size; + _index = 0; + _validTexture = false; + + // Depth renderbuffer + glGenRenderbuffers(1, &_depth); + glBindRenderbuffer(GL_RENDERBUFFER, _depth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _size.x, _size.y); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + // Framebuffer + glGenFramebuffers(1, &_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + _swapChain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D, GL_RGBA8, _size.x, _size.y, 1, 3); + _length = vrapi_GetTextureSwapChainLength(_swapChain); + if (!_length) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Unable to count swap chain textures"); + return; + } + + for (int i = 0; i < _length; ++i) { + GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, i); + glBindTexture(GL_TEXTURE_2D, chainTexId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + glBindTexture(GL_TEXTURE_2D, 0); +} + +void Framebuffer::destroy() { + if (0 != _fbo) { + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } + if (0 != _depth) { + glDeleteRenderbuffers(1, &_depth); + _depth = 0; + } + if (_swapChain != nullptr) { + vrapi_DestroyTextureSwapChain(_swapChain); + _swapChain = nullptr; + } + _index = -1; + _length = -1; +} + +void Framebuffer::advance() { + _index = (_index + 1) % _length; + _validTexture = false; +} + +void Framebuffer::bind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + if (!_validTexture) { + GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, _index); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, chainTexId, 0); + _validTexture = true; + } +} diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.h b/libraries/oculusMobile/src/ovr/Framebuffer.h new file mode 100644 index 0000000000..5127574462 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Framebuffer.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// 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 + +#include +#include + +#include + +namespace ovr { + +struct Framebuffer { +public: + void updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix = nullptr) const; + void create(const glm::uvec2& size); + void advance(); + void destroy(); + void bind(); + + uint32_t _depth { 0 }; + uint32_t _fbo{ 0 }; + int _length{ -1 }; + int _index{ -1 }; + bool _validTexture{ false }; + glm::uvec2 _size; + ovrTextureSwapChain* _swapChain{ nullptr }; +}; + +} // namespace ovr \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp new file mode 100644 index 0000000000..449ba67084 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/GLContext.cpp @@ -0,0 +1,182 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 "GLContext.h" + +#include +#include +#include + +#include + +#if !defined(EGL_OPENGL_ES3_BIT_KHR) +#define EGL_OPENGL_ES3_BIT_KHR 0x0040 +#endif + +using namespace ovr; + +static void* getGlProcessAddress(const char *namez) { + auto result = eglGetProcAddress(namez); + return (void*)result; +} + + +void GLContext::initModule() { + static std::once_flag once; + std::call_once(once, [&]{ + gladLoadGLES2Loader(getGlProcessAddress); + }); +} + +void APIENTRY debugMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) { + if (type == GL_DEBUG_TYPE_PERFORMANCE_KHR) { + return; + } + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: + case GL_DEBUG_SEVERITY_MEDIUM: + break; + default: + return; + } + + __android_log_write(ANDROID_LOG_WARN, "QQQ_GL", message); +} + +GLContext::~GLContext() { + destroy(); +} + +EGLConfig GLContext::findConfig(EGLDisplay display) { + // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample + // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in + // settings, and that is completely wasted for our warp target. + std::vector configs; + { + const int MAX_CONFIGS = 1024; + EGLConfig configsBuffer[MAX_CONFIGS]; + EGLint numConfigs = 0; + if (eglGetConfigs(display, configsBuffer, MAX_CONFIGS, &numConfigs) == EGL_FALSE) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed to fetch configs"); + return 0; + } + configs.resize(numConfigs); + memcpy(configs.data(), configsBuffer, sizeof(EGLConfig) * numConfigs); + } + + std::vector> configAttribs{ + { EGL_RED_SIZE, 8 }, { EGL_GREEN_SIZE, 8 }, { EGL_BLUE_SIZE, 8 }, { EGL_ALPHA_SIZE, 8 }, + { EGL_DEPTH_SIZE, 0 }, { EGL_STENCIL_SIZE, 0 }, { EGL_SAMPLES, 0 }, + }; + + auto matchAttrib = [&](EGLConfig config, const std::pair& attribAndValue) { + EGLint value = 0; + eglGetConfigAttrib(display, config, attribAndValue.first, &value); + return (attribAndValue.second == value); + }; + + auto matchAttribFlags = [&](EGLConfig config, const std::pair& attribAndValue) { + EGLint value = 0; + eglGetConfigAttrib(display, config, attribAndValue.first, &value); + return (value & attribAndValue.second) == attribAndValue.second; + }; + + auto matchConfig = [&](EGLConfig config) { + if (!matchAttribFlags(config, { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR})) { + return false; + } + // The pbuffer config also needs to be compatible with normal window rendering + // so it can share textures with the window context. + if (!matchAttribFlags(config, { EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT})) { + return false; + } + + for (const auto& attrib : configAttribs) { + if (!matchAttrib(config, attrib)) { + return false; + } + } + + return true; + }; + + + for (const auto& config : configs) { + if (matchConfig(config)) { + return config; + } + } + + return 0; +} + +bool GLContext::makeCurrent() { + return eglMakeCurrent(display, surface, surface, context) != EGL_FALSE; +} + +void GLContext::doneCurrent() { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +bool GLContext::create(EGLDisplay display, EGLContext shareContext) { + this->display = display; + + auto config = findConfig(display); + + if (config == 0) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglChooseConfig"); + return false; + } + + EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + + context = eglCreateContext(display, config, shareContext, contextAttribs); + if (context == EGL_NO_CONTEXT) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreateContext"); + return false; + } + + const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE }; + surface = eglCreatePbufferSurface(display, config, surfaceAttribs); + if (surface == EGL_NO_SURFACE) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreatePbufferSurface"); + return false; + } + + if (!makeCurrent()) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglMakeCurrent"); + return false; + } + + ovr::GLContext::initModule(); + +#ifndef NDEBUG + glDebugMessageCallback(debugMessageCallback, this); + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); +#endif + return true; +} + +void GLContext::destroy() { + if (context != EGL_NO_CONTEXT) { + eglDestroyContext(display, context); + context = EGL_NO_CONTEXT; + } + + if (surface != EGL_NO_SURFACE) { + eglDestroySurface(display, surface); + surface = EGL_NO_SURFACE; + } +} diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h new file mode 100644 index 0000000000..04f96e8d47 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/GLContext.h @@ -0,0 +1,37 @@ +// +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 + +#include + +#include +#include + +namespace ovr { + +struct GLContext { + using Pointer = std::shared_ptr; + EGLSurface surface{ EGL_NO_SURFACE }; + EGLContext context{ EGL_NO_CONTEXT }; + EGLDisplay display{ EGL_NO_DISPLAY }; + + ~GLContext(); + static EGLConfig findConfig(EGLDisplay display); + bool makeCurrent(); + void doneCurrent(); + bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT); + void destroy(); + operator bool() const { return context != EGL_NO_CONTEXT; } + static void initModule(); +}; + +} + + +#define CHECK_GL_ERROR() if(false) {} \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/Helpers.cpp b/libraries/oculusMobile/src/ovr/Helpers.cpp new file mode 100644 index 0000000000..a48d37311e --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Helpers.cpp @@ -0,0 +1,38 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 "Helpers.h" + +#include +#include +#include +#include + +using namespace ovr; + +void Fov::extend(const Fov& other) { + for (size_t i = 0; i < 4; ++i) { + leftRightUpDown[i] = std::max(leftRightUpDown[i], other.leftRightUpDown[i]); + } +} + +void Fov::extract(const ovrMatrix4f& mat) { + auto& fs = leftRightUpDown; + ovrMatrix4f_ExtractFov( &mat, fs, fs + 1, fs + 2, fs + 3); +} + +glm::mat4 Fov::withZ(float nearZ, float farZ) const { + const auto& fs = leftRightUpDown; + return ovr::toGlm(ovrMatrix4f_CreateProjectionAsymmetricFov(fs[0], fs[1], fs[2], fs[3], nearZ, farZ)); +} + +glm::mat4 Fov::withZ(const glm::mat4& other) const { + // FIXME + return withZ(0.01f, 1000.0f); +} + diff --git a/libraries/oculusMobile/src/ovr/Helpers.h b/libraries/oculusMobile/src/ovr/Helpers.h new file mode 100644 index 0000000000..2bd0b7f603 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Helpers.h @@ -0,0 +1,94 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 + +#include +#include +#include +#include +#include + +namespace ovr { + +struct Fov { + float leftRightUpDown[4]; + Fov() {} + Fov(const ovrMatrix4f& mat) { extract(mat); } + void extract(const ovrMatrix4f& mat); + void extend(const Fov& other); + glm::mat4 withZ(const glm::mat4& other) const; + glm::mat4 withZ(float nearZ, float farZ) const; +}; + +// Convenience method for looping over each eye with a lambda +static inline void for_each_eye(const std::function& f) { + f(VRAPI_EYE_LEFT); + f(VRAPI_EYE_RIGHT); +} + +static inline void for_each_hand(const std::function& f) { + f(VRAPI_HAND_LEFT); + f(VRAPI_HAND_RIGHT); +} + +static inline glm::mat4 toGlm(const ovrMatrix4f& om) { + return glm::transpose(glm::make_mat4(&om.M[0][0])); +} + +static inline glm::vec3 toGlm(const ovrVector3f& ov) { + return glm::make_vec3(&ov.x); +} + +static inline glm::vec2 toGlm(const ovrVector2f& ov) { + return glm::make_vec2(&ov.x); +} + +static inline glm::quat toGlm(const ovrQuatf& oq) { + return glm::make_quat(&oq.x); +} + +static inline glm::mat4 toGlm(const ovrPosef& op) { + glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); + glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); + return translation * orientation; +} + +static inline ovrMatrix4f fromGlm(const glm::mat4& m) { + ovrMatrix4f result; + glm::mat4 transposed(glm::transpose(m)); + memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); + return result; +} + +static inline ovrVector3f fromGlm(const glm::vec3& v) { + return { v.x, v.y, v.z }; +} + +static inline ovrVector2f fromGlm(const glm::vec2& v) { + return { v.x, v.y }; +} + +static inline ovrQuatf fromGlm(const glm::quat& q) { + return { q.x, q.y, q.z, q.w }; +} + +static inline ovrPosef poseFromGlm(const glm::mat4& m) { + glm::vec3 translation = glm::vec3(m[3]) / m[3].w; + glm::quat orientation = glm::quat_cast(m); + ovrPosef result; + result.Orientation = fromGlm(orientation); + result.Position = fromGlm(translation); + return result; +} + +} + + + + + diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.cpp b/libraries/oculusMobile/src/ovr/TaskQueue.cpp new file mode 100644 index 0000000000..5506a35acd --- /dev/null +++ b/libraries/oculusMobile/src/ovr/TaskQueue.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2018/11/23 +// 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 "TaskQueue.h" + +using namespace ovr; + +void TaskQueue::submitTaskBlocking(Lock& lock, const Task& newTask) { + _task = newTask; + _taskPending = true; + _taskCondition.wait(lock, [=]() -> bool { return !_taskPending; }); +} + +void TaskQueue::submitTaskBlocking(const Task& task) { + Lock lock(_mutex); + submitTaskBlocking(lock, task); +} + +void TaskQueue::pollTask() { + Lock lock(_mutex); + if (_taskPending) { + _task(); + _taskPending = false; + _taskCondition.notify_one(); + } +} + +void TaskQueue::withLock(const Task& task) { + Lock lock(_mutex); + task(); +} + +void TaskQueue::withLockConditional(const LockTask& task) { + Lock lock(_mutex); + task(lock); +} diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.h b/libraries/oculusMobile/src/ovr/TaskQueue.h new file mode 100644 index 0000000000..4ec055ece9 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/TaskQueue.h @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 + +#include +#include + +namespace ovr { + +using Mutex = std::mutex; +using Condition = std::condition_variable; +using Lock = std::unique_lock; +using Task = std::function; +using LockTask = std::function; + +class TaskQueue { +public: + // Execute a task on another thread + void submitTaskBlocking(const Task& task); + void submitTaskBlocking(Lock& lock, const Task& task); + void pollTask(); + + void withLock(const Task& task); + void withLockConditional(const LockTask& task); +private: + Mutex _mutex; + Task _task; + bool _taskPending{ false }; + Condition _taskCondition; +}; + +} + + + + + diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp new file mode 100644 index 0000000000..de2b4e1ff6 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -0,0 +1,337 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 "VrHandler.h" + +#include +#include + +#include + +#include +#include +#include +//#include + +#include "GLContext.h" +#include "Helpers.h" +#include "Framebuffer.h" + + + +using namespace ovr; + +static thread_local bool isRenderThread { false }; + +struct VrSurface : public TaskQueue { + using HandlerTask = VrHandler::HandlerTask; + + JavaVM* vm{nullptr}; + jobject oculusActivity{ nullptr }; + ANativeWindow* nativeWindow{ nullptr }; + + VrHandler* handler{nullptr}; + ovrMobile* session{nullptr}; + bool resumed { false }; + GLContext vrglContext; + Framebuffer eyeFbos[2]; + uint32_t readFbo{0}; + std::atomic presentIndex{1}; + double displayTime{0}; + + static constexpr float EYE_BUFFER_SCALE = 1.0f; + + void onCreate(JNIEnv* env, jobject activity) { + env->GetJavaVM(&vm); + oculusActivity = env->NewGlobalRef(activity); + } + + void setResumed(bool newResumed) { + this->resumed = newResumed; + submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); }); + } + + void setNativeWindow(ANativeWindow* newNativeWindow) { + auto oldNativeWindow = nativeWindow; + nativeWindow = newNativeWindow; + if (oldNativeWindow) { + ANativeWindow_release(oldNativeWindow); + } + submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); }); + } + + void init() { + if (!handler) { + return; + } + + EGLContext currentContext = eglGetCurrentContext(); + EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + vrglContext.create(currentDisplay, currentContext); + vrglContext.makeCurrent(); + + glm::uvec2 eyeTargetSize; + withEnv([&](JNIEnv* env){ + ovrJava java{ vm, env, oculusActivity }; + eyeTargetSize = glm::uvec2 { + vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH) * EYE_BUFFER_SCALE, + vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT) * EYE_BUFFER_SCALE, + }; + }); + __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "QQQ Eye Size %d, %d", eyeTargetSize.x, eyeTargetSize.y); + ovr::for_each_eye([&](ovrEye eye) { + eyeFbos[eye].create(eyeTargetSize); + }); + glGenFramebuffers(1, &readFbo); + vrglContext.doneCurrent(); + } + + void shutdown() { + } + + void setHandler(VrHandler *newHandler) { + withLock([&] { + isRenderThread = newHandler != nullptr; + if (handler != newHandler) { + shutdown(); + handler = newHandler; + init(); + if (handler) { + updateVrMode(); + } + } + }); + } + + void submitRenderThreadTask(const HandlerTask &task) { + withLockConditional([&](Lock &lock) { + if (handler != nullptr) { + submitTaskBlocking(lock, [&] { + task(handler); + }); + } + }); + } + + void withEnv(const std::function& f) { + JNIEnv* env = nullptr; + bool attached = false; + vm->GetEnv((void**)&env, JNI_VERSION_1_6); + if (!env) { + attached = true; + vm->AttachCurrentThread(&env, nullptr); + } + f(env); + if (attached) { + vm->DetachCurrentThread(); + } + } + + void updateVrMode() { + // For VR mode to be valid, the activity must be between an onResume and + // an onPause call and must additionally have a valid native window handle + bool vrReady = resumed && nullptr != nativeWindow; + // If we're IN VR mode, we'll have a non-null ovrMobile pointer in session + bool vrRunning = session != nullptr; + if (vrReady != vrRunning) { + if (vrRunning) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode"); + vrapi_LeaveVrMode(session); + session = nullptr; + oculusActivity = nullptr; + } else { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_EnterVrMode"); + withEnv([&](JNIEnv* env){ + ovrJava java{ vm, env, oculusActivity }; + ovrModeParms modeParms = vrapi_DefaultModeParms(&java); + modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW; + modeParms.Display = (unsigned long long) vrglContext.display; + modeParms.ShareContext = (unsigned long long) vrglContext.context; + modeParms.WindowSurface = (unsigned long long) nativeWindow; + session = vrapi_EnterVrMode(&modeParms); + ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL); + vrapi_SetTrackingTransform( session, trackingTransform ); + vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self()); + vrapi_SetClockLevels(session, 2, 4); + vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC); + vrapi_SetDisplayRefreshRate(session, 72); + }); + } + } + } + + void presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) { + ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2(); + layer.HeadPose = tracking.HeadPose; + if (sourceTexture) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, sourceTexture, 0); + GLenum framebufferStatus = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE != framebufferStatus) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "incomplete framebuffer"); + } + } + GLenum invalidateAttachment = GL_COLOR_ATTACHMENT0; + + ovr::for_each_eye([&](ovrEye eye) { + const auto &eyeTracking = tracking.Eye[eye]; + auto &eyeFbo = eyeFbos[eye]; + const auto &destSize = eyeFbo._size; + eyeFbo.bind(); + glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &invalidateAttachment); + if (sourceTexture) { + auto sourceWidth = sourceSize.x / 2; + auto sourceX = (eye == VRAPI_EYE_LEFT) ? 0 : sourceWidth; + glBlitFramebuffer( + sourceX, 0, sourceX + sourceWidth, sourceSize.y, + 0, 0, destSize.x, destSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + eyeFbo.updateLayer(eye, layer, &eyeTracking.ProjectionMatrix); + eyeFbo.advance(); + }); + if (sourceTexture) { + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &invalidateAttachment); + glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + } + glFlush(); + + ovrLayerHeader2 *layerHeader = &layer.Header; + ovrSubmitFrameDescription2 frameDesc = {}; + frameDesc.SwapInterval = 2; + frameDesc.FrameIndex = presentIndex; + frameDesc.DisplayTime = displayTime; + frameDesc.LayerCount = 1; + frameDesc.Layers = &layerHeader; + vrapi_SubmitFrame2(session, &frameDesc); + ++presentIndex; + } + + ovrTracking2 beginFrame() { + displayTime = vrapi_GetPredictedDisplayTime(session, presentIndex); + return vrapi_GetPredictedTracking2(session, displayTime); + } +}; + +static VrSurface SURFACE; + +bool VrHandler::vrActive() const { + return SURFACE.session != nullptr; +} + +void VrHandler::setHandler(VrHandler* handler) { + SURFACE.setHandler(handler); +} + +void VrHandler::pollTask() { + SURFACE.pollTask(); +} + +void VrHandler::makeCurrent() { + if (!SURFACE.vrglContext.makeCurrent()) { + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to make GL current"); + } +} + +void VrHandler::doneCurrent() { + SURFACE.vrglContext.doneCurrent(); +} + +uint32_t VrHandler::currentPresentIndex() const { + return SURFACE.presentIndex; +} + +ovrTracking2 VrHandler::beginFrame() { + return SURFACE.beginFrame(); +} + +void VrHandler::presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) const { + SURFACE.presentFrame(sourceTexture, sourceSize, tracking); +} + +bool VrHandler::withOvrJava(const OvrJavaTask& task) { + SURFACE.withEnv([&](JNIEnv* env){ + ovrJava java{ SURFACE.vm, env, SURFACE.oculusActivity }; + task(&java); + }); + return true; +} + +bool VrHandler::withOvrMobile(const OvrMobileTask &task) { + auto sessionTask = [&]()->bool{ + if (!SURFACE.session) { + return false; + } + task(SURFACE.session); + return true; + }; + + if (isRenderThread) { + return sessionTask(); + } + + bool result = false; + SURFACE.withLock([&]{ + result = sessionTask(); + }); + return result; +} + + +void VrHandler::initVr(const char* appId) { + withOvrJava([&](const ovrJava* java){ + ovrInitParms initParms = vrapi_DefaultInitParms(java); + initParms.GraphicsAPI = VRAPI_GRAPHICS_API_OPENGL_ES_3; + if (vrapi_Initialize(&initParms) != VRAPI_INITIALIZE_SUCCESS) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed vrapi init"); + } + }); + + // if (appId) { + // auto platformInitResult = ovr_PlatformInitializeAndroid(appId, activity.object(), env); + // if (ovrPlatformInitialize_Success != platformInitResult) { + // __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed ovr platform init"); + // } + // } +} + +void VrHandler::shutdownVr() { + vrapi_Shutdown(); +} + +extern "C" { + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) { + __android_log_write(ANDROID_LOG_WARN, "QQQ", "oculusMobile::JNI_OnLoad"); + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnCreate(JNIEnv* env, jobject obj) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.onCreate(env, obj); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnDestroy(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnResume(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setResumed(true); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnPause(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setResumed(false); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnSurfaceChanged(JNIEnv* env, jclass, jobject surface) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setNativeWindow(surface ? ANativeWindow_fromSurface( env, surface ) : nullptr); +} + +} // extern "C" diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h new file mode 100644 index 0000000000..3c36ee8626 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/VrHandler.h @@ -0,0 +1,47 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 + +#include +#include +#include + +#include "TaskQueue.h" + +typedef struct ovrMobile ovrMobile; +namespace ovr { + +class VrHandler { +public: + using HandlerTask = std::function; + using OvrMobileTask = std::function; + using OvrJavaTask = std::function; + static void setHandler(VrHandler* handler); + static bool withOvrMobile(const OvrMobileTask& task); + +protected: + static void initVr(const char* appId = nullptr); + static void shutdownVr(); + static bool withOvrJava(const OvrJavaTask& task); + + uint32_t currentPresentIndex() const; + ovrTracking2 beginFrame(); + void presentFrame(uint32_t textureId, const glm::uvec2& size, const ovrTracking2& tracking) const; + + bool vrActive() const; + void pollTask(); + void makeCurrent(); + void doneCurrent(); +}; + +} + + + + + diff --git a/libraries/oculusMobilePlugin/CMakeLists.txt b/libraries/oculusMobilePlugin/CMakeLists.txt new file mode 100644 index 0000000000..b404c51f02 --- /dev/null +++ b/libraries/oculusMobilePlugin/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Created by Bradley Austin Davis on 2018/11/15 +# 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 +# + +if (ANDROID) + set(TARGET_NAME oculusMobilePlugin) + setup_hifi_library(AndroidExtras Multimedia) + + # if we were passed an Oculus App ID for entitlement checks, send that along + if (DEFINED ENV{OCULUS_APP_ID}) + target_compile_definitions(${TARGET_NAME} -DOCULUS_APP_ID="$ENV{OCULUS_APP_ID}") + endif () + + link_hifi_libraries( + shared task gl shaders gpu controllers ui qml + plugins ui-plugins display-plugins input-plugins + audio-client networking render-utils + render graphics + oculusMobile + ${PLATFORM_GL_BACKEND} + ) + + include_hifi_library_headers(octree) + target_oculus_mobile() +endif() diff --git a/libraries/oculusMobilePlugin/src/Logging.cpp b/libraries/oculusMobilePlugin/src/Logging.cpp new file mode 100644 index 0000000000..3d8628b0cb --- /dev/null +++ b/libraries/oculusMobilePlugin/src/Logging.cpp @@ -0,0 +1,4 @@ +#include "Logging.h" + +Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") +Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus") diff --git a/libraries/oculusMobilePlugin/src/Logging.h b/libraries/oculusMobilePlugin/src/Logging.h new file mode 100644 index 0000000000..066712ef6a --- /dev/null +++ b/libraries/oculusMobilePlugin/src/Logging.h @@ -0,0 +1,13 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// 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 + +#include + +Q_DECLARE_LOGGING_CATEGORY(displayplugins) +Q_DECLARE_LOGGING_CATEGORY(oculusLog) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp new file mode 100644 index 0000000000..705045b517 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp @@ -0,0 +1,727 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 "OculusMobileControllerManager.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Logging.h" +#include + +const char* OculusMobileControllerManager::NAME = "Oculus"; +const quint64 LOST_TRACKING_DELAY = 3000000; + +namespace ovr { + + controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.Pose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(handPose.Pose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); + pose.valid = true; + return pose; + } + + controller::Pose toControllerPose(ovrHandedness hand, + const ovrRigidBodyPosef& handPose, + const ovrRigidBodyPosef& lastHandPose) { + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.Pose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(lastHandPose.Pose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = toGlm(lastHandPose.AngularVelocity); + pose.velocity = toGlm(lastHandPose.LinearVelocity); + pose.valid = true; + return pose; + } + +} + + +class OculusMobileInputDevice : public controller::InputDevice { + friend class OculusMobileControllerManager; +public: + using Pointer = std::shared_ptr; + static Pointer check(ovrMobile* session); + + OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps); + void updateHands(ovrMobile* session); + + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + +private: + void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, + ovrHandedness hand, const ovrRigidBodyPosef& handPose); + void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, + ovrHandedness hand, const ovrRigidBodyPosef& handPose); + void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, + const ovrRigidBodyPosef& headPose); + + void reconnectTouchControllers(ovrMobile* session); + + // perform an action when the TouchDevice mutex is acquired. + using Locker = std::unique_lock; + + template + void withLock(F&& f) { Locker locker(_lock); f(); } + + mutable std::recursive_mutex _lock; + ovrTracking2 _headTracking; + struct HandData { + HandData() { + state.Header.ControllerType =ovrControllerType_TrackedRemote; + } + + float hapticDuration { 0.0f }; + float hapticStrength { 0.0f }; + bool valid{ false }; + bool lostTracking{ false }; + quint64 regainTrackingDeadline; + ovrRigidBodyPosef lastPose; + ovrInputTrackedRemoteCapabilities caps; + ovrInputStateTrackedRemote state; + ovrResult stateResult{ ovrError_NotInitialized }; + ovrTracking tracking; + ovrResult trackingResult{ ovrError_NotInitialized }; + bool setHapticFeedback(float strength, float duration) { +#if 0 + bool success = true; + bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + if (strength == 0.0f) { + hapticStrength = 0.0f; + hapticDuration = 0.0f; + } else { + hapticStrength = (duration > hapticDuration) ? strength : hapticStrength; + if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) { + success = false; + } + hapticDuration = std::max(duration, hapticDuration); + } + }); + return success && sessionSuccess; +#else + return true; +#endif + } + + void stopHapticPulse() { + ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f); + }); + } + + bool isValid() const { + return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess); + } + + void update(ovrMobile* session, double time = 0.0) { + const auto& deviceId = caps.Header.DeviceID; + stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header); + trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking); + } + }; + std::array _hands; +}; + +OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) { + Pointer result; + + std::vector devicesCaps; + { + uint32_t deviceIndex { 0 }; + ovrInputCapabilityHeader capsHeader; + while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { + if (capsHeader.Type == ovrControllerType_TrackedRemote) { + ovrInputTrackedRemoteCapabilities caps; + caps.Header = capsHeader; + vrapi_GetInputDeviceCapabilities(session, &caps.Header); + devicesCaps.push_back(caps); + } + ++deviceIndex; + } + } + if (!devicesCaps.empty()) { + result.reset(new OculusMobileInputDevice(session, devicesCaps)); + } + return result; +} + +static OculusMobileInputDevice::Pointer oculusMobileControllers; + +bool OculusMobileControllerManager::isHandController() const { + return oculusMobileControllers.operator bool(); +} + +bool OculusMobileControllerManager::isSupported() const { + return true; +} + +bool OculusMobileControllerManager::activate() { + InputPlugin::activate(); + checkForConnectedDevices(); + return true; +} + +void OculusMobileControllerManager::checkForConnectedDevices() { + if (oculusMobileControllers) { + return; + } + + ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + oculusMobileControllers = OculusMobileInputDevice::check(session); + if (oculusMobileControllers) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(oculusMobileControllers); + } + }); +} + +void OculusMobileControllerManager::deactivate() { + InputPlugin::deactivate(); + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + if (oculusMobileControllers) { + userInputMapper->removeDevice(oculusMobileControllers->getDeviceID()); + oculusMobileControllers.reset(); + } +} + +void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + PerformanceTimer perfTimer("OculusMobileInputDevice::update"); + + checkForConnectedDevices(); + + if (!oculusMobileControllers) { + return; + } + + bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + oculusMobileControllers->updateHands(session); + }); + + if (updated) { + oculusMobileControllers->update(deltaTime, inputCalibrationData); + } +} + +void OculusMobileControllerManager::pluginFocusOutEvent() { + if (oculusMobileControllers) { + oculusMobileControllers->focusOutEvent(); + } +} + +QStringList OculusMobileControllerManager::getSubdeviceNames() { + QStringList devices; + if (oculusMobileControllers) { + devices << oculusMobileControllers->getName(); + } + return devices; +} + +using namespace controller; + +static const std::vector> BUTTON_MAP { { + { ovrButton_Up, DU }, + { ovrButton_Down, DD }, + { ovrButton_Left, DL }, + { ovrButton_Right, DR }, + { ovrButton_Enter, START }, + { ovrButton_Back, BACK }, + { ovrButton_X, X }, + { ovrButton_Y, Y }, + { ovrButton_A, A }, + { ovrButton_B, B }, + { ovrButton_LThumb, LS }, + { ovrButton_RThumb, RS }, + //{ ovrButton_LShoulder, LB }, + //{ ovrButton_RShoulder, RB }, +} }; + +static const std::vector> LEFT_TOUCH_MAP { { + { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, + { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_LThumb, LS_TOUCH }, + { ovrTouch_ThumbUp, LEFT_THUMB_UP }, + { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_IndexPointing, LEFT_INDEX_POINT }, +} }; + + +static const std::vector> RIGHT_TOUCH_MAP { { + { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, + { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_RThumb, RS_TOUCH }, + { ovrTouch_ThumbUp, RIGHT_THUMB_UP }, + { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_IndexPointing, RIGHT_INDEX_POINT }, +} }; + +void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _buttonPressedMap.clear(); + + int numTrackedControllers = 0; + quint64 currentTime = usecTimestampNow(); + handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose); + + static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED; + ovr::for_each_hand([&](ovrHandedness hand) { + size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1; + int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; + auto& handData = _hands[handIndex]; + const auto& tracking = handData.tracking; + ++numTrackedControllers; + + // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) +// if (!hasInputFocus) { +// _poseStateMap.erase(controller); +// _poseStateMap[controller].valid = false; +// return; +// } + + if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) { + _poseStateMap.erase(controller); + handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose); + handData.lostTracking = false; + handData.lastPose = tracking.HeadPose; + return; + } + + if (handData.lostTracking) { + if (currentTime > handData.regainTrackingDeadline) { + _poseStateMap.erase(controller); + _poseStateMap[controller].valid = false; + return; + } + } else { + quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; + handData.regainTrackingDeadline = deadlineToRegainTracking; + handData.lostTracking = true; + } + handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose); + }); + + + using namespace controller; + // Axes + { + const auto& inputState = _hands[0].state; + _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x; + _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y; + _axisStateMap[LT].value = inputState.IndexTrigger; + _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger; + for (const auto& pair : BUTTON_MAP) { + if (inputState.Buttons & pair.first) { + _buttonPressedMap.insert(pair.second); + qDebug()<<"AAAA:BUTTON PRESSED "<The Controller.Hardware.OculusTouch object has properties representing Oculus Rift. The property values are + * integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or + * Controller.Standard items in a {@link RouteObject} mapping.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyTypeDataDescription
Buttons
Anumbernumber"A" button pressed.
Bnumbernumber"B" button pressed.
Xnumbernumber"X" button pressed.
Ynumbernumber"Y" button pressed.
LeftApplicationMenunumbernumberLeft application menu button pressed. + *
RightApplicationMenunumbernumberRight application menu button pressed. + *
Sticks
LXnumbernumberLeft stick x-axis scale.
LYnumbernumberLeft stick y-axis scale.
RXnumbernumberRight stick x-axis scale.
RYnumbernumberRight stick y-axis scale.
LSnumbernumberLeft stick button pressed.
RSnumbernumberRight stick button pressed.
LSTouchnumbernumberLeft stick is touched.
RSTouchnumbernumberRight stick is touched.
Triggers
LTnumbernumberLeft trigger scale.
RTnumbernumberRight trigger scale.
LeftGripnumbernumberLeft grip scale.
RightGripnumbernumberRight grip scale.
Finger Abstractions
LeftPrimaryThumbTouchnumbernumberLeft thumb touching primary thumb + * button.
LeftSecondaryThumbTouchnumbernumberLeft thumb touching secondary thumb + * button.
LeftThumbUpnumbernumberLeft thumb not touching primary or secondary + * thumb buttons.
RightPrimaryThumbTouchnumbernumberRight thumb touching primary thumb + * button.
RightSecondaryThumbTouchnumbernumberRight thumb touching secondary thumb + * button.
RightThumbUpnumbernumberRight thumb not touching primary or secondary + * thumb buttons.
LeftPrimaryIndexTouchnumbernumberLeft index finger is touching primary + * index finger control.
LeftIndexPointnumbernumberLeft index finger is pointing, not touching + * primary or secondary index finger controls.
RightPrimaryIndexTouchnumbernumberRight index finger is touching primary + * index finger control.
RightIndexPointnumbernumberRight index finger is pointing, not touching + * primary or secondary index finger controls.
Avatar Skeleton
Headnumber{@link Pose}Head pose.
LeftHandnumber{@link Pose}Left hand pose.
RightHandnumber{@link Pose}right hand pose.
+ * @typedef {object} Controller.Hardware-OculusTouch + */ +controller::Input::NamedVector OculusMobileInputDevice::getAvailableInputs() const { + using namespace controller; + QVector availableInputs{ + // buttons + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + + // trackpad analogs + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + + // triggers + makePair(LT, "LT"), + makePair(RT, "RT"), + + // trigger buttons + //makePair(LB, "LB"), + //makePair(RB, "RB"), + + // side grip triggers + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), + + // joystick buttons + makePair(LS, "LS"), + makePair(RS, "RS"), + + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + makePair(HEAD, "Head"), + + makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), + makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), + makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), + makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), + makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), + makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + makePair(LEFT_THUMB_UP, "LeftThumbUp"), + makePair(RIGHT_THUMB_UP, "RightThumbUp"), + makePair(LEFT_INDEX_POINT, "LeftIndexPoint"), + makePair(RIGHT_INDEX_POINT, "RightIndexPoint"), + makePair(BACK, "LeftApplicationMenu"), + makePair(START, "RightApplicationMenu"), + }; + return availableInputs; +} + +OculusMobileInputDevice::OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps) : controller::InputDevice("OculusTouch") { + qWarning() << "QQQ" << __FUNCTION__ << "Found " << devicesCaps.size() << "devices"; + for (const auto& deviceCaps : devicesCaps) { + size_t handIndex = -1; + if (deviceCaps.ControllerCapabilities & ovrControllerCaps_LeftHand) { + handIndex = 0; + } else if (deviceCaps.ControllerCapabilities & ovrControllerCaps_RightHand) { + handIndex = 1; + } else { + continue; + } + HandData& handData = _hands[handIndex]; + handData.state.Header.ControllerType = ovrControllerType_TrackedRemote; + handData.valid = true; + handData.caps = deviceCaps; + handData.update(session); + } +} + +void OculusMobileInputDevice::updateHands(ovrMobile* session) { + _headTracking = vrapi_GetPredictedTracking2(session, 0.0); + + bool touchControllerNotConnected = false; + for (auto& hand : _hands) { + hand.update(session); + + if (hand.stateResult < 0 || hand.trackingResult < 0) { + touchControllerNotConnected = true; + } + } + + if (touchControllerNotConnected) { + reconnectTouchControllers(session); + } +} + +void OculusMobileInputDevice::reconnectTouchControllers(ovrMobile* session) { + uint32_t deviceIndex { 0 }; + ovrInputCapabilityHeader capsHeader; + while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { + if (capsHeader.Type == ovrControllerType_TrackedRemote) { + ovrInputTrackedRemoteCapabilities caps; + caps.Header = capsHeader; + vrapi_GetInputDeviceCapabilities(session, &caps.Header); + + if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand || caps.ControllerCapabilities & ovrControllerCaps_RightHand) { + size_t handIndex = caps.ControllerCapabilities & ovrControllerCaps_LeftHand ? 0 : 1; + HandData& handData = _hands[handIndex]; + handData.state.Header.ControllerType = ovrControllerType_TrackedRemote; + handData.valid = true; + handData.caps = caps; + handData.update(session); + } + } + ++deviceIndex; + } +} + +QString OculusMobileInputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json"; + return MAPPING_JSON; +} + +// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class +InputPluginList getInputPlugins() { + InputPlugin* PLUGIN_POOL[] = { + new KeyboardMouseDevice(), + new OculusMobileControllerManager(), + nullptr + }; + + InputPluginList result; + for (int i = 0; PLUGIN_POOL[i]; ++i) { + InputPlugin* plugin = PLUGIN_POOL[i]; + if (plugin->isSupported()) { + result.push_back(InputPluginPointer(plugin)); + } + } + return result; +} diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h new file mode 100644 index 0000000000..43ead8d6a2 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h @@ -0,0 +1,43 @@ +// +// Created by Bradley Austin Davis on 2016/03/04 +// Copyright 2013-2016 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__OculusMobileControllerManager +#define hifi__OculusMobileControllerManager + +#include +#include +#include + +#include + +#include +#include + +class OculusMobileControllerManager : public InputPlugin { +Q_OBJECT +public: + // Plugin functions + bool isSupported() const override; + const QString getName() const override { return NAME; } + bool isHandController() const override; + bool isHeadController() const override { return true; } + QStringList getSubdeviceNames() override; + + bool activate() override; + void deactivate() override; + + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + +private: + static const char* NAME; + + void checkForConnectedDevices(); +}; + +#endif // hifi__OculusMobileControllerManager diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp new file mode 100644 index 0000000000..78d80443d8 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -0,0 +1,269 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 "OculusMobileDisplayPlugin.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ovr; + +const char* OculusMobileDisplayPlugin::NAME { "Oculus Rift" }; +//thread_local bool renderThread = false; +#define OCULUS_APP_ID 2331695256865113 + +OculusMobileDisplayPlugin::OculusMobileDisplayPlugin() { + +} + +OculusMobileDisplayPlugin::~OculusMobileDisplayPlugin() { +} + +void OculusMobileDisplayPlugin::init() { + Parent::init(); + initVr(); + + emit deviceConnected(getName()); +} + +void OculusMobileDisplayPlugin::deinit() { + shutdownVr(); + Parent::deinit(); +} + +bool OculusMobileDisplayPlugin::internalActivate() { + _renderTargetSize = { 1024, 512 }; + _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 0.0f, 0.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); + + + withOvrJava([&](const ovrJava* java){ + _renderTargetSize = glm::uvec2{ + 2 * vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH), + vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT), + }; + }); + + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = _cullingProjection; + }); + + // This must come after the initialization, so that the values calculated + // above are available during the customizeContext call (when not running + // in threaded present mode) + return Parent::internalActivate(); +} + +void OculusMobileDisplayPlugin::internalDeactivate() { + Parent::internalDeactivate(); + // ovr::releaseRenderSession(_session); +} + +void OculusMobileDisplayPlugin::customizeContext() { + qWarning() << "QQQ" << __FUNCTION__ << "done"; + gl::initModuleGl(); + _mainContext = _container->getPrimaryWidget()->context(); + _mainContext->makeCurrent(); + ovr::VrHandler::setHandler(this); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::customizeContext(); + qWarning() << "QQQ" << __FUNCTION__ << "done"; +} + +void OculusMobileDisplayPlugin::uncustomizeContext() { + ovr::VrHandler::setHandler(nullptr); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::uncustomizeContext(); +} + +QRectF OculusMobileDisplayPlugin::getPlayAreaRect() { + QRectF result; + VrHandler::withOvrMobile([&](ovrMobile* session){ + ovrPosef pose; + ovrVector3f scale; + if (ovrSuccess != vrapi_GetBoundaryOrientedBoundingBox(session, &pose, &scale)) { + return; + } + // FIXME extract the center from the pose + glm::vec2 center { 0 }; + glm::vec2 dimensions = glm::vec2(scale.x, scale.z); + dimensions *= 2.0f; + result = QRectF(center.x, center.y, dimensions.x, dimensions.y); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + result = ovr::Fov{ trackingState.Eye[eye].ProjectionMatrix }.withZ(baseProjection); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + ovr::Fov fovs[2]; + for (size_t i = 0; i < 2; ++i) { + fovs[i].extract(trackingState.Eye[i].ProjectionMatrix); + } + fovs[0].extend(fovs[1]); + return fovs[0].withZ(baseProjection); + }); + return result; +} + +void OculusMobileDisplayPlugin::resetSensors() { + VrHandler::withOvrMobile([&](ovrMobile* session){ + vrapi_RecenterPose(session); + }); + _currentRenderFrameInfo.renderPose = glm::mat4(); // identity +} + +float OculusMobileDisplayPlugin::getTargetFrameRate() const { + float result = 0.0f; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = vrapi_GetSystemPropertyFloat(java, VRAPI_SYS_PROP_DISPLAY_REFRESH_RATE); + }); + return result; +} + +bool OculusMobileDisplayPlugin::isHmdMounted() const { + bool result = false; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = VRAPI_FALSE != vrapi_GetSystemStatusInt(java, VRAPI_SYS_STATUS_MOUNTED); + }); + return result; +} + +static void goToDevMobile() { + auto addressManager = DependencyManager::get(); + auto currentAddress = addressManager->currentAddress().toString().toStdString(); + if (std::string::npos == currentAddress.find("dev-mobile")) { + addressManager->handleLookupString("hifi://dev-mobile/495.236,501.017,482.434/0,0.97452,0,-0.224301"); + //addressManager->handleLookupString("hifi://dev-mobile/504,498,491/0,0,0,0"); + //addressManager->handleLookupString("hifi://dev-mobile/0,-1,1"); + } +} + +// Called on the render thread, establishes the rough tracking for the upcoming +bool OculusMobileDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + bool result = false; + _currentRenderFrameInfo = FrameInfo(); + ovrTracking2 trackingState = {}; + static bool resetTrackingTransform = true; + static glm::mat4 transformOffset; + + VrHandler::withOvrMobile([&](ovrMobile* session){ + if (resetTrackingTransform) { + auto pose = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_FLOOR_LEVEL); + transformOffset = glm::inverse(ovr::toGlm(pose)); + vrapi_SetTrackingTransform( session, pose); + resetTrackingTransform = false; + } + // Find a better way of + _currentRenderFrameInfo.predictedDisplayTime = vrapi_GetPredictedDisplayTime(session, currentPresentIndex() + 2); + trackingState = vrapi_GetPredictedTracking2(session, _currentRenderFrameInfo.predictedDisplayTime); + result = true; + }); + + + + if (result) { + _currentRenderFrameInfo.renderPose = transformOffset; + withNonPresentThreadLock([&] { + _currentRenderFrameInfo.sensorSampleTime = trackingState.HeadPose.TimeInSeconds; + _currentRenderFrameInfo.renderPose = transformOffset * ovr::toGlm(trackingState.HeadPose.Pose); + _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + _frameInfos[frameIndex] = _currentRenderFrameInfo; + _ipd = vrapi_GetInterpupillaryDistance(&trackingState); + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = ovr::toGlm(trackingState.Eye[eye].ProjectionMatrix); + _eyeOffsets[eye] = glm::translate(mat4(), vec3{ _ipd * (eye == VRAPI_EYE_LEFT ? -0.5f : 0.5f), 0.0f, 0.0f }); + }); + }); + } + + // static uint32_t count = 0; + // if ((++count % 1000) == 0) { + // AbstractViewStateInterface::instance()->postLambdaEvent([] { + // goToDevMobile(); + // }); + // } + + return result && Parent::beginFrameRender(frameIndex); +} + +ovrTracking2 presentTracking; + +void OculusMobileDisplayPlugin::updatePresentPose() { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + VrHandler::withOvrMobile([&](ovrMobile* session){ + presentTracking = beginFrame(); + _currentPresentFrameInfo.sensorSampleTime = vrapi_GetTimeInSeconds(); + _currentPresentFrameInfo.predictedDisplayTime = presentTracking.HeadPose.TimeInSeconds; + _currentPresentFrameInfo.presentPose = ovr::toGlm(presentTracking.HeadPose.Pose); + }); +} + +void OculusMobileDisplayPlugin::internalPresent() { + VrHandler::pollTask(); + + if (!vrActive()) { + QThread::msleep(1); + return; + } + + auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() }; + VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking); + _presentRate.increment(); +} + +DisplayPluginList getDisplayPlugins() { + static DisplayPluginList result; + static std::once_flag once; + std::call_once(once, [&]{ + auto plugin = std::make_shared(); + plugin->isSupported(); + result.push_back(plugin); + }); + return result; +} + diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h new file mode 100644 index 0000000000..4a0a21e995 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// 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 + +#include + +#include + +#include +#include +#include + +#include +#include + +typedef struct ovrTextureSwapChain ovrTextureSwapChain; +typedef struct ovrMobile ovrMobile; +typedef struct ANativeWindow ANativeWindow; + +class OculusMobileDisplayPlugin : public HmdDisplayPlugin, public ovr::VrHandler { + using Parent = HmdDisplayPlugin; +public: + OculusMobileDisplayPlugin(); + virtual ~OculusMobileDisplayPlugin(); + bool isSupported() const override { return true; }; + bool hasAsyncReprojection() const override { return true; } + bool getSupportsAutoSwitch() override final { return false; } + QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; } + + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; + + // Stereo specific methods + void resetSensors() override final; + bool beginFrameRender(uint32_t frameIndex) override; + + QRectF getPlayAreaRect() override; + float getTargetFrameRate() const override; + void init() override; + void deinit() override; + +protected: + const QString getName() const override { return NAME; } + + bool internalActivate() override; + void internalDeactivate() override; + + void customizeContext() override; + void uncustomizeContext() override; + + void updatePresentPose() override; + void internalPresent() override; + void hmdPresent() override { throw std::runtime_error("Unused"); } + bool isHmdMounted() const override; + + static const char* NAME; + mutable gl::Context* _mainContext{ nullptr }; + uint32_t _readFbo; +}; + diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 13a1328065..66ce5f32bf 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -124,6 +124,11 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _rigidBody->setGravity(_currentGravity * _currentUp); // set flag to enable custom contactAddedCallback _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); + + // enable CCD + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); @@ -455,6 +460,12 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const // it's ok to change offset immediately -- there are no thread safety issues here _shapeLocalOffset = minCorner + 0.5f * scale; + + if (_rigidBody) { + // update CCD with new _radius + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + } } void CharacterController::setCollisionless(bool collisionless) { @@ -672,7 +683,7 @@ void CharacterController::updateState() { return; } if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) { - SET_STATE(CharacterController::State::Hover, "recomputeFlying"); + SET_STATE(CharacterController::State::Hover, "recomputeFlying"); _hasSupport = false; _stepHeight = _minStepHeight; // clears memory of last step obstacle _pendingFlags &= ~PENDING_FLAG_RECOMPUTE_FLYING; @@ -784,15 +795,13 @@ void CharacterController::updateState() { // 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) { + if (!_flyingAllowed) { SET_STATE(State::InAir, "flying not allowed"); } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { SET_STATE(State::InAir, "near ground"); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a82931064a..ce9cb20c21 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -115,17 +115,30 @@ void EntityMotionState::updateServerPhysicsVariables() { } void EntityMotionState::handleDeactivation() { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero + if (_entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + // Some non-physical event (script-call or network-packet) has modified the entity's transform and/or velocities + // at the last minute before deactivation --> the values stored in _server* and _body are stale. + // We assume the EntityMotionState is the last to know, so we copy from EntityItem and let things sort themselves out. + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); + } else { + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero + } } // virtual diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a571a81109..4453a7d9f0 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -27,6 +27,23 @@ #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" +static bool flipNormalsMyAvatarVsBackfacingTriangles(btManifoldPoint& cp, + const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) { + if (colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) { + auto triShape = static_cast(colObj1Wrap->getCollisionShape()); + const btVector3* v = triShape->m_vertices1; + btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]); + float nDotF = btDot(faceNormal, cp.m_normalWorldOnB); + if (nDotF <= 0.0f) { + faceNormal.normalize(); + // flip the contact normal to be aligned with the face normal + cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal; + } + } + // return value is currently ignored but to be future-proof: return false when not modifying friction + return false; +} PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), @@ -904,6 +921,16 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } } +void PhysicsEngine::enableGlobalContactAddedCallback(bool enabled) { + if (enabled) { + // register contact filter to help MyAvatar pass through backfacing triangles + gContactAddedCallback = flipNormalsMyAvatarVsBackfacingTriangles; + } else { + // deregister contact filter + gContactAddedCallback = nullptr; + } +} + struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject, float threshold) : btCollisionWorld::ContactResultCallback(), diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index d10be018b8..43cc0d2176 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -148,6 +148,8 @@ public: // See PhysicsCollisionGroups.h for mask flags. std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const; + void enableGlobalContactAddedCallback(bool enabled); + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index ef7a4a1749..8acbe51540 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -17,7 +17,10 @@ #include "ShapeFactory.h" +const int MAX_RING_SIZE = 256; + ShapeManager::ShapeManager() { + _garbageRing.reserve(MAX_RING_SIZE); } ShapeManager::~ShapeManager() { @@ -33,8 +36,8 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return nullptr; } - HashKey key = info.getHash(); - ShapeReference* shapeRef = _shapeMap.find(key); + HashKey hashKey(info.getHash()); + ShapeReference* shapeRef = _shapeMap.find(hashKey); if (shapeRef) { shapeRef->refCount++; return shapeRef->shape; @@ -44,23 +47,43 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { ShapeReference newRef; newRef.refCount = 1; newRef.shape = shape; - newRef.key = key; - _shapeMap.insert(key, newRef); + newRef.key = info.getHash(); + _shapeMap.insert(hashKey, newRef); } return shape; } // private helper method -bool ShapeManager::releaseShapeByKey(const HashKey& key) { - ShapeReference* shapeRef = _shapeMap.find(key); +bool ShapeManager::releaseShapeByKey(uint64_t key) { + HashKey hashKey(key); + ShapeReference* shapeRef = _shapeMap.find(hashKey); if (shapeRef) { if (shapeRef->refCount > 0) { shapeRef->refCount--; if (shapeRef->refCount == 0) { - _pendingGarbage.push_back(key); - const int MAX_SHAPE_GARBAGE_CAPACITY = 255; - if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) { - collectGarbage(); + // look for existing entry in _garbageRing + int32_t ringSize = (int32_t)(_garbageRing.size()); + for (int32_t i = 0; i < ringSize; ++i) { + int32_t j = (_ringIndex + ringSize) % ringSize; + if (_garbageRing[j] == key) { + // already on the list, don't add it again + return true; + } + } + if (ringSize == MAX_RING_SIZE) { + // remove one + HashKey hashKeyToRemove(_garbageRing[_ringIndex]); + ShapeReference* shapeRef = _shapeMap.find(hashKeyToRemove); + if (shapeRef && shapeRef->refCount == 0) { + ShapeFactory::deleteShape(shapeRef->shape); + _shapeMap.remove(hashKeyToRemove); + } + // replace at _ringIndex and advance + _garbageRing[_ringIndex] = key; + _ringIndex = (_ringIndex + 1) % ringSize; + } else { + // add one + _garbageRing.push_back(key); } } return true; @@ -87,21 +110,22 @@ bool ShapeManager::releaseShape(const btCollisionShape* shape) { } void ShapeManager::collectGarbage() { - int numShapes = _pendingGarbage.size(); + int numShapes = (int32_t)(_garbageRing.size()); for (int i = 0; i < numShapes; ++i) { - HashKey& key = _pendingGarbage[i]; + HashKey key(_garbageRing[i]); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { ShapeFactory::deleteShape(shapeRef->shape); _shapeMap.remove(key); } } - _pendingGarbage.clear(); + _ringIndex = 0; + _garbageRing.clear(); } int ShapeManager::getNumReferences(const ShapeInfo& info) const { - HashKey key = info.getHash(); - const ShapeReference* shapeRef = _shapeMap.find(key); + HashKey hashKey(info.getHash()); + const ShapeReference* shapeRef = _shapeMap.find(hashKey); if (shapeRef) { return shapeRef->refCount; } diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index d75bb1dc4a..c1fb57e017 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -12,6 +12,8 @@ #ifndef hifi_ShapeManager_h #define hifi_ShapeManager_h +#include + #include #include @@ -41,6 +43,7 @@ // later. When that list grows big enough the ShapeManager will remove any matching // entries that still have zero ref-count. + class ShapeManager { public: @@ -63,19 +66,20 @@ public: bool hasShape(const btCollisionShape* shape) const; private: - bool releaseShapeByKey(const HashKey& key); + bool releaseShapeByKey(uint64_t key); class ShapeReference { public: int refCount; const btCollisionShape* shape; - HashKey key; + uint64_t key { 0 }; ShapeReference() : refCount(0), shape(nullptr) {} }; // btHashMap is required because it supports memory alignment of the btCollisionShapes btHashMap _shapeMap; - btAlignedObjectArray _pendingGarbage; + std::vector _garbageRing; + uint32_t _ringIndex { 0 }; }; #endif // hifi_ShapeManager_h diff --git a/libraries/plugins/src/plugins/SteamClientPlugin.h b/libraries/plugins/src/plugins/SteamClientPlugin.h index fc1b85c572..2124d16b5e 100644 --- a/libraries/plugins/src/plugins/SteamClientPlugin.h +++ b/libraries/plugins/src/plugins/SteamClientPlugin.h @@ -45,6 +45,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {boolean} running - Read-only. */ diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 857a72caa8..dda35a53a2 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -23,7 +23,7 @@ enum IntersectionType { NONE = 0, ENTITY, - OVERLAY, + LOCAL_ENTITY, AVATAR, HUD }; @@ -68,6 +68,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} Ray Ray picks intersect a ray with the nearest object in front of them, along a given direction. * @property {number} Stylus Stylus picks provide "tapping" functionality on/into flat surfaces. @@ -176,7 +177,6 @@ public: virtual T getMathematicalPick() const = 0; virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0; virtual PickResultPointer getEntityIntersection(const T& pick) = 0; - virtual PickResultPointer getOverlayIntersection(const T& pick) = 0; virtual PickResultPointer getAvatarIntersection(const T& pick) = 0; virtual PickResultPointer getHUDIntersection(const T& pick) = 0; diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index e91283f02c..0bbdfea8e4 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -37,7 +37,7 @@ template class PickCacheOptimizer { public: - QVector4D update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); + QVector3D update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD); protected: typedef std::unordered_map> PickCache; @@ -67,9 +67,9 @@ void PickCacheOptimizer::cacheResult(const bool needToCompareResults, const P } template -QVector4D PickCacheOptimizer::update(std::unordered_map>& picks, +QVector3D PickCacheOptimizer::update(std::unordered_map>& picks, uint32_t& nextToUpdate, uint64_t expiry, bool shouldPickHUD) { - QVector4D numIntersectionsComputed; + QVector3D numIntersectionsComputed; PickCache results; const uint32_t INVALID_PICK_ID = 0; auto itr = picks.begin(); @@ -88,7 +88,7 @@ QVector4D PickCacheOptimizer::update(std::unordered_mapisEnabled() || pick->getMaxDistance() < 0.0f || !mathematicalPick) { pick->setPickResult(res); } else { - if (pick->getFilter().doesPickDomainEntities() || pick->getFilter().doesPickAvatarEntities()) { + if (pick->getFilter().doesPickDomainEntities() || pick->getFilter().doesPickAvatarEntities() || pick->getFilter().doesPickLocalEntities()) { PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); @@ -99,22 +99,11 @@ QVector4D PickCacheOptimizer::update(std::unordered_mapgetFilter().doesPickLocalEntities()) { - PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; - if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { - PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); - numIntersectionsComputed[1]++; - if (overlayRes) { - cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); - } - } - } - if (pick->getFilter().doesPickAvatars()) { PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); - numIntersectionsComputed[2]++; + numIntersectionsComputed[1]++; if (avatarRes) { cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); } @@ -126,7 +115,7 @@ QVector4D PickCacheOptimizer::update(std::unordered_mapgetFilter().getHUDFlags(), QVector(), QVector() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); - numIntersectionsComputed[3]++; + numIntersectionsComputed[2]++; if (hudRes) { cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); } diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 9d5971078d..8f5aa5caf8 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -61,14 +61,14 @@ public: bool getForceCoarsePicking() { return _forceCoarsePicking; } - const std::vector& getUpdatedPickCounts() { return _updatedPickCounts; } + const std::vector& getUpdatedPickCounts() { return _updatedPickCounts; } const std::vector& getTotalPickCounts() { return _totalPickCounts; } public slots: void setForceCoarsePicking(bool forceCoarsePicking) { _forceCoarsePicking = forceCoarsePicking; } protected: - std::vector _updatedPickCounts { PickQuery::NUM_PICK_TYPES }; + std::vector _updatedPickCounts { PickQuery::NUM_PICK_TYPES }; std::vector _totalPickCounts { 0, 0, 0, 0 }; bool _forceCoarsePicking { false }; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 26460cbdd7..c08a3c2be6 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -109,14 +109,14 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin if (_enabled && _hover && doHover && !_prevDoHover) { if (hoveredObject.type == ENTITY) { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); - } else if (hoveredObject.type == OVERLAY) { + } else if (hoveredObject.type == LOCAL_ENTITY) { emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == HUD) { emit pointerManager->hoverBeginHUD(hoveredEvent); } } else if (_enabled && _hover && doHover) { - if (hoveredObject.type == OVERLAY) { - if (_prevHoveredObject.type == OVERLAY) { + if (hoveredObject.type == LOCAL_ENTITY) { + if (_prevHoveredObject.type == LOCAL_ENTITY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { emit pointerManager->hoverContinueOverlay(hoveredObject.objectID, hoveredEvent); } else { @@ -150,7 +150,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } } else { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); - if (_prevHoveredObject.type == OVERLAY) { + if (_prevHoveredObject.type == LOCAL_ENTITY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { emit pointerManager->hoverEndHUD(hoveredEvent); @@ -166,7 +166,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin emit pointerManager->hoverBeginHUD(hoveredEvent); if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); - } else if (_prevHoveredObject.type == OVERLAY) { + } else if (_prevHoveredObject.type == LOCAL_ENTITY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } } @@ -175,7 +175,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin if (hoveredObject.type == NONE) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); - } else if (_prevHoveredObject.type == OVERLAY) { + } else if (_prevHoveredObject.type == LOCAL_ENTITY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { emit pointerManager->hoverEndHUD(hoveredEvent); @@ -191,7 +191,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin hoveredEvent.setShouldFocus(button == SHOULD_FOCUS_BUTTON); if (hoveredObject.type == ENTITY) { emit pointerManager->triggerBeginEntity(hoveredObject.objectID, hoveredEvent); - } else if (hoveredObject.type == OVERLAY) { + } else if (hoveredObject.type == LOCAL_ENTITY) { emit pointerManager->triggerBeginOverlay(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == HUD) { emit pointerManager->triggerBeginHUD(hoveredEvent); @@ -207,7 +207,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin triggeredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent); - } else if (_triggeredObjects[button].type == OVERLAY) { + } else if (_triggeredObjects[button].type == LOCAL_ENTITY) { emit pointerManager->triggerContinueOverlay(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == HUD) { emit pointerManager->triggerContinueHUD(triggeredEvent); @@ -222,7 +222,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin triggeredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent); - } else if (_triggeredObjects[button].type == OVERLAY) { + } else if (_triggeredObjects[button].type == LOCAL_ENTITY) { emit pointerManager->triggerEndOverlay(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == HUD) { emit pointerManager->triggerEndHUD(triggeredEvent); @@ -234,7 +234,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); - } else if (_prevHoveredObject.type == OVERLAY) { + } else if (_prevHoveredObject.type == LOCAL_ENTITY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { emit pointerManager->hoverEndHUD(hoveredEvent); diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 173163374f..28d7e42e8f 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -62,7 +62,7 @@ public: // Pointers can choose to implement these virtual void setLength(float length) {} - virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} + virtual void setLockEndUUID(const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) {} void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index 72e4b417cd..ca8dfb3f26 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -124,10 +124,10 @@ void PointerManager::setLength(unsigned int uid, float length) const { } } -void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat) const { +void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat) const { auto pointer = find(uid); if (pointer) { - pointer->setLockEndUUID(objectID, isOverlay, offsetMat); + pointer->setLockEndUUID(objectID, isAvatar, offsetMat); } } diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index 2d0b2a107e..6c1581b09b 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -37,7 +37,7 @@ public: void setIncludeItems(unsigned int uid, const QVector& includeEntities) const; void setLength(unsigned int uid, float length) const; - void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; + void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const; void update(); diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt index 6d6610a323..f3c3be687a 100644 --- a/libraries/procedural/CMakeLists.txt +++ b/libraries/procedural/CMakeLists.txt @@ -1,4 +1,3 @@ set(TARGET_NAME procedural) setup_hifi_library() link_hifi_libraries(shared gpu shaders networking graphics model-networking ktx image) - diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 05cbde374d..ff8c270371 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -104,13 +104,13 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { //} Procedural::Procedural() { - _opaqueState->setCullMode(gpu::State::CULL_BACK); + _opaqueState->setCullMode(gpu::State::CULL_NONE); _opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); _opaqueState->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _transparentState->setCullMode(gpu::State::CULL_BACK); + _transparentState->setCullMode(gpu::State::CULL_NONE); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index abab5391e2..69e6c833ee 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,21 @@ static QSize clampSize(const QSize& qsize, uint32_t maxDimension) { const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_OBJECT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; const QmlContextCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*) {}; +QQmlFileSelector* OffscreenSurface::getFileSelector() { + auto context = getSurfaceContext(); + if (!context) { + return nullptr; + } + auto engine = context->engine(); + if (!engine) { + return nullptr; + } + + return QQmlFileSelector::get(engine); +} + void OffscreenSurface::initializeEngine(QQmlEngine* engine) { + new QQmlFileSelector(engine); } using namespace hifi::qml::impl; diff --git a/libraries/qml/src/qml/OffscreenSurface.h b/libraries/qml/src/qml/OffscreenSurface.h index b3539e7709..18d24c93f7 100644 --- a/libraries/qml/src/qml/OffscreenSurface.h +++ b/libraries/qml/src/qml/OffscreenSurface.h @@ -30,6 +30,7 @@ class QQmlComponent; class QQuickWindow; class QQuickItem; class OffscreenQmlSharedObject; +class QQmlFileSelector; namespace hifi { namespace qml { @@ -72,6 +73,7 @@ public: QQuickWindow* getWindow(); QObject* getEventHandler(); QQmlContext* getSurfaceContext(); + QQmlFileSelector* getFileSelector(); // Checks to see if a new texture is available. If one is, the function returns true and // textureAndFence will be populated with the texture ID and a fence which will be signalled diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index c63350de7f..c08dd40ad8 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -48,8 +48,11 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { return getResource(url).staticCast(); } -QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { +QSharedPointer ClipCache::createResource(const QUrl& url) { qCDebug(recordingLog) << "Loading recording at" << url; return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); } +QSharedPointer ClipCache::createResourceCopy(const QSharedPointer& resource) { + return QSharedPointer(new NetworkClipLoader(*resource.staticCast().data()), &Resource::deleter); +} \ No newline at end of file diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h index 2c3465e725..202cd2f00e 100644 --- a/libraries/recording/src/recording/ClipCache.h +++ b/libraries/recording/src/recording/ClipCache.h @@ -33,6 +33,8 @@ class NetworkClipLoader : public Resource { Q_OBJECT public: NetworkClipLoader(const QUrl& url); + NetworkClipLoader(const NetworkClipLoader& other) : Resource(other), _clip(other._clip) {} + virtual void downloadFinished(const QByteArray& data) override; ClipPointer getClip() { return _clip; } bool completed() { return _failedToLoad || isLoaded(); } @@ -54,7 +56,8 @@ public slots: NetworkClipLoaderPointer getClipLoader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url) override; + QSharedPointer createResourceCopy(const QSharedPointer& resource) override; private: ClipCache(QObject* parent = nullptr); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index bf528ee5f0..8944ae7996 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -374,7 +374,7 @@ void AnimDebugDraw::update() { glm::vec4 color = std::get<3>(iter.second); for (int i = 0; i < skeleton->getNumJoints(); i++) { - const float radius = BONE_RADIUS / (absPoses[i].scale().x * rootPose.scale().x); + const float radius = BONE_RADIUS / (absPoses[i].scale() * rootPose.scale()); // draw bone addBone(rootPose, absPoses[i], radius, color, v); @@ -394,16 +394,16 @@ void AnimDebugDraw::update() { glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, color, v); + addBone(AnimPose::identity, AnimPose(1.0f, rot, pos), radius, color, v); } - AnimPose myAvatarPose(glm::vec3(1), DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); + AnimPose myAvatarPose(1.0f, DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); for (auto& iter : myAvatarMarkerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, color, v); + addBone(myAvatarPose, AnimPose(1.0f, rot, pos), radius, color, v); } // draw rays from shared DebugDraw singleton diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 81a81c5602..b70925201a 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -122,7 +122,7 @@ void CauterizedModel::updateClusterMatrices() { if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform jointTransform(jointPose.rot(), glm::vec3(jointPose.scale()), jointPose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); @@ -138,7 +138,7 @@ void CauterizedModel::updateClusterMatrices() { if (!_cauterizeBoneSet.empty()) { AnimPose cauterizePose = _rig.getJointPose(_rig.indexOfJoint("Neck")); - cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); + cauterizePose.scale() = 0.0001f; static const glm::mat4 zeroScale( glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), @@ -161,7 +161,7 @@ void CauterizedModel::updateClusterMatrices() { // not cauterized so just copy the value from the non-cauterized version. state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j]; } else { - Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + Transform jointTransform(cauterizePose.rot(), glm::vec3(cauterizePose.scale()), cauterizePose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 0bf1d5d689..e2285febe4 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -28,7 +28,7 @@ struct HazeParams { vec3 colorModulationFactor; int hazeMode; - mat4 transform; + vec3 spare; float backgroundBlend; float hazeRangeFactor; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index da8dceb176..260e25009e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1116,10 +1116,6 @@ int Model::getParentJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).parentIndex : -1; } -int Model::getLastFreeJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).freeLineage.last() : -1; -} - void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsFixupInScene = true; @@ -1368,7 +1364,7 @@ void Model::updateClusterMatrices() { if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform jointTransform(jointPose.rot(), glm::vec3(jointPose.scale()), jointPose.trans()); Transform clusterTransform; Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 16e08c2b23..c55178a21a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -298,9 +298,9 @@ public: TransformDualQuaternion() {} TransformDualQuaternion(const glm::mat4& m) { AnimPose p(m); - _scale.x = p.scale().x; - _scale.y = p.scale().y; - _scale.z = p.scale().z; + _scale.x = p.scale(); + _scale.y = p.scale(); + _scale.z = p.scale(); _scale.w = 0.0f; _dq = DualQuaternion(p.rot(), p.trans()); } @@ -379,9 +379,6 @@ protected: /// Clear the joint states void clearJointState(int index); - /// Returns the index of the last free ancestor of the indexed joint, or -1 if not found. - int getLastFreeJointIndex(int jointIndex) const; - /// \param jointIndex index of joint in model structure /// \param position[out] position of joint in model-frame /// \return true if joint exists diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 40724cbf5a..64037d64b6 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -45,13 +45,13 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); } -DrawOverlay3D::DrawOverlay3D(bool opaque) : +DrawLayered3D::DrawLayered3D(bool opaque) : _shapePlumber(std::make_shared()), _opaquePass(opaque) { initForwardPipelines(*_shapePlumber); } -void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { +void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -70,7 +70,7 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& // Needs to be distinct from the other batch because using the clear call // while stereo is enabled triggers a warning if (_opaquePass) { - gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawLayered3D::run::clear", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); }); @@ -78,7 +78,7 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& if (!inItems.empty()) { // Render the items - gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawLayered3D::main", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 29f195ffff..4f8f53257d 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -40,7 +40,7 @@ public: protected: }; -class DrawOverlay3DConfig : public render::Job::Config { +class DrawLayered3DConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) @@ -58,13 +58,13 @@ protected: int numDrawn{ 0 }; }; -class DrawOverlay3D { +class DrawLayered3D { public: using Inputs = render::VaryingSet3; - using Config = DrawOverlay3DConfig; - using JobModel = render::Job::ModelI; + using Config = DrawLayered3DConfig; + using JobModel = render::Job::ModelI; - DrawOverlay3D(bool opaque); + DrawLayered3D(bool opaque); void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index a685f3998e..673a165105 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -116,13 +116,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto& items = fetchedItems.get0(); - // Extract opaques / transparents / lights / metas / overlays / background + // Extract opaques / transparents / lights / metas / layered / background const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; - const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; - const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; - const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; - const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + const auto& inFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& inFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + const auto& hudOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + const auto& hudTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; // Lighting model comes next, the big configuration of the view const auto& lightingModel = inputs[1]; @@ -227,12 +227,12 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("HighlightRangeTimer", outlineRangeTimer); // Layered Over (in front) - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, jitter).asVarying(); + const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, jitter).asVarying(); + task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); + task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); - const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); + const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneLayeredAntialiasing"); // AA job before bloom to limit flickering const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, lightingFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); @@ -257,14 +257,14 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Upscale to finale resolution const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", scaledPrimaryFramebuffer); - // Composite the HUD and HUD overlays + // Composite the HUD and HUD layered objects task.addJob("HUD"); const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel, nullJitter).asVarying(); - const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel, nullJitter).asVarying(); - task.addJob("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); - task.addJob("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); + const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, nullJitter).asVarying(); + const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, nullJitter).asVarying(); + task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); + task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); @@ -283,15 +283,15 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input const auto& fetchCullSortTaskOut = inputs.get0(); const auto& items = fetchCullSortTaskOut.get0(); - // Extract opaques / transparents / lights / metas / overlays InFront and HUD / background + // Extract opaques / transparents / lights / metas / layered / background const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; const auto& lights = items[RenderFetchCullSortTask::LIGHT]; const auto& metas = items[RenderFetchCullSortTask::META]; - const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; - const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; - const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; - const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + const auto& inFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& inFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + const auto& hudOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + const auto& hudTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; const auto& spatialSelection = fetchCullSortTaskOut[1]; @@ -388,14 +388,14 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input task.addJob("DrawSelectionBounds", selectedItems); } - { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer - task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); - task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); + { // Debug the bounds of the layered objects, still look at the zbuffer + task.addJob("DrawInFrontOpaqueBounds", inFrontOpaque); + task.addJob("DrawInFrontTransparentBounds", inFrontTransparent); } - { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer - task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); - task.addJob("DrawOverlayHUDTransparentBounds", overlaysHUDTransparent); + { // Debug the bounds of the layered objects, still look at the zbuffer + task.addJob("DrawHUDOpaqueBounds", hudOpaque); + task.addJob("DrawHUDTransparentBounds", hudTransparent); } // Debugging stages diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index ffdbc1c4b1..b7078a00a7 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -61,15 +61,14 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto& items = fetchedItems.get0(); - // Extract opaques / transparents / lights / metas / overlays / background + // Extract opaques / transparents / lights / metas / layered / background const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; const auto& metas = items[RenderFetchCullSortTask::META]; - const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; - const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; - // TODO: Re enable the rendering of the HUD overlayes - // const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; - // const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + const auto& inFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& inFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + const auto& hudOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + const auto& hudTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; // Lighting model comes next, the big configuration of the view const auto& lightingModel = inputs[1]; @@ -97,14 +96,12 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", framebuffer); - // Layered Overlays + // Layered const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - - // Layered Over (in front) - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullJitter).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullJitter).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, nullJitter).asVarying(); + const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, nullJitter).asVarying(); + task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); + task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); // Draw opaques forward const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); @@ -135,10 +132,14 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto toneMappingInputs = ToneMappingDeferred::Inputs(framebuffer, static_cast(nullptr) ).asVarying(); task.addJob("ToneMapping", toneMappingInputs); - // Layered Overlays - // Composite the HUD and HUD overlays + // Composite the HUD and HUD layered objects task.addJob("HUD"); + const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, nullJitter).asVarying(); + const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, nullJitter).asVarying(); + task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); + task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); + // Disable blit because we do tonemapping and compositing directly to the blit FBO // Blit! // task.addJob("Blit", framebuffer); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 85bdf0fadc..5f3763ac2a 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -579,7 +579,7 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial } else { forceDefault = true; } - schemaKey.setScattering(true); + schemaKey.setScatteringMap(true); } break; case graphics::MaterialKey::EMISSIVE_MAP_BIT: diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index d192266d7e..64a2adb5d4 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -79,8 +79,8 @@ void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& ligh void ToneMappingDeferred::configure(const Config& config) { - _toneMappingEffect.setExposure(config.exposure); - _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve); + _toneMappingEffect.setExposure(config.exposure); + _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve); } void ToneMappingDeferred::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { diff --git a/libraries/render-utils/src/toneMapping.slf b/libraries/render-utils/src/toneMapping.slf index 29f618c2f0..3fe53d9be1 100644 --- a/libraries/render-utils/src/toneMapping.slf +++ b/libraries/render-utils/src/toneMapping.slf @@ -37,7 +37,7 @@ int getToneCurve() { } LAYOUT(binding=RENDER_UTILS_TEXTURE_TM_COLOR) uniform sampler2D colorMap; - + layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render/src/render/HighlightStage.h b/libraries/render/src/render/HighlightStage.h index 5e6574840f..91d8cc3f81 100644 --- a/libraries/render/src/render/HighlightStage.h +++ b/libraries/render/src/render/HighlightStage.h @@ -107,7 +107,7 @@ namespace render { float getOccludedFillOpacity() const { return getStyle()._fillOccluded.alpha; } void setOccludedFillOpacity(float value); - std::string _selectionName{ "contextOverlayHighlightList" }; + std::string _selectionName { "contextOverlayHighlightList" }; mutable SelectionStyles _styles; const HighlightStyle& getStyle() const; diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 324f1d879f..d82fdef258 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -29,10 +29,10 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto cullInputs = CullSpatialSelection::Inputs(spatialSelection, spatialFilter).asVarying(); const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); - // Overlays are not culled - const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withoutSubMetaCulled().withTagBits(tagBits, tagMask); - const auto nonspatialFilter = render::Varying(overlayfilter); - const auto nonspatialSelection = task.addJob("FetchOverlaySelection", nonspatialFilter); + // Layered objects are not culled + const ItemFilter layeredFilter = ItemFilter::Builder().withVisible().withoutSubMetaCulled().withTagBits(tagBits, tagMask); + const auto nonspatialFilter = render::Varying(layeredFilter); + const auto nonspatialSelection = task.addJob("FetchLayeredSelection", nonspatialFilter); // Multi filter visible items into different buckets const int NUM_SPATIAL_FILTERS = 4; @@ -57,26 +57,26 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin task.addJob>("FilterSceneSelection", culledSpatialSelection, spatialFilters) .get::ItemBoundsArray>(); const auto filteredNonspatialBuckets = - task.addJob>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters) + task.addJob>("FilterLayeredSelection", nonspatialSelection, nonspatialFilters) .get::ItemBoundsArray>(); - // Extract opaques / transparents / lights / overlays + // Extract opaques / transparents / lights / layered const auto opaques = task.addJob("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]); const auto transparents = task.addJob("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto lights = filteredSpatialBuckets[LIGHT_BUCKET]; const auto metas = filteredSpatialBuckets[META_BUCKET]; - const auto overlayOpaques = task.addJob("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]); - const auto overlayTransparents = task.addJob("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET]; - // split up the overlays into 3D front, hud - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, ItemKey::Layer::LAYER_1); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, ItemKey::Layer::LAYER_1); + // split up the layered objects into 3D front, hud + const auto layeredOpaques = task.addJob("DepthSortLayaredOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]); + const auto layeredTransparents = task.addJob("DepthSortLayeredTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); + const auto filteredLayeredOpaque = task.addJob("FilterLayeredOpaque", layeredOpaques, ItemKey::Layer::LAYER_1); + const auto filteredLayeredTransparent = task.addJob("FilterLayeredTransparent", layeredTransparents, ItemKey::Layer::LAYER_1); - output = Output(BucketList{ opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, - filteredOverlaysOpaque.getN(0), filteredOverlaysTransparent.getN(0), - filteredOverlaysOpaque.getN(1), filteredOverlaysTransparent.getN(1), + output = Output(BucketList{ opaques, transparents, lights, metas, + filteredLayeredOpaque.getN(0), filteredLayeredTransparent.getN(0), + filteredLayeredOpaque.getN(1), filteredLayeredTransparent.getN(1), background }, spatialSelection); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.h b/libraries/render/src/render/RenderFetchCullSortTask.h index a75c814d91..1b1e4a5d8f 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.h +++ b/libraries/render/src/render/RenderFetchCullSortTask.h @@ -23,8 +23,6 @@ public: TRANSPARENT_SHAPE, LIGHT, META, - OVERLAY_OPAQUE_SHAPE, - OVERLAY_TRANSPARENT_SHAPE, LAYER_FRONT_OPAQUE_SHAPE, LAYER_FRONT_TRANSPARENT_SHAPE, LAYER_HUD_OPAQUE_SHAPE, diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index e05cb04532..f00c74775d 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -167,11 +167,9 @@ public: // Access the spatialized items const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; } - // Access non-spatialized items (overlays, backgrounds) + // Access non-spatialized items (layered objects, backgrounds) const ItemIDSet& getNonspatialSet() const { return _masterNonspatialSet; } - - // Access a particular Stage (empty if doesn't exist) // Thread safe StagePointer getStage(const Stage::Name& name) const; diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 0e05a563b2..07d681ca88 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -30,6 +30,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/script-engine/src/FileScriptingInterface.h b/libraries/script-engine/src/FileScriptingInterface.h index 7b833399e0..859f343ec5 100644 --- a/libraries/script-engine/src/FileScriptingInterface.h +++ b/libraries/script-engine/src/FileScriptingInterface.h @@ -21,6 +21,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 288a101234..7ad77b9b24 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -26,6 +26,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 76b7ac45e3..d24db786d0 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -40,6 +40,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index c4d576351f..6a058feea5 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -28,6 +28,7 @@ class QScriptValue; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-assignment-client */ class RecordingScriptingInterface : public QObject, public Dependency { diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index da42cf2df3..a78fa44641 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -115,6 +115,7 @@ namespace SceneScripting { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {string} backgroundMode * @property {Scene.Stage.KeyLight} keyLight @@ -178,6 +179,7 @@ namespace SceneScripting { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {boolean} shouldRenderAvatars * @property {boolean} shouldRenderEntities diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 2c88d618e1..c7fb2f8a9a 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -23,6 +23,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index fa7a8e1114..5d33a6a061 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -588,6 +588,7 @@ static void scriptableResourceFromScriptValue(const QScriptValue& value, Scripta * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8753010089..48fb4f0b83 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -104,6 +104,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 8ecfb84633..3963ad5593 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "ScriptEngine.h" #include "ScriptEngineLogging.h" @@ -476,6 +477,8 @@ ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool i scriptUrl = normalizeScriptURL(scriptFilename); } + scriptUrl = QUrl(FileUtils::selectFile(scriptUrl.toString())); + auto scriptEngine = getScriptEngine(scriptUrl); if (scriptEngine && !scriptEngine->isStopping()) { return scriptEngine; diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 4d5964e462..4db150fce5 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -32,6 +32,7 @@ class ScriptEngine; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {string} debugScriptUrl * @property {string} defaultScriptsPath diff --git a/libraries/script-engine/src/ScriptUUID.h b/libraries/script-engine/src/ScriptUUID.h index 45e6ec0ad1..548bc6a6c8 100644 --- a/libraries/script-engine/src/ScriptUUID.h +++ b/libraries/script-engine/src/ScriptUUID.h @@ -19,7 +19,7 @@ #include /**jsdoc - * A UUID (Universally Unique IDentifier) is used to uniquely identify entities, overlays, avatars, and the like. It is + * A UUID (Universally Unique IDentifier) is used to uniquely identify entities, avatars, and the like. It is * represented in JavaScript as a string in the format, {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}, where the "n"s are * hexadecimal digits. * @@ -27,6 +27,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index 2466347baa..adfb15affc 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -71,6 +71,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ScriptsModel : public QAbstractItemModel { Q_OBJECT diff --git a/libraries/script-engine/src/ScriptsModelFilter.h b/libraries/script-engine/src/ScriptsModelFilter.h index 05a76334bb..81ae4c1226 100644 --- a/libraries/script-engine/src/ScriptsModelFilter.h +++ b/libraries/script-engine/src/ScriptsModelFilter.h @@ -23,6 +23,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ScriptsModelFilter : public QSortFilterProxyModel { Q_OBJECT diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index e80d38239f..57de205066 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -21,6 +21,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-assignment-client * * @property {boolean} canKick - true if the domain server allows the node or avatar to kick (ban) avatars, diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index fe903c07e2..7887938004 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -31,6 +31,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/shared/src/CrashHelpers.cpp b/libraries/shared/src/CrashHelpers.cpp index f8ca90bc4c..1676318f3e 100644 --- a/libraries/shared/src/CrashHelpers.cpp +++ b/libraries/shared/src/CrashHelpers.cpp @@ -11,6 +11,16 @@ #include "CrashHelpers.h" +#ifdef NDEBUG +// undefine NDEBUG so doAssert() works for all builds +#undef NDEBUG +#include +#define NDEBUG +#else +#include +#endif + + namespace crash { class B; @@ -34,6 +44,11 @@ A::~A() { _b->virtualFunction(); } +// only use doAssert() for debug purposes +void doAssert(bool value) { + assert(value); +} + void pureVirtualCall() { qCDebug(shared) << "About to make a pure virtual call"; B b; diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h index ad988c8906..247aea5cde 100644 --- a/libraries/shared/src/CrashHelpers.h +++ b/libraries/shared/src/CrashHelpers.h @@ -18,6 +18,7 @@ namespace crash { +void doAssert(bool value); // works for Release void pureVirtualCall(); void doubleFree(); void nullDeref(); diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h index 65fbcb75ed..4742bb2e09 100644 --- a/libraries/shared/src/CubicHermiteSpline.h +++ b/libraries/shared/src/CubicHermiteSpline.h @@ -60,7 +60,7 @@ protected: class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor { public: - enum Constants { NUM_SUBDIVISIONS = 30 }; + enum Constants { NUM_SUBDIVISIONS = 15 }; CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() { memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1)); @@ -71,11 +71,13 @@ public: float alpha = 0.0f; float accum = 0.0f; _values[0] = 0.0f; + glm::vec3 prevValue = this->operator()(alpha); for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) { - accum += glm::distance(this->operator()(alpha), - this->operator()(alpha + DELTA)); + glm::vec3 nextValue = this->operator()(alpha + DELTA); + accum += glm::distance(prevValue, nextValue); alpha += DELTA; _values[i] = accum; + prevValue = nextValue; } } diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 81acbf554c..785e549c03 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -28,6 +28,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client */ diff --git a/libraries/shared/src/Grab.h b/libraries/shared/src/Grab.h index 5765d6fd0e..f16a80befa 100644 --- a/libraries/shared/src/Grab.h +++ b/libraries/shared/src/Grab.h @@ -26,16 +26,16 @@ public: void accumulate(glm::vec3 position, glm::quat orientation) { _position += position; _orientation = orientation; // XXX - count++; + _count++; } - glm::vec3 finalizePosition() { return count > 0 ? _position * (1.0f / count) : glm::vec3(0.0f); } + glm::vec3 finalizePosition() { return _count > 0 ? _position * (1.0f / _count) : glm::vec3(0.0f); } glm::quat finalizeOrientation() { return _orientation; } // XXX protected: glm::vec3 _position; glm::quat _orientation; - int count { 0 }; + int _count { 0 }; }; class Grab { @@ -48,7 +48,8 @@ public: _parentJointIndex(newParentJointIndex), _hand(newHand), _positionalOffset(newPositionalOffset), - _rotationalOffset(newRotationalOffset) {} + _rotationalOffset(newRotationalOffset), + _released(false) {} QByteArray toByteArray(); bool fromByteArray(const QByteArray& grabData); @@ -61,6 +62,7 @@ public: _positionalOffset = other->_positionalOffset; _rotationalOffset = other->_rotationalOffset; _actionID = other->_actionID; + _released = other->_released; return *this; } @@ -85,6 +87,9 @@ public: glm::quat getRotationalOffset() const { return _rotationalOffset; } void setRotationalOffset(glm::quat rotationalOffset) { _rotationalOffset = rotationalOffset; } + bool getReleased() const { return _released; } + void setReleased(bool value) { _released = value; } + protected: QUuid _actionID; // if an action is created in bullet for this grab, this is the ID QUuid _ownerID; // avatar ID of grabber @@ -93,6 +98,7 @@ protected: QString _hand; // "left" or "right" glm::vec3 _positionalOffset; // relative to joint glm::quat _rotationalOffset; // relative to joint + bool _released { false }; // released and scheduled for deletion }; diff --git a/libraries/shared/src/HashKey.h b/libraries/shared/src/HashKey.h index 5fce182084..446eb4c25f 100644 --- a/libraries/shared/src/HashKey.h +++ b/libraries/shared/src/HashKey.h @@ -32,17 +32,19 @@ class HashKey { public: static float getNumQuantizedValuesPerMeter(); + HashKey() {} + HashKey(uint64_t hash) : _hash(hash) {} + // These two methods are required by btHashMap. bool equals(const HashKey& other) const { return _hash == other._hash; } int32_t getHash() const { return (int32_t)((uint32_t)_hash); } - void clear() { _hash = _hashCount = 0; } - bool isNull() const { return _hash == 0 && _hashCount == 0; } + // These methods for accumulating a hash. void hashUint64(uint64_t data); void hashFloat(float data); void hashVec3(const glm::vec3& data); - uint64_t getHash64() const { return _hash; } // for debug/test purposes + uint64_t getHash64() const { return _hash; } private: uint64_t _hash { 0 }; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 65651373be..c51d9bf611 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -38,7 +38,6 @@ LogHandler::LogHandler() { } LogHandler::~LogHandler() { - flushRepeatedMessages(); } const char* stringForLogType(LogMsgType msgType) { diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 2247f4cc6a..0096cb6c90 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -25,6 +25,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @deprecated The Paths API is deprecated. Use {@link Script.resolvePath} and {@link Script.resourcesPath} instead. * @readonly diff --git a/libraries/shared/src/PickFilter.h b/libraries/shared/src/PickFilter.h index 2efd408e4f..33f6e7278a 100644 --- a/libraries/shared/src/PickFilter.h +++ b/libraries/shared/src/PickFilter.h @@ -68,7 +68,7 @@ public: // Helpers for RayPickManager Flags getEntityFlags() const { unsigned int toReturn = 0; - for (int i = DOMAIN_ENTITIES; i < LOCAL_ENTITIES; i++) { + for (int i = DOMAIN_ENTITIES; i <= LOCAL_ENTITIES; i++) { if (_flags[i]) { toReturn |= getBitMask(FlagBit(i)); } @@ -80,15 +80,6 @@ public: } return Flags(toReturn); } - Flags getOverlayFlags() const { - unsigned int toReturn = getBitMask(LOCAL_ENTITIES); - for (int i = HUD + 1; i < NUM_FLAGS; i++) { - if (_flags[i]) { - toReturn |= getBitMask(FlagBit(i)); - } - } - return Flags(toReturn); - } Flags getAvatarFlags() const { return Flags(getBitMask(AVATARS)); } Flags getHUDFlags() const { return Flags(getBitMask(HUD)); } diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 422f2dfb20..69d3f28d80 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -72,8 +72,8 @@ void PointerEvent::setButton(Button button) { * "Move". * @property {number} id - Integer number used to identify the pointer: 0 = hardware mouse, 1 = left * controller, 2 = right controller. - * @property {Vec2} pos2D - The 2D position of the event on the intersected overlay or entity XY plane, where applicable. - * @property {Vec3} pos3D - The 3D position of the event on the intersected overlay or entity, where applicable. + * @property {Vec2} pos2D - The 2D position of the event on the intersected object XY plane, where applicable. + * @property {Vec3} pos3D - The 3D position of the event on the intersected object, where applicable. * @property {Vec3} normal - The surface normal at the intersection point. * @property {Vec3} direction - The direction of the intersection ray. * @property {string} button - The name of the button pressed: None, Primary, Secondary, diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 5394a0f448..ec1126c92f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -1247,30 +1247,30 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector } } -QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { +QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTextures) { // If textures are unset, revert to original textures - if (textures.isEmpty()) { + if (newTextures.isEmpty()) { return defaultTextures; } // Legacy: a ,\n-delimited list of filename:"texturepath" - if (*textures.cbegin() != '{') { - textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + if (*newTextures.cbegin() != '{') { + newTextures = "{\"" + newTextures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; } QJsonParseError error; - QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + QJsonDocument newTexturesJson = QJsonDocument::fromJson(newTextures.toUtf8(), &error); // If textures are invalid, revert to original textures if (error.error != QJsonParseError::NoError) { - qWarning() << "Could not evaluate textures property value:" << textures; + qWarning() << "Could not evaluate textures property value:" << newTextures; return defaultTextures; } - QVariantMap texturesMap = texturesJson.toVariant().toMap(); - // If textures are unset, revert to original textures - if (texturesMap.isEmpty()) { - return defaultTextures; + QVariantMap newTexturesMap = newTexturesJson.toVariant().toMap(); + QVariantMap toReturn = defaultTextures; + for (auto& texture : newTexturesMap.keys()) { + toReturn[texture] = newTexturesMap[texture]; } - return texturesJson.toVariant().toMap(); + return toReturn; } \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 9d5bca6b9f..1edb303455 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -232,7 +232,7 @@ public: }; /**jsdoc - * A PickRay defines a vector with a starting point. It is used, for example, when finding entities or overlays that lie under a + * A PickRay defines a vector with a starting point. It is used, for example, when finding entities or avatars that lie under a * mouse click or intersect a laser beam. * * @typedef {object} PickRay @@ -351,7 +351,7 @@ public: * The depth is measured in world space, but will scale with the parent if defined. * @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group * will be considered colliding with the pick. -* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or an overlay. +* @property {Uuid} parentID - The ID of the parent, either an avatar or an entity. * @property {number} parentJointIndex - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint) * @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar. */ @@ -645,6 +645,7 @@ using MeshPointer = std::shared_ptr; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 564d79bfda..bf51e455c5 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -13,6 +13,7 @@ #include +#include "HashKey.h" #include "NumericalConstants.h" // for MILLIMETERS_PER_METER /**jsdoc @@ -96,7 +97,7 @@ void ShapeInfo::clear() { _sphereCollection.clear(); _halfExtents = glm::vec3(0.0f); _offset = glm::vec3(0.0f); - _hashKey.clear(); + _hash64 = 0; _type = SHAPE_TYPE_NONE; } @@ -131,14 +132,14 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString default: break; } - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; setHalfExtents(halfExtents); - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setSphere(float radius) { @@ -146,23 +147,24 @@ void ShapeInfo::setSphere(float radius) { _type = SHAPE_TYPE_SPHERE; radius = glm::max(radius, MIN_HALF_EXTENT); _halfExtents = glm::vec3(radius); - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setMultiSphere(const std::vector& centers, const std::vector& radiuses) { _url = ""; _type = SHAPE_TYPE_MULTISPHERE; - assert(centers.size() == radiuses.size() && centers.size() > 0); + assert(centers.size() == radiuses.size()); + assert(centers.size() > 0); for (size_t i = 0; i < centers.size(); i++) { SphereData sphere = SphereData(centers[i], radiuses[i]); _sphereCollection.push_back(sphere); } - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { _pointCollection = pointCollection; - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setCapsuleY(float radius, float cylinderHalfHeight) { @@ -171,12 +173,12 @@ void ShapeInfo::setCapsuleY(float radius, float cylinderHalfHeight) { radius = glm::max(radius, MIN_HALF_EXTENT); cylinderHalfHeight = glm::max(cylinderHalfHeight, 0.0f); _halfExtents = glm::vec3(radius, cylinderHalfHeight + radius, radius); - _hashKey.clear(); + _hash64 = 0; } void ShapeInfo::setOffset(const glm::vec3& offset) { _offset = offset; - _hashKey.clear(); + _hash64 = 0; } uint32_t ShapeInfo::getNumSubShapes() const { @@ -268,20 +270,21 @@ float ShapeInfo::computeVolume() const { return volume; } -const HashKey& ShapeInfo::getHash() const { +uint64_t ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. - if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) { + if (_hash64 == 0 && _type != SHAPE_TYPE_NONE) { + HashKey hashKey; // The key is not yet cached therefore we must compute it. - _hashKey.hashUint64((uint64_t)_type); + hashKey.hashUint64((uint64_t)_type); if (_type == SHAPE_TYPE_MULTISPHERE) { for (auto &sphereData : _sphereCollection) { - _hashKey.hashVec3(glm::vec3(sphereData)); - _hashKey.hashFloat(sphereData.w); + hashKey.hashVec3(glm::vec3(sphereData)); + hashKey.hashFloat(sphereData.w); } } else if (_type != SHAPE_TYPE_SIMPLE_HULL) { - _hashKey.hashVec3(_halfExtents); - _hashKey.hashVec3(_offset); + hashKey.hashVec3(_halfExtents); + hashKey.hashVec3(_offset); } else { // TODO: we could avoid hashing all of these points if we were to supply the ShapeInfo with a unique // descriptive string. Shapes that are uniquely described by their type and URL could just put their @@ -291,7 +294,7 @@ const HashKey& ShapeInfo::getHash() const { const int numPoints = (int)points.size(); for (int i = 0; i < numPoints; ++i) { - _hashKey.hashVec3(points[i]); + hashKey.hashVec3(points[i]); } } @@ -299,23 +302,24 @@ const HashKey& ShapeInfo::getHash() const { if (!url.isEmpty()) { QByteArray baUrl = url.toLocal8Bit(); uint32_t urlHash = qChecksum(baUrl.data(), baUrl.size()); - _hashKey.hashUint64((uint64_t)urlHash); + hashKey.hashUint64((uint64_t)urlHash); } if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) { uint64_t numHulls = (uint64_t)_pointCollection.size(); - _hashKey.hashUint64(numHulls); + hashKey.hashUint64(numHulls); } else if (_type == SHAPE_TYPE_MULTISPHERE) { uint64_t numSpheres = (uint64_t)_sphereCollection.size(); - _hashKey.hashUint64(numSpheres); + hashKey.hashUint64(numSpheres); } else if (_type == SHAPE_TYPE_SIMPLE_HULL) { - _hashKey.hashUint64(1); - } + hashKey.hashUint64(1); + } + _hash64 = hashKey.getHash64(); } - return _hashKey; + return _hash64; } void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) { _halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT)); - _hashKey.clear(); + _hash64 = 0; } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index d838d7b214..6b0f981b24 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -18,8 +18,6 @@ #include #include -#include "HashKey.h" - const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored // Bullet has a mesh generation util for convex shapes that we used to @@ -91,7 +89,7 @@ public: float computeVolume() const; - const HashKey& getHash() const; + uint64_t getHash() const; protected: void setHalfExtents(const glm::vec3& halfExtents); @@ -102,7 +100,7 @@ protected: TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); - mutable HashKey _hashKey; + mutable uint64_t _hash64; ShapeType _type = SHAPE_TYPE_NONE; }; diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index c524e3183b..d3ed79faf4 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1390,7 +1390,12 @@ void SpatiallyNestable::removeGrab(GrabPointer grab) { bool SpatiallyNestable::hasGrabs() { bool result { false }; _grabsLock.withReadLock([&] { - result = !_grabs.isEmpty(); + foreach (const GrabPointer &grab, _grabs) { + if (grab && !grab->getReleased()) { + result = true; + break; + } + } }); return result; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 319f07236b..e7a449f73f 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -218,6 +218,7 @@ public: virtual void addGrab(GrabPointer grab); virtual void removeGrab(GrabPointer grab); + virtual void disableGrab(GrabPointer grab) {}; bool hasGrabs(); virtual QUuid getEditSenderID(); @@ -241,7 +242,7 @@ protected: quint64 _rotationChanged { 0 }; mutable ReadWriteLockable _grabsLock; - QSet _grabs; + QSet _grabs; // upon this thing private: SpatiallyNestable() = delete; diff --git a/libraries/shared/src/VariantMapToScriptValue.cpp b/libraries/shared/src/VariantMapToScriptValue.cpp index 008c3a5d9b..1a747a4e5b 100644 --- a/libraries/shared/src/VariantMapToScriptValue.cpp +++ b/libraries/shared/src/VariantMapToScriptValue.cpp @@ -41,6 +41,9 @@ QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine) break; } default: + if (qValue.canConvert()) { + return qValue.toFloat(); + } qCDebug(shared) << "unhandled QScript type" << qValue.type(); break; } diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h index d14489b92c..31e6228bb9 100644 --- a/libraries/shared/src/shared/Camera.h +++ b/libraries/shared/src/shared/Camera.h @@ -43,6 +43,7 @@ class Camera : public QObject { * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {Vec3} position - The position of the camera. You can set this value only when the camera is in independent * mode. diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 0709a53602..f2a4925351 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -30,9 +30,18 @@ const QStringList& FileUtils::getFileSelectors() { static std::once_flag once; static QStringList extraSelectors; std::call_once(once, [] { + +#if defined(Q_OS_ANDROID) + extraSelectors << "android_" HIFI_ANDROID_APP; +#endif + #if defined(USE_GLES) extraSelectors << "gles"; #endif + +#ifndef Q_OS_ANDROID + extraSelectors << "webengine"; +#endif }); return extraSelectors; diff --git a/libraries/task/src/task/Config.h b/libraries/task/src/task/Config.h index da9b95a274..486b28b6b9 100644 --- a/libraries/task/src/task/Config.h +++ b/libraries/task/src/task/Config.h @@ -196,6 +196,7 @@ public: * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {number} cpuRunTime - Read-only. * @property {boolean} enabled diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h index a25d559557..22a3df7530 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/libraries/ui/src/InteractiveWindow.h @@ -44,7 +44,8 @@ using namespace InteractiveWindowEnums; * @class InteractiveWindow * * @hifi-interface - * @hifi-client-en + * @hifi-client-entity + * @hifi-avatar * * @property {string} title * @property {Vec2} position diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index a719593df2..137cffde94 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -36,6 +36,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * @property {boolean} navigationFocused * @property {boolean} navigationFocusDisabled */ diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index e3aea22e3d..770f8ec965 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -17,6 +17,7 @@ * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {string} url - Read-only. * @property {Vec2} position diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 18ee1fedd5..8aad9a8b36 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -24,7 +24,8 @@ class QScriptContext; * @param {OverlayWindow.Properties} [properties=null] * * @hifi-interface - * @hifi-client-en + * @hifi-client-entity + * @hifi-avatar * * @property {Vec2} position * @property {Vec2} size diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index f67a356078..ec0a011bd0 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -49,6 +49,7 @@ #include #include "SecurityImageProvider.h" +#include "shared/FileUtils.h" #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" @@ -237,7 +238,12 @@ void OffscreenQmlSurface::clearFocusItem() { void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); - new QQmlFileSelector(engine); + auto fileSelector = QQmlFileSelector::get(engine); + if (!fileSelector) { + fileSelector = new QQmlFileSelector(engine); + } + fileSelector->setExtraSelectors(FileUtils::getFileSelectors()); + static std::once_flag once; std::call_once(once, [] { qRegisterMetaType(); @@ -713,11 +719,7 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n } void OffscreenQmlSurface::emitScriptEvent(const QVariant& message) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); - } else { - emit scriptEventReceived(message); - } + emit scriptEventReceived(message); } void OffscreenQmlSurface::emitWebEvent(const QVariant& message) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 9821ad1263..1a6df83d93 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -43,12 +43,14 @@ class OffscreenQmlSurface; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ /**jsdoc * @namespace tabletInterface * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @deprecated This API is deprecated and will be removed. Use {@link Tablet} instead. */ @@ -208,6 +210,7 @@ Q_DECLARE_METATYPE(TabletButtonsProxyModel*); * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {string} name - Name of this tablet. Read-only. * @property {boolean} toolbarMode - Used to transition this tablet into and out of toolbar mode. @@ -456,6 +459,7 @@ Q_DECLARE_METATYPE(TabletProxy*); * * @hifi-interface * @hifi-client-entity + * @hifi-avatar * * @property {Uuid} uuid - Uniquely identifies this button. Read-only. * @property {TabletButtonProxy.ButtonProperties} properties diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index 777eeba9dd..409ea28fdc 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -24,6 +24,7 @@ class QQuickItem; * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ToolbarButtonProxy : public QmlWrapper { Q_OBJECT @@ -83,6 +84,7 @@ Q_DECLARE_METATYPE(ToolbarButtonProxy*); * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ToolbarProxy : public QmlWrapper { Q_OBJECT @@ -136,6 +138,7 @@ Q_DECLARE_METATYPE(ToolbarProxy*); * * @hifi-interface * @hifi-client-entity + * @hifi-avatar */ class ToolbarScriptingInterface : public QObject, public Dependency { Q_OBJECT diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index a34e647a5e..df01591639 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -138,6 +138,10 @@ void OculusDisplayPlugin::hmdPresent() { return; } + if (!_currentFrame) { + return; + } + PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) { diff --git a/prebuild.py b/prebuild.py index fb54b8d6fe..060e1fd3b0 100644 --- a/prebuild.py +++ b/prebuild.py @@ -35,9 +35,50 @@ import re import tempfile import time import functools +import subprocess +import logging + +from uuid import uuid4 +from contextlib import contextmanager print = functools.partial(print, flush=True) +class TrackableLogger(logging.Logger): + guid = str(uuid4()) + + def _log(self, msg, *args, **kwargs): + x = {'guid': self.guid} + if 'extra' in kwargs: + kwargs['extra'].update(x) + else: + kwargs['extra'] = x + super()._log(msg, *args, **kwargs) + +logging.setLoggerClass(TrackableLogger) +logger = logging.getLogger('prebuild') + +def headSha(): + repo_dir = os.path.dirname(os.path.abspath(__file__)) + git = subprocess.Popen( + 'git rev-parse --short HEAD', + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=True, cwd=repo_dir, universal_newlines=True, + ) + stdout, _ = git.communicate() + sha = stdout.split('\n')[0] + if not sha: + raise RuntimeError("couldn't find git sha") + return sha + +@contextmanager +def timer(name): + ''' Print the elapsed time a context's execution takes to execute ''' + start = time.time() + yield + # Please take care when modifiying this print statement. + # Log parsing logic may depend on it. + logger.info('%s took %.3f secs' % (name, time.time() - start)) + def parse_args(): # our custom ports, relative to the script location defaultPortsPath = hifi_utils.scriptRelative('cmake', 'ports') @@ -50,6 +91,7 @@ def parse_args(): parser.add_argument('--vcpkg-root', type=str, help='The location of the vcpkg distribution') parser.add_argument('--build-root', required=True, type=str, help='The location of the cmake build') parser.add_argument('--ports-path', type=str, default=defaultPortsPath) + parser.add_argument('--ci-build', action='store_true') if True: args = parser.parse_args() else: @@ -66,11 +108,19 @@ def main(): del os.environ[var] args = parse_args() + + if args.ci_build: + logging.basicConfig(datefmt='%s', format='%(asctime)s %(guid)s %(message)s', level=logging.INFO) + + logger.info('sha=%s' % headSha()) + logger.info('start') + # Only allow one instance of the program to run at a time pm = hifi_vcpkg.VcpkgRepo(args) with hifi_singleton.Singleton(pm.lockFile) as lock: - if not pm.upToDate(): - pm.bootstrap() + with timer('Bootstraping'): + if not pm.upToDate(): + pm.bootstrap() # Always write the tag, even if we changed nothing. This # allows vcpkg to reclaim disk space by identifying directories with @@ -80,11 +130,13 @@ def main(): # Grab our required dependencies: # * build host tools, like spirv-cross and scribe # * build client dependencies like openssl and nvtt - pm.setupDependencies() + with timer('Setting up dependencies'): + pm.setupDependencies() # wipe out the build directories (after writing the tag, since failure # here shouldn't invalidte the vcpkg install) - pm.cleanBuilds() + with timer('Cleaning builds'): + pm.cleanBuilds() # If we're running in android mode, we also need to grab a bunch of additional binaries # (this logic is all migrated from the old setupDependencies tasks in gradle) @@ -98,7 +150,10 @@ def main(): hifi_android.QtPackager(appPath, qtPath).bundle() # Write the vcpkg config to the build directory last - pm.writeConfig() + with timer('Writing configuration'): + pm.writeConfig() + + logger.info('end') print(sys.argv) main() diff --git a/scripts/+android/defaultScripts.js b/scripts/+android_interface/defaultScripts.js similarity index 93% rename from scripts/+android/defaultScripts.js rename to scripts/+android_interface/defaultScripts.js index 8950af808d..e6971f5a6b 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android_interface/defaultScripts.js @@ -13,10 +13,10 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", - "system/+android/touchscreenvirtualpad.js", - "system/+android/actionbar.js", - "system/+android/audio.js" , - "system/+android/modes.js"/*, + "system/+android_interface/touchscreenvirtualpad.js", + "system/+android_interface/actionbar.js", + "system/+android_interface/audio.js" , + "system/+android_interface/modes.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", @@ -33,7 +33,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEBUG_SCRIPTS = [ - "system/+android/stats.js" + "system/+android_interface/stats.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ ]; diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js new file mode 100644 index 0000000000..d22716302c --- /dev/null +++ b/scripts/+android_questInterface/defaultScripts.js @@ -0,0 +1,119 @@ +"use strict"; +/* jslint vars: true, plusplus: true */ + +// +// defaultScripts.js +// examples +// +// 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 +// + +var DEFAULT_SCRIPTS_COMBINED = [ + "system/request-service.js", + "system/progress.js", + //"system/away.js", + "system/hmd.js", + "system/menu.js", + "system/bubble.js", + "system/pal.js", // "system/mod.js", // older UX, if you prefer + "system/avatarapp.js", + "system/makeUserConnection.js", + "system/tablet-goto.js", + "system/notifications.js", + "system/commerce/wallet.js", + "system/dialTone.js", + "system/quickGoto.js", + "system/firstPersonHMD.js", + "system/tablet-ui/tabletUI.js", + "system/miniTablet.js" +]; +var DEFAULT_SCRIPTS_SEPARATE = [ + "system/controllers/controllerScripts.js", + //"system/chat.js" +]; + +if (Window.interstitialModeEnabled) { + // Insert interstitial scripts at front so that they're started first. + DEFAULT_SCRIPTS_COMBINED.splice(0, 0, "system/interstitialPage.js", "system/redirectOverlays.js"); +} + +// add a menu item for debugging +var MENU_CATEGORY = "Developer > Scripting"; +var MENU_ITEM = "Debug defaultScripts.js"; + +var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); + +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; +} + +if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { + Menu.addMenuItem({ + menuName: MENU_CATEGORY, + menuItemName: MENU_ITEM, + isCheckable: true, + isChecked: previousSetting, + }); +} + +function loadSeparateDefaults() { + for (var i in DEFAULT_SCRIPTS_SEPARATE) { + Script.load(DEFAULT_SCRIPTS_SEPARATE[i]); + } +} + +function runDefaultsTogether() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.include(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.load(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +// start all scripts +if (Menu.isOptionChecked(MENU_ITEM)) { + // we're debugging individual default scripts + // so we load each into its own ScriptEngine instance + runDefaultsSeparately(); +} else { + // include all default scripts into this ScriptEngine + runDefaultsTogether(); +} + +function menuItemEvent(menuItem) { + if (menuItem === MENU_ITEM) { + var isChecked = Menu.isOptionChecked(MENU_ITEM); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); + } + Menu.triggerOption("Reload All Scripts"); + } +} + +function removeMenuItem() { + if (!Menu.isOptionChecked(MENU_ITEM)) { + Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + } +} + +Script.scriptEnding.connect(function() { + removeMenuItem(); +}); + +Menu.menuItemEvent.connect(menuItemEvent); diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index aef533b7a3..6d98e96780 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -123,7 +123,6 @@ Rectangle { anchors.right: parent.right } } - Item { height: childrenRect.height anchors.left: parent.left @@ -134,18 +133,17 @@ Rectangle { anchors.left: parent.left } - HifiControls.ComboBox { + ComboBox { anchors.right: parent.right currentIndex: 1 - model: ListModel { - id: cbItems - ListElement { text: "RGB"; color: "Yellow" } - ListElement { text: "SRGB"; color: "Green" } - ListElement { text: "Reinhard"; color: "Yellow" } - ListElement { text: "Filmic"; color: "White" } - } + model: [ + "RGB", + "SRGB", + "Reinhard", + "Filmic", + ] width: 200 - onCurrentIndexChanged: { render.mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex } + onCurrentIndexChanged: { render.mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex; } } } } @@ -170,7 +168,7 @@ Rectangle { framebuffer.config.mode = mode; } - HifiControls.ComboBox { + ComboBox { anchors.right: parent.right currentIndex: 0 model: ListModel { diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android_interface/actionbar.js similarity index 91% rename from scripts/system/+android/actionbar.js rename to scripts/system/+android_interface/actionbar.js index e7e2459e69..74b3896a62 100644 --- a/scripts/system/+android/actionbar.js +++ b/scripts/system/+android_interface/actionbar.js @@ -26,8 +26,8 @@ function init() { qml: "hifi/ActionBar.qml" }); backButton = actionbar.addButton({ - icon: "icons/+android/backward.svg", - activeIcon: "icons/+android/backward.svg", + icon: "icons/+android_interface/backward.svg", + activeIcon: "icons/+android_interface/backward.svg", text: "", bgOpacity: 0.0, hoverBgOpacity: 0.0, diff --git a/scripts/system/+android/audio.js b/scripts/system/+android_interface/audio.js similarity index 100% rename from scripts/system/+android/audio.js rename to scripts/system/+android_interface/audio.js diff --git a/scripts/system/+android/clickWeb.js b/scripts/system/+android_interface/clickWeb.js similarity index 100% rename from scripts/system/+android/clickWeb.js rename to scripts/system/+android_interface/clickWeb.js diff --git a/scripts/system/+android/displayNames.js b/scripts/system/+android_interface/displayNames.js similarity index 100% rename from scripts/system/+android/displayNames.js rename to scripts/system/+android_interface/displayNames.js diff --git a/scripts/system/+android/modes.js b/scripts/system/+android_interface/modes.js similarity index 100% rename from scripts/system/+android/modes.js rename to scripts/system/+android_interface/modes.js diff --git a/scripts/system/+android/radar.js b/scripts/system/+android_interface/radar.js similarity index 100% rename from scripts/system/+android/radar.js rename to scripts/system/+android_interface/radar.js diff --git a/scripts/system/+android/stats.js b/scripts/system/+android_interface/stats.js similarity index 100% rename from scripts/system/+android/stats.js rename to scripts/system/+android_interface/stats.js diff --git a/scripts/system/+android/touchscreenvirtualpad.js b/scripts/system/+android_interface/touchscreenvirtualpad.js similarity index 100% rename from scripts/system/+android/touchscreenvirtualpad.js rename to scripts/system/+android_interface/touchscreenvirtualpad.js diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android_interface/uniqueColor.js similarity index 100% rename from scripts/system/+android/uniqueColor.js rename to scripts/system/+android_interface/uniqueColor.js diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 19efdc042c..17ff918243 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -379,21 +379,16 @@ function deleteSendMoneyParticleEffect() { function onUsernameChanged() { } -var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); -var METAVERSE_SERVER_URL = Account.metaverseServerURL; -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } function setCertificateInfo(itemCertificateId) { @@ -425,10 +420,10 @@ function fromQml(message) { case 'purchases': case 'marketplace cta': case 'mainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; - default: // User needs to return to an individual marketplace item URL - ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + default: + openMarketplace(); break; } break; @@ -440,13 +435,13 @@ function fromQml(message) { case 'maybeEnableHmdPreview': break; // do nothing here, handled in marketplaces.js case 'transactionHistory_linkClicked': - ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'goToMarketplaceMainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'goToMarketplaceItemPage': - ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'refreshConnections': print('Refreshing Connections...'); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index f059b91e81..b19873a049 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -681,6 +681,9 @@ function loaded() { if (isNullOrEmpty(valueB)) { return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1); } + if (typeof(valueA) === "string") { + return valueA.localeCompare(valueB); + } return valueA < valueB ? -1 : 1; }); }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index ee95312fa4..c1a8f363b5 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2328,7 +2328,7 @@ function createTextureProperty(property, elProperty) { elInput.setAttribute("id", elementID); elInput.setAttribute("type", "text"); - let imageLoad = _.debounce(function (url) { + let imageLoad = function(url) { if (url.slice(0, 5).toLowerCase() === "atp:/") { elImage.src = ""; elImage.style.display = "none"; @@ -2348,15 +2348,12 @@ function createTextureProperty(property, elProperty) { elDiv.classList.remove("no-preview"); elDiv.classList.add("no-texture"); } - }, IMAGE_DEBOUNCE_TIMEOUT); - elInput.imageLoad = imageLoad; - elInput.oninput = function (event) { - // Add throttle - let url = event.target.value; - imageLoad(url); - updateProperty(property.name, url, property.isParticleProperty) }; - elInput.onchange = elInput.oninput; + elInput.imageLoad = imageLoad; + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + elInput.addEventListener('change', function(ev) { + imageLoad(ev.target.value); + }); elProperty.appendChild(elInput); elProperty.appendChild(elDiv); @@ -3018,6 +3015,13 @@ function toggleDropdown(event) { element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); } +function closeAllDropdowns() { + elDropdowns = document.querySelectorAll("div.dropdown > dl"); + for (let i = 0; i < elDropdowns.length; ++i) { + elDropdowns[i].setAttribute('dropped', 'false'); + } +} + function setDropdownValue(event) { let dt = event.target.parentNode.parentNode.previousSibling; dt.value = event.target.getAttribute("value"); @@ -3780,6 +3784,8 @@ function loaded() { property.elInput = dt; dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); } + + document.addEventListener('click', function(ev) { closeAllDropdowns() }, true); elDropdowns = document.getElementsByTagName("select"); while (elDropdowns.length > 0) { diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index f1931192e4..8d408169ba 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -19,6 +19,7 @@ var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; + var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -72,7 +73,16 @@ // Footer actions. $("#back-button").on("click", function () { - (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); + if (document.referrer !== "") { + window.history.back(); + } else { + var params = { type: GOTO_MARKETPLACE }; + var itemIdMatch = location.search.match(/itemId=([^&]*)/); + if (itemIdMatch && itemIdMatch.length === 2) { + params.itemId = itemIdMatch[1]; + } + EventBridge.emitWebEvent(JSON.stringify(params)); + } }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(JSON.stringify({ @@ -93,7 +103,9 @@ window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = marketplaceBaseURL + "/marketplace?"; + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); }); } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index f29edd8ff9..c0e3178521 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -23,7 +23,6 @@ var SENSOR_TO_ROOM_MATRIX = -2; var CAMERA_MATRIX = -7; var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0}; var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0}; -var ROT_LANDSCAPE_WINDOW = {x: 0.0, y: 0.0, z: 0.0, w: 0}; var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; var INCHES_TO_METERS = 1 / 39.3701; @@ -170,8 +169,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { visible: visible }); - // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here - var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var homeButtonDim = 4.0 * tabletScaleFactor / 1.5; var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleFactor; var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)); var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleFactor; @@ -196,7 +194,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) { color: {red: 255, green: 255, blue: 255}, solid: true, innerRadius: 0.9, - ignoreIntersection: true, + ignorePickIntersection: true, alpha: 0.0, visible: visible, drawInFront: false, @@ -224,23 +222,6 @@ WebTablet = function (url, width, dpi, hand, location, visible) { } }; - this.myOnHoverEnterOverlay = function (overlayID, pointerEvent) { - _this.onHoverEnterOverlay(overlayID, pointerEvent); - }; - - Overlays.hoverEnterOverlay.connect(this.myOnHoverEnterOverlay); - - this.myOnHoverLeaveOverlay = function (overlayID, pointerEvent) { - _this.onHoverLeaveOverlay(overlayID, pointerEvent); - }; - - Overlays.hoverLeaveOverlay.connect(this.myOnHoverLeaveOverlay); - - this.myOnHoverOverOverlay = function (overlayID, pointerEvent) { - _this.onHoverOverOverlay(overlayID, pointerEvent); - }; - Overlays.hoverOverOverlay.connect(this.myOnHoverOverOverlay); - this.state = "idle"; this.getRoot = function() { @@ -304,16 +285,19 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { this.landscape = newLandscapeValue; var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation); - Overlays.editOverlay(this.tabletEntityID, - { rotation: Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + var tabletRotation = Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180); + Overlays.editOverlay(this.tabletEntityID, { + rotation: tabletRotation + }); var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; var screenWidth = 0.9275 * tabletWidth; var screenHeight = 0.8983 * tabletHeight; + var screenRotation = Quat.angleAxis(180, Vec3.UP); Overlays.editOverlay(this.webOverlayID, { - rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW), + localRotation: this.landscape ? Quat.multiply(screenRotation, Quat.angleAxis(-90, Vec3.FRONT)) : screenRotation, dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; @@ -350,10 +334,6 @@ WebTablet.prototype.setWidth = function (width) { }; WebTablet.prototype.destroy = function () { - Overlays.hoverEnterOverlay.disconnect(this.myOnHoverEnterOverlay); - Overlays.hoverLeaveOverlay.disconnect(this.myOnHoverLeaveOverlay); - Overlays.hoverOverOverlay.disconnect(this.myOnHoverOverOverlay); - Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.tabletEntityID); Overlays.deleteOverlay(this.homeButtonID); @@ -449,24 +429,6 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos }; }; -WebTablet.prototype.onHoverEnterOverlay = function (overlayID, pointerEvent) { - if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 1.0 }); - } -}; - -WebTablet.prototype.onHoverOverOverlay = function (overlayID, pointerEvent) { - if (overlayID !== this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); - } -}; - -WebTablet.prototype.onHoverLeaveOverlay = function (overlayID, pointerEvent) { - if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); - } -}; - // compute position, rotation & parentJointIndex of the tablet WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMouse, tabletProperties) { if (HMD.active) { @@ -595,17 +557,8 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() { WebTablet.prototype.handleHomeButtonHover = function(x, y) { var pickRay = Camera.computePickRay(x, y); - var entityPickResults; - var homebuttonHovered = false; - entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); - if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || - entityPickResults.overlayID === this.tabletEntityID)) { - var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - homebuttonHovered = true; - } - } - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homebuttonHovered ? 1.0 : 0.0 }); + var homePickResult = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID]); + Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homePickResult.intersects ? 1.0 : 0.0 }); }; WebTablet.prototype.mouseMoveEvent = function (event) { diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 01e5f6e22b..39796311a0 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -822,26 +822,14 @@ SelectionDisplay = (function() { borderSize: 1.4 }); - var handlePropertiesBoundingEdge = { + var handleBoundingBox = Overlays.addOverlay("cube", { alpha: 1, color: COLOR_BOUNDING_EDGE, visible: false, - ignoreRayIntersection: true, + ignorePickIntersection: true, drawInFront: true, - lineWidth: 0.2 - }; - var handleBoundingTREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingTLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingTFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingTNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingBREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingBLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingBFEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingBNEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingNREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingNLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingFREdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); - var handleBoundingFLEdge = Overlays.addOverlay("line3d", handlePropertiesBoundingEdge); + isSolid: false + }); var handleDuplicator = Overlays.addOverlay("cube", { alpha: 1, @@ -929,26 +917,17 @@ SelectionDisplay = (function() { handleStretchYPanel, handleStretchZPanel, handleScaleCube, - handleBoundingTREdge, - handleBoundingTLEdge, - handleBoundingTFEdge, - handleBoundingTNEdge, - handleBoundingBREdge, - handleBoundingBLEdge, - handleBoundingBFEdge, - handleBoundingBNEdge, - handleBoundingNREdge, - handleBoundingNLEdge, - handleBoundingFREdge, - handleBoundingFLEdge, + handleBoundingBox, handleDuplicator, selectionBox, iconSelectionBox, xRailOverlay, yRailOverlay, zRailOverlay - ]; + + const nonLayeredOverlays = [selectionBox, iconSelectionBox]; + var maximumHandleInAllOverlays = handleDuplicator; overlayNames[handleTranslateXCone] = "handleTranslateXCone"; @@ -973,18 +952,7 @@ SelectionDisplay = (function() { overlayNames[handleScaleCube] = "handleScaleCube"; - overlayNames[handleBoundingTREdge] = "handleBoundingTREdge"; - overlayNames[handleBoundingTLEdge] = "handleBoundingTLEdge"; - overlayNames[handleBoundingTFEdge] = "handleBoundingTFEdge"; - overlayNames[handleBoundingTNEdge] = "handleBoundingTNEdge"; - overlayNames[handleBoundingBREdge] = "handleBoundingBREdge"; - overlayNames[handleBoundingBLEdge] = "handleBoundingBLEdge"; - overlayNames[handleBoundingBFEdge] = "handleBoundingBFEdge"; - overlayNames[handleBoundingBNEdge] = "handleBoundingBNEdge"; - overlayNames[handleBoundingNREdge] = "handleBoundingNREdge"; - overlayNames[handleBoundingNLEdge] = "handleBoundingNLEdge"; - overlayNames[handleBoundingFREdge] = "handleBoundingFREdge"; - overlayNames[handleBoundingFLEdge] = "handleBoundingFLEdge"; + overlayNames[handleBoundingBox] = "handleBoundingBox"; overlayNames[handleDuplicator] = "handleDuplicator"; overlayNames[selectionBox] = "selectionBox"; @@ -1074,7 +1042,31 @@ SelectionDisplay = (function() { return null; } - var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); + // We want to first check the drawInFront overlays (i.e. the handles, but really everything except the selectionBoxes) + // so that you can click on them even when they're behind things + var overlayIncludesLayered = []; + var overlayIncludesNonLayered = []; + for (var i = 0; i < overlayIncludes.length; i++) { + var value = overlayIncludes[i]; + var contains = false; + for (var j = 0; j < nonLayeredOverlays.length; j++) { + if (nonLayeredOverlays[j] === value) { + contains = true; + break; + } + } + if (contains) { + overlayIncludesNonLayered.push(value); + } else { + overlayIncludesLayered.push(value); + } + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesLayered, overlayExcludes); + + if (!intersectObj.intersects && overlayIncludesNonLayered.length > 0) { + intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesNonLayered, overlayExcludes); + } if (wantDebug) { if (!overlayIncludes) { @@ -1200,7 +1192,7 @@ SelectionDisplay = (function() { that.updateHighlight = function(event) { // if no tool is active, then just look for handles to highlight... var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); + var result = testRayIntersect(pickRay, allOverlays); var pickedColor; var highlightNeeded = false; @@ -1703,40 +1695,26 @@ SelectionDisplay = (function() { dimensions: scaleCubeDimensions }); - // UPDATE BOUNDING BOX EDGES + // UPDATE BOUNDING BOX + Overlays.editOverlay(handleBoundingBox, { + position: position, + rotation: rotation, + dimensions: dimensions + }); + + // UPDATE STRETCH HIGHLIGHT PANELS var edgeOffsetX = BOUNDING_EDGE_OFFSET * dimensions.x; var edgeOffsetY = BOUNDING_EDGE_OFFSET * dimensions.y; var edgeOffsetZ = BOUNDING_EDGE_OFFSET * dimensions.z; - var LBNPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; - LBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBNPosition)); - var RBNPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: -edgeOffsetZ }; - RBNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBNPosition)); - var LBFPosition = { x: -edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; - LBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LBFPosition)); var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); - var LTFPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; - LTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTFPosition)); - var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; - RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); - Overlays.editOverlay(handleBoundingTREdge, { start: RTNPosition, end: RTFPosition }); - Overlays.editOverlay(handleBoundingTLEdge, { start: LTNPosition, end: LTFPosition }); - Overlays.editOverlay(handleBoundingTFEdge, { start: LTFPosition, end: RTFPosition }); - Overlays.editOverlay(handleBoundingTNEdge, { start: LTNPosition, end: RTNPosition }); - Overlays.editOverlay(handleBoundingBREdge, { start: RBNPosition, end: RBFPosition }); - Overlays.editOverlay(handleBoundingBLEdge, { start: LBNPosition, end: LBFPosition }); - Overlays.editOverlay(handleBoundingBFEdge, { start: LBFPosition, end: RBFPosition }); - Overlays.editOverlay(handleBoundingBNEdge, { start: LBNPosition, end: RBNPosition }); - Overlays.editOverlay(handleBoundingNREdge, { start: RTNPosition, end: RBNPosition }); - Overlays.editOverlay(handleBoundingNLEdge, { start: LTNPosition, end: LBNPosition }); - Overlays.editOverlay(handleBoundingFREdge, { start: RTFPosition, end: RBFPosition }); - Overlays.editOverlay(handleBoundingFLEdge, { start: LTFPosition, end: LBFPosition }); - - // UPDATE STRETCH HIGHLIGHT PANELS + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); @@ -1867,7 +1845,7 @@ SelectionDisplay = (function() { var showOutlineForZone = (SelectionManager.selections.length === 1 && typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); - that.setHandleBoundingEdgeVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + that.setHandleBoundingBoxVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing))); @@ -1967,26 +1945,15 @@ SelectionDisplay = (function() { // FUNCTION: SET HANDLE SCALE VISIBLE that.setHandleScaleVisible = function(isVisible) { that.setHandleScaleVisible(isVisible); - that.setHandleBoundingEdgeVisible(isVisible); + that.setHandleBoundingBoxVisible(isVisible); }; that.setHandleScaleVisible = function(isVisible) { Overlays.editOverlay(handleScaleCube, { visible: isVisible }); }; - that.setHandleBoundingEdgeVisible = function(isVisible) { - Overlays.editOverlay(handleBoundingTREdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingTLEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingTFEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingTNEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingBREdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingBLEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingBFEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingBNEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingNREdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingNLEdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingFREdge, { visible: isVisible }); - Overlays.editOverlay(handleBoundingFLEdge, { visible: isVisible }); + that.setHandleBoundingBoxVisible = function(isVisible) { + Overlays.editOverlay(handleBoundingBox, { visible: isVisible }); }; // FUNCTION: SET HANDLE DUPLICATOR VISIBLE diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index c0a52a45c6..931c346299 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -412,8 +412,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update homeButton - // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here - var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var homeButtonDim = 4.0 * tabletScaleFactor / 1.5; var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor; var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride; var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; diff --git a/scripts/system/marketplaces/marketplace.js b/scripts/system/marketplaces/marketplace.js index d3e5c96739..70680acd1d 100644 --- a/scripts/system/marketplaces/marketplace.js +++ b/scripts/system/marketplaces/marketplace.js @@ -1,8 +1,8 @@ // // marketplace.js // -// Created by Eric Levin on 8 Jan 2016 -// Copyright 2016 High Fidelity, Inc. +// Created by Roxanne Skelly on 1/18/2019 +// 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 @@ -10,108 +10,27 @@ (function() { // BEGIN LOCAL_SCOPE -/* global WebTablet */ -Script.include("../libraries/WebTablet.js"); +var AppUi = Script.require('appUi'); -var toolIconUrl = Script.resolvePath("../assets/images/tools/"); +var BUTTON_NAME = "MARKET"; +var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml"; +var ui; +function startup() { -var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; -var marketplaceWindow = new OverlayWebWindow({ - title: "Marketplace", - source: "about:blank", - width: 900, - height: 700, - visible: false -}); - -var toolHeight = 50; -var toolWidth = 50; -var TOOLBAR_MARGIN_Y = 0; -var marketplaceVisible = false; -var marketplaceWebTablet; - -// We persist avatarEntity data in the .ini file, and reconsistitute it on restart. -// To keep things consistent, we pickle the tablet data in Settings, and kill any existing such on restart and domain change. -var persistenceKey = "io.highfidelity.lastDomainTablet"; - -function shouldShowWebTablet() { - var rightPose = Controller.getPoseValue(Controller.Standard.RightHand); - var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand); - var hasHydra = !!Controller.Hardware.Hydra; - return HMD.active && (leftPose.valid || rightPose.valid || hasHydra); + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: MARKETPLACE_QML_SOURCE + }); } -function showMarketplace(marketplaceID) { - var url = MARKETPLACE_URL; - if (marketplaceID) { - url = url + "/items/" + marketplaceID; - } - tablet.gotoWebScreen(url); - marketplaceVisible = true; - UserActivityLogger.openedMarketplace(); +function shutdown() { } -function hideTablet(tablet) { - if (!tablet) { - return; - } - updateButtonState(false); - tablet.unregister(); - tablet.destroy(); - marketplaceWebTablet = null; - Settings.setValue(persistenceKey, ""); -} -function clearOldTablet() { // If there was a tablet from previous domain or session, kill it and let it be recreated - -} -function hideMarketplace() { - if (marketplaceWindow.visible) { - marketplaceWindow.setVisible(false); - marketplaceWindow.setURL("about:blank"); - } else if (marketplaceWebTablet) { - - } - marketplaceVisible = false; -} - -function toggleMarketplace() { - if (marketplaceVisible) { - hideMarketplace(); - } else { - showMarketplace(); - } -} - -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - -var browseExamplesButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - text: "MARKET" -}); - -function updateButtonState(visible) { - -} -function onMarketplaceWindowVisibilityChanged() { - updateButtonState(marketplaceWindow.visible); - marketplaceVisible = marketplaceWindow.visible; -} - -function onClick() { - toggleMarketplace(); -} - -browseExamplesButton.clicked.connect(onClick); -marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged); - -clearOldTablet(); // Run once at startup, in case there's anything laying around from a crash. -// We could also optionally do something like Window.domainChanged.connect(function () {Script.setTimeout(clearOldTablet, 2000)}), -// but the HUD version stays around, so lets do the same. - -Script.scriptEnding.connect(function () { - browseExamplesButton.clicked.disconnect(onClick); - tablet.removeButton(browseExamplesButton); - marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged); -}); +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e4891a9bae..e059081741 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,12 +19,14 @@ var selectionDisplay = null; // for gridTool.js to ignore var AppUi = Script.require('appUi'); Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); +Script.include("/~/system/libraries/accountUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; // HRS FIXME "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; @@ -36,6 +38,7 @@ var CLARA_IO_STATUS = "CLARA.IO STATUS"; var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; +var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -154,18 +157,14 @@ function onMarketplaceOpen(referrer) { } } -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +function openMarketplace(optionalItem, edition) { + ui.open(MARKETPLACE_QML_PATH); + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem, edition: edition } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } // Function Name: wireQmlEventBridge() @@ -439,7 +438,9 @@ var referrerURL; // Used for updating Purchases QML var filterText; // Used for updating Purchases QML function onWebEventReceived(message) { message = JSON.parse(message); - if (message.type === GOTO_DIRECTORY) { + if (message.type === GOTO_MARKETPLACE) { + openMarketplace(message.itemId); + } else if (message.type === GOTO_DIRECTORY) { // This is the chooser between marketplaces. Only OUR markteplace // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -485,7 +486,6 @@ function onWebEventReceived(message) { } else if (message.type === "WALLET_SETUP") { setupWallet('marketplace cta'); } else if (message.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -546,11 +546,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { openWallet(); break; case 'checkout_cancelClicked': - openMarketplace(message.params); + openMarketplace(message.itemId); break; case 'header_goToPurchases': case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; ui.open(MARKETPLACE_PURCHASES_QML_PATH); break; @@ -560,6 +559,25 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_continue': openMarketplace(); break; + case 'marketplace_checkout': + ui.open(MARKETPLACE_CHECKOUT_QML_PATH); + ui.tablet.sendToQml({ + method: 'updateCheckoutQMLItemID', + params: message + }); + break; + case 'marketplace_marketplaces': + // This is the chooser between marketplaces. Only OUR markteplace + // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. + var url = MARKETPLACES_URL; + if(message.itemId) { + url = url + "?itemId=" + message.itemId + } + ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'marketplace_open_link': + ui.open(message.link); + break; case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': @@ -588,18 +606,18 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { } break; case 'header_marketplaceImageClicked': - openMarketplace(message.referrerURL); + openMarketplace(); break; case 'purchases_goToMarketplaceClicked': openMarketplace(); break; case 'updateItemClicked': - openMarketplace(message.upgradeUrl + "?edition=" + message.itemEdition); + openMarketplace(message.itemId, message.itemEdition); break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': // Should/must NOT check for wallet setup. - ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'needsLogIn_loginClicked': openLoginWindow(); @@ -624,10 +642,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': - openMarketplace(message.marketplaceUrl); + console.log("INSPECTION CERTIFICATE SHOW IN MARKETPLACE CLICKED: " + message.itemId); + openMarketplace(message.itemId); break; case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -689,7 +707,7 @@ var onMarketplaceScreen = false; var onWalletScreen = false; var onTabletScreenChanged = function onTabletScreenChanged(type, url) { ui.setCurrentVisibleScreenMetadata(type, url); - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onMarketplaceScreen = type === "QML" && url.indexOf(MARKETPLACE_QML_PATH) !== -1; onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; var onCommerceScreenNow = type === "QML" && ( @@ -699,7 +717,9 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var onMarketplaceItemTesterScreenNow = ( url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 || url === MARKETPLACE_ITEM_TESTER_QML_PATH); - + var onMarketplaceScreenNow = ( + url.indexOf(MARKETPLACE_QML_PATH) !== -1 || + url === MARKETPLACE_QML_PATH); if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen) || (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) @@ -711,7 +731,7 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; - wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen); + wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen || onMarketplaceScreenNow); if (url === MARKETPLACE_PURCHASES_QML_PATH) { // FIXME? Is there a race condition here in which the event @@ -734,11 +754,8 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { Keyboard.raised = false; } - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } + ContextOverlay.isInMarketplaceInspectionMode = false; + if (onInspectionCertificateScreen) { setCertificateInfo(contextOverlayEntity); @@ -769,16 +786,13 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var BUTTON_NAME = "MARKET"; -var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; -// Append "?" if necessary to signal injected script that it's the initial page. -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + (MARKETPLACE_URL.indexOf("?") > -1 ? "" : "?"); + var ui; function startup() { ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 9, - inject: MARKETPLACES_INJECT_SCRIPT_URL, - home: MARKETPLACE_URL_INITIAL, + home: MARKETPLACE_QML_PATH, onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived }); diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 780dacf85e..449921514c 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -114,7 +114,7 @@ uiHand = LEFT_HAND, miniUIOverlay = null, MINI_UI_HTML = Script.resolvePath("./html/miniTablet.html"), - MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865 }, + MINI_UI_DIMENSIONS = { x: 0.059, y: 0.0865, z: 0.001 }, MINI_UI_WIDTH_PIXELS = 150, METERS_TO_INCHES = 39.3701, MINI_UI_DPI = MINI_UI_WIDTH_PIXELS / (MINI_UI_DIMENSIONS.x * METERS_TO_INCHES), @@ -171,19 +171,23 @@ function updateMutedStatus() { - var isMuted = Audio.muted; - miniOverlayObject.emitScriptEvent(JSON.stringify({ - type: MUTE_MESSAGE, - on: isMuted, - icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON - })); + if (miniOverlayObject) { + var isMuted = Audio.muted; + miniOverlayObject.emitScriptEvent(JSON.stringify({ + type: MUTE_MESSAGE, + on: isMuted, + icon: isMuted ? MUTE_ON_ICON : MUTE_OFF_ICON + })); + } } function setGotoIcon() { - miniOverlayObject.emitScriptEvent(JSON.stringify({ - type: GOTO_MESSAGE, - icon: GOTO_ICON - })); + if (miniOverlayObject) { + miniOverlayObject.emitScriptEvent(JSON.stringify({ + type: GOTO_MESSAGE, + icon: GOTO_ICON + })); + } } function onWebEventReceived(data) { @@ -445,6 +449,19 @@ }); } + function checkEventBridge() { + // The miniUIOverlay overlay's overlay object is not available immediately the overlay is created so we have to + // provide a means to check for and connect it when it does become available. + if (miniOverlayObject) { + return; + } + + miniOverlayObject = Overlays.getOverlayObject(miniUIOverlay); + if (miniOverlayObject) { + miniOverlayObject.webEventReceived.connect(onWebEventReceived); + } + } + function create() { miniOverlay = Overlays.addOverlay("model", { url: MINI_MODEL, @@ -452,7 +469,7 @@ solid: true, grabbable: true, showKeyboardFocusHighlight: false, - displayInFront: true, + drawInFront: true, visible: false }); miniUIOverlay = Overlays.addOverlay("web3d", { @@ -465,14 +482,13 @@ alpha: 0, // Hide overlay while its content is being created. grabbable: false, showKeyboardFocusHighlight: false, - displayInFront: true, + drawInFront: true, visible: false }); miniUIOverlayEnabled = false; // This and alpha = 0 hides overlay while its content is being created. - miniOverlayObject = Overlays.getOverlayObject(miniUIOverlay); - miniOverlayObject.webEventReceived.connect(onWebEventReceived); + checkEventBridge(); } function destroy() { @@ -502,6 +518,7 @@ updateRotation: updateRotation, release: release, hide: hide, + checkEventBridge: checkEventBridge, destroy: destroy }; @@ -978,6 +995,8 @@ } function updateState() { + ui.checkEventBridge(); + if (STATE_MACHINE[STATE_STRINGS[miniState]].update) { STATE_MACHINE[STATE_STRINGS[miniState]].update(); } @@ -1125,4 +1144,4 @@ setUp(); Script.scriptEnding.connect(tearDown); -}()); +}()); \ No newline at end of file diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index fe950c2e2b..da28369cdd 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -135,6 +135,7 @@ EntityShape.prototype = { overlayProperties.canCastShadows = false; overlayProperties.parentID = this.entityID; overlayProperties.collisionless = true; + overlayProperties.ignorePickIntersection = true; this.entity = Entities.addEntity(overlayProperties, "local"); var PROJECTED_MATERIALS = false; this.materialEntity = Entities.addEntity({ @@ -146,6 +147,7 @@ EntityShape.prototype = { priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), + ignorePickIntersection: true, }, "local"); }, update: function() { diff --git a/scripts/system/quickGoto.js b/scripts/system/quickGoto.js new file mode 100644 index 0000000000..c5560cce83 --- /dev/null +++ b/scripts/system/quickGoto.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// quickGoto.js +// scripts/system/ +// +// Created by Dante Ruiz +// Copyright 2016 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 +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + function addGotoButton(destination) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: destination + }); + var buttonDestination = destination; + button.clicked.connect(function() { + Window.location = "hifi://" + buttonDestination; + }); + Script.scriptEnding.connect(function () { + tablet.removeButton(button); + }); + } + + addGotoButton("dev-mobile"); + addGotoButton("quest-dev"); + +}()); // END LOCAL_SCOPE diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 17999c4d8e..2af4d5f2cd 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -1,7 +1,7 @@ # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared animation gpu fbx graphics networking test-utils) + link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils) package_libraries_for_deployment() endmacro () diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 910770bb0d..708b1b2d2a 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -143,7 +143,7 @@ void AnimInverseKinematicsTests::testSingleChain() { ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"), QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"), QString("poleReferenceVectorD"), QString("poleVectorD")); - AnimNode::Triggers triggers; + AnimVariantMap triggers; // the IK solution should be: // @@ -236,7 +236,7 @@ void AnimInverseKinematicsTests::testSingleChain() { ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"), QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"), QString("poleReferenceVectorD"), QString("poleVectorD")); - AnimNode::Triggers triggers; + AnimVariantMap triggers; // the IK solution should be: // diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 01c8d1c1b6..b1926efb71 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -19,11 +19,14 @@ #include #include #include +#include #include #include +#include QTEST_MAIN(AnimTests) + const float TEST_EPSILON = 0.001f; void AnimTests::initTestCase() { @@ -33,6 +36,7 @@ void AnimTests::initTestCase() { DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); } @@ -84,26 +88,26 @@ void AnimTests::testClipEvaulate() { AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag); - AnimNode::Triggers triggers; + AnimVariantMap triggers; clip.evaluate(vars, context, framesToSec(10.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, TEST_EPSILON); // does it loop? - triggers.clear(); + triggers.clearMap(); clip.evaluate(vars, context, framesToSec(12.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, TEST_EPSILON); // Note: frame 3 and not 4, because extra frame between start and end. // did we receive a loop trigger? - QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end()); + QVERIFY(triggers.hasKey("myClipNodeOnLoop")); // does it pause at end? - triggers.clear(); + triggers.clearMap(); clip.setLoopFlagVar("FalseVar"); clip.evaluate(vars, context, framesToSec(20.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, TEST_EPSILON); // did we receive a done trigger? - QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnDone") != triggers.end()); + QVERIFY(triggers.hasKey("myClipNodeOnDone")); } void AnimTests::testClipEvaulateWithVars() { @@ -133,7 +137,7 @@ void AnimTests::testClipEvaulateWithVars() { clip.setTimeScaleVar("timeScale2"); clip.setLoopFlagVar("loopFlag2"); - AnimNode::Triggers triggers; + AnimVariantMap triggers; clip.evaluate(vars, context, framesToSec(0.1f), triggers); // verify that the values from the AnimVariantMap made it into the clipNode's @@ -284,11 +288,11 @@ void AnimTests::testAccumulateTime() { timeScale = 1.0f; float dt = 1.0f; QString id = "testNode"; - AnimNode::Triggers triggers; + AnimVariantMap triggers; float loopFlag = true; float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); // a one frame looping animation should NOT trigger onLoop events - QVERIFY(triggers.empty()); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); const uint32_t MAX_TRIGGER_COUNT = 3; @@ -296,45 +300,45 @@ void AnimTests::testAccumulateTime() { endFrame = 1.1f; timeScale = 10.0f; dt = 10.0f; - triggers.clear(); + triggers.clearMap(); loopFlag = true; resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); - // a short animation with a large dt & a large timescale, should only create a MAXIMUM of 3 loop events. - QVERIFY(triggers.size() <= MAX_TRIGGER_COUNT); + // a short animation with a large dt & a large timescale, should generate a onLoop event. + QVERIFY(triggers.hasKey("testNodeOnLoop")); } void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const { float dt = (1.0f / 30.0f) / timeScale; // sec QString id = "testNode"; - AnimNode::Triggers triggers; + AnimVariantMap triggers; bool loopFlag = false; float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 1.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 2.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 3.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // test onDone trigger and frame clamping. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnDone")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnDone")); + triggers.clearMap(); // test onLoop trigger and looping frame logic loopFlag = true; @@ -342,26 +346,26 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram // should NOT trigger loop even though we stop at last frame, because there is an extra frame between end and start frames. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // now we should hit loop trigger resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // should NOT trigger loop, even though we move past the end frame, because of extra frame between end and start. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame + 0.5f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // now we should hit loop trigger resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 0.5f); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); } void AnimTests::testAnimPose() { @@ -370,16 +374,10 @@ void AnimTests::testAnimPose() { const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); - std::vector scaleVec = { - glm::vec3(1), - glm::vec3(2.0f, 1.0f, 1.0f), - glm::vec3(1.0f, 0.5f, 1.0f), - glm::vec3(1.0f, 1.0f, 1.5f), - glm::vec3(2.0f, 0.5f, 1.5f), - glm::vec3(-2.0f, 0.5f, 1.5f), - glm::vec3(2.0f, -0.5f, 1.5f), - glm::vec3(2.0f, 0.5f, -1.5f), - glm::vec3(-2.0f, -0.5f, -1.5f), + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f }; std::vector rotVec = { @@ -409,7 +407,7 @@ void AnimTests::testAnimPose() { for (auto& trans : transVec) { // build a matrix the old fashioned way. - glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); glm::mat4 rawMat = rotTransMat * scaleMat; @@ -427,7 +425,7 @@ void AnimTests::testAnimPose() { for (auto& trans : transVec) { // build a matrix the old fashioned way. - glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); glm::mat4 rawMat = rotTransMat * scaleMat; @@ -443,6 +441,145 @@ void AnimTests::testAnimPose() { } } +void AnimTests::testAnimPoseMultiply() { + const float PI = (float)M_PI; + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f, + }; + + std::vector rotVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + -ROT_Y_180 + }; + + std::vector transVec = { + glm::vec3(), + glm::vec3(10.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 5.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 7.5f), + glm::vec3(10.0f, 5.0f, 7.5f), + glm::vec3(-10.0f, 5.0f, 7.5f), + glm::vec3(10.0f, -5.0f, 7.5f), + glm::vec3(10.0f, 5.0f, -7.5f) + }; + + const float TEST_EPSILON = 0.001f; + + std::vector matrixVec; + std::vector poseVec; + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = rotTransMat * scaleMat; + + matrixVec.push_back(rawMat); + + // use an anim pose to build a matrix by parts. + AnimPose pose(scale, rot, trans); + poseVec.push_back(pose); + } + } + } + + for (int i = 0; i < matrixVec.size(); i++) { + for (int j = 0; j < matrixVec.size(); j++) { + + // multiply the matrices together + glm::mat4 matrix = matrixVec[i] * matrixVec[j]; + + // convert to matrix (note this will remove sheer from the matrix) + AnimPose resultA(matrix); + + // multiply the poses together directly + AnimPose resultB = poseVec[i] * poseVec[j]; + + /* + qDebug() << "matrixVec[" << i << "] =" << matrixVec[i]; + qDebug() << "matrixVec[" << j << "] =" << matrixVec[j]; + qDebug() << "matrixResult =" << resultA; + + qDebug() << "poseVec[" << i << "] =" << poseVec[i]; + qDebug() << "poseVec[" << j << "] =" << poseVec[j]; + qDebug() << "poseResult =" << resultB; + */ + + // compare results. + QCOMPARE_WITH_ABS_ERROR(resultA.scale(), resultB.scale(), TEST_EPSILON); + QCOMPARE_WITH_ABS_ERROR(resultA.rot(), resultB.rot(), TEST_EPSILON); + QCOMPARE_WITH_ABS_ERROR(resultA.trans(), resultB.trans(), TEST_EPSILON); + } + } +} + +void AnimTests::testAnimPoseInverse() { + const float PI = (float)M_PI; + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + std::vector scaleVec = { + 1.0f, + 2.0f, + 0.5f + }; + + std::vector rotVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + -ROT_Y_180 + }; + + std::vector transVec = { + glm::vec3(), + glm::vec3(10.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 5.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 7.5f), + glm::vec3(10.0f, 5.0f, 7.5f), + glm::vec3(-10.0f, 5.0f, 7.5f), + glm::vec3(10.0f, -5.0f, 7.5f), + glm::vec3(10.0f, 5.0f, -7.5f) + }; + + const float TEST_EPSILON = 0.001f; + + for (auto& scale : scaleVec) { + for (auto& rot : rotVec) { + for (auto& trans : transVec) { + + // build a matrix the old fashioned way. + glm::mat4 scaleMat = glm::scale(glm::mat4(), glm::vec3(scale)); + glm::mat4 rotTransMat = createMatFromQuatAndPos(rot, trans); + glm::mat4 rawMat = glm::inverse(rotTransMat * scaleMat); + + // use an anim pose to build a matrix by parts. + AnimPose pose(scale, rot, trans); + glm::mat4 poseMat = pose.inverse(); + + QCOMPARE_WITH_ABS_ERROR(rawMat, poseMat, TEST_EPSILON); + } + } + } +} + + void AnimTests::testExpressionTokenizer() { QString str = "(10 + x) >= 20.1 && (y != !z)"; AnimExpression e("x"); diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index 439793f21d..326545b0a9 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -27,6 +27,8 @@ private slots: void testVariant(); void testAccumulateTime(); void testAnimPose(); + void testAnimPoseMultiply(); + void testAnimPoseInverse(); void testExpressionTokenizer(); void testExpressionParser(); void testExpressionEvaluator(); diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp index efc88a4032..0116eb027f 100644 --- a/tests/physics/src/ShapeInfoTests.cpp +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -55,20 +55,20 @@ void ShapeInfoTests::testHashFunctions() { // test sphere info.setSphere(radiusX); ++testCount; - HashKey key = info.getHash(); - hashPtr = hashes.find(key); + HashKey hashKey(info.getHash()); + hashPtr = hashes.find(hashKey); if (hashPtr) { std::cout << testCount << " hash collision sphere radius = " << radiusX - << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr + << " h = 0x" << std::hex << hashKey.getHash() << " : 0x" << *hashPtr << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key, key.getHash()); + hashes.insert(hashKey, hashKey.getHash()); } // track bit distribution counts to evaluate hash function randomness for (int j = 0; j < NUM_HASH_BITS; ++j) { - if (masks[j] & key.getHash()) { + if (masks[j] & hashKey.getHash()) { ++bits[j]; } } @@ -80,21 +80,21 @@ void ShapeInfoTests::testHashFunctions() { // test box info.setBox(glm::vec3(radiusX, radiusY, radiusZ)); ++testCount; - HashKey key = info.getHash(); - hashPtr = hashes.find(key); + HashKey hashKey(info.getHash()); + hashPtr = hashes.find(hashKey); if (hashPtr) { std::cout << testCount << " hash collision box dimensions = < " << radiusX << ", " << radiusY << ", " << radiusZ << " >" - << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr << " : 0x" << key.getHash64() + << " h = 0x" << std::hex << hashKey.getHash() << " : 0x" << *hashPtr << " : 0x" << hashKey.getHash64() << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key, key.getHash()); + hashes.insert(hashKey, hashKey.getHash()); } // track bit distribution counts to evaluate hash function randomness for (int k = 0; k < NUM_HASH_BITS; ++k) { - if (masks[k] & key.getHash()) { + if (masks[k] & hashKey.getHash()) { ++bits[k]; } } @@ -117,14 +117,14 @@ void ShapeInfoTests::testBoxShape() { ShapeInfo info; glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); info.setBox(halfExtents); - HashKey key = info.getHash(); + HashKey hashKey(info.getHash()); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; HashKey otherKey = otherInfo.getHash(); - QCOMPARE(key.getHash(), otherKey.getHash()); + QCOMPARE(hashKey.getHash(), otherKey.getHash()); delete shape; } @@ -133,14 +133,14 @@ void ShapeInfoTests::testSphereShape() { ShapeInfo info; float radius = 1.23f; info.setSphere(radius); - HashKey key = info.getHash(); + HashKey hashKey = info.getHash(); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; HashKey otherKey = otherInfo.getHash(); - QCOMPARE(key.getHash(), otherKey.getHash()); + QCOMPARE(hashKey.getHash(), otherKey.getHash()); delete shape; } @@ -151,14 +151,14 @@ void ShapeInfoTests::testCylinderShape() { float radius = 1.23f; float height = 4.56f; info.setCylinder(radius, height); - HashKey key = info.getHash(); + HashKey hashKey(info.getHash()); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; HashKey otherKey = otherInfo.getHash(); - QCOMPARE(key.getHash(), otherKey.getHash()); + QCOMPARE(hashKey.getHash(), otherKey.getHash()); delete shape; */ @@ -170,14 +170,14 @@ void ShapeInfoTests::testCapsuleShape() { float radius = 1.23f; float height = 4.56f; info.setCapsule(radius, height); - HashKey key = info.getHash(); + HashKey hashKey(info.getHash()); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; HashKey otherKey = otherInfo.getHash(); - QCOMPARE(key.getHash(), otherKey.getHash()); + QCOMPARE(hashKey.getHash(), otherKey.getHash()); delete shape; */ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 886f15ded4..b9ae635a4f 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -19,20 +19,36 @@ function(check_test name) endfunction() if (BUILD_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 - ) - + # Allow different tools for stable builds + if (STABLE_BUILD) + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + ) + else() + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + nitpick + ) + endif() + foreach(TOOL ${ALL_TOOLS}) check_test(${TOOL}) if (${BUILD_TOOL_RESULT}) diff --git a/tools/gpu-frame-player/CMakeLists.txt b/tools/gpu-frame-player/CMakeLists.txt index bd50839f9c..996fc859d8 100644 --- a/tools/gpu-frame-player/CMakeLists.txt +++ b/tools/gpu-frame-player/CMakeLists.txt @@ -7,7 +7,7 @@ setup_hifi_project(Gui Widgets) # link in the shared libraries link_hifi_libraries( - shared ktx shaders gpu + shared ktx shaders gpu # vk gpu-vk gl ${PLATFORM_GL_BACKEND} ) diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp index 7fbf43139f..e74caddd5e 100644 --- a/tools/gpu-frame-player/src/PlayerWindow.cpp +++ b/tools/gpu-frame-player/src/PlayerWindow.cpp @@ -64,11 +64,37 @@ void PlayerWindow::loadFrame() { } void PlayerWindow::keyPressEvent(QKeyEvent* event) { + bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); + float moveScale = isShifted ? 10.0f : 1.0f; switch (event->key()) { case Qt::Key_F1: loadFrame(); return; + case Qt::Key_W: + _renderThread.move(vec3{ 0, 0, -0.1f } * moveScale); + return; + + case Qt::Key_S: + _renderThread.move(vec3{ 0, 0, 0.1f } * moveScale); + return; + + case Qt::Key_A: + _renderThread.move(vec3{ -0.1f, 0, 0 } * moveScale); + return; + + case Qt::Key_D: + _renderThread.move(vec3{ 0.1f, 0, 0 } * moveScale); + return; + + case Qt::Key_E: + _renderThread.move(vec3{ 0, 0.1f, 0 } * moveScale); + return; + + case Qt::Key_F: + _renderThread.move(vec3{ 0, -0.1f, 0 } * moveScale); + return; + default: break; } @@ -106,5 +132,4 @@ void PlayerWindow::loadFrame(const QString& path) { } resize(size.x, size.y); } - _renderThread.submitFrame(frame); } diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index 608e8f250f..ff0d7630e5 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -20,6 +20,11 @@ void RenderThread::resize(const QSize& newSize) { _pendingSize.push(newSize); } +void RenderThread::move(const glm::vec3& v) { + std::unique_lock lock(_frameLock); + _correction = glm::inverse(glm::translate(mat4(), v)) * _correction; +} + void RenderThread::initialize(QWindow* window) { std::unique_lock lock(_frameLock); setObjectName("RenderThread"); @@ -27,9 +32,12 @@ void RenderThread::initialize(QWindow* window) { _window = window; #ifdef USE_GL + _window->setFormat(getDefaultOpenGLSurfaceFormat()); _context.setWindow(window); _context.create(); - _context.makeCurrent(); + if (!_context.makeCurrent()) { + qFatal("Unable to make context current"); + } QOpenGLContextWrapper(_context.qglContext()).makeCurrent(_window); glGenTextures(1, &_externalTexture); glBindTexture(GL_TEXTURE_2D, _externalTexture); @@ -105,6 +113,13 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { #ifdef USE_GL _context.makeCurrent(); #endif + if (_correction != glm::mat4()) { + std::unique_lock lock(_frameLock); + if (_correction != glm::mat4()) { + _backend->setCameraCorrection(_correction, _activeFrame->view); + //_prevRenderView = _correction * _activeFrame->view; + } + } _backend->recycle(); _backend->syncCache(); @@ -139,18 +154,29 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { using namespace vks::debug::marker; beginRegion(commandBuffer, "executeFrame", glm::vec4{ 1, 1, 1, 1 }); #endif + auto& glbackend = (gpu::gl::GLBackend&)(*_backend); + glm::uvec2 fboSize{ frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; + auto fbo = glbackend.getFramebufferID(frame->framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glClearColor(0, 0, 0, 1); + glClearDepth(0); + glClear(GL_DEPTH_BUFFER_BIT); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + //_gpuContext->enableStereo(true); 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); + //glDisable(GL_FRAMEBUFFER_SRGB); + //glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBlitFramebuffer( + 0, 0, fboSize.x, fboSize.y, + 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); (void)CHECK_GL_ERROR(); _context.swapBuffers(); @@ -183,11 +209,11 @@ bool RenderThread::process() { pendingFrames.swap(_pendingFrames); pendingSize.swap(_pendingSize); } - + while (!pendingFrames.empty()) { _activeFrame = pendingFrames.front(); - _gpuContext->consumeFrameUpdates(_activeFrame); pendingFrames.pop(); + _gpuContext->consumeFrameUpdates(_activeFrame); } while (!pendingSize.empty()) { diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h index 7312fece6c..09eef56623 100644 --- a/tools/gpu-frame-player/src/RenderThread.h +++ b/tools/gpu-frame-player/src/RenderThread.h @@ -55,6 +55,8 @@ public: std::queue _pendingSize; gpu::FramePointer _activeFrame; uint32_t _externalTexture{ 0 }; + void move(const glm::vec3& v); + glm::mat4 _correction; void resize(const QSize& newSize); diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 76f33e2c73..a525093965 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -107,6 +107,9 @@ exports.handlers = { if (e.doclet.hifiClientEntity) { rows.push("Client Entity Scripts"); } + if (e.doclet.hifiAvatar) { + rows.push("Avatar Scripts"); + } if (e.doclet.hifiServerEntity) { rows.push("Server Entity Scripts"); } @@ -140,6 +143,14 @@ exports.defineTags = function (dictionary) { } }); + // @hifi-avatar-script + dictionary.defineTag("hifi-avatar", { + onTagged: function (doclet, tag) { + doclet.hifiAvatar = true; + } + }); + + // @hifi-client-entity dictionary.defineTag("hifi-client-entity", { onTagged: function (doclet, tag) { diff --git a/tools/nitpick/CMakeLists.txt b/tools/nitpick/CMakeLists.txt index efb5125f69..e69b16b866 100644 --- a/tools/nitpick/CMakeLists.txt +++ b/tools/nitpick/CMakeLists.txt @@ -1,75 +1,197 @@ -set (TARGET_NAME nitpick) +set(TARGET_NAME nitpick) project(${TARGET_NAME}) -# Automatically run UIC and MOC. This replaces the older WRAP macros -SET (CMAKE_AUTOUIC ON) -SET (CMAKE_AUTOMOC ON) +set(CUSTOM_NITPICK_QRC_PATHS "") -setup_hifi_project (Core Widgets Network Xml) -link_hifi_libraries () +find_npm() -# FIX: Qt was built with -reduce-relocations -if (Qt5_POSITION_INDEPENDENT_CODE) - SET (CMAKE_POSITION_INDEPENDENT_CODE ON) -endif() +set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) +set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) +generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_NITPICK_QRC_PATHS} GLOBS *) -# Qt includes -include_directories (${CMAKE_CURRENT_SOURCE_DIR}) -include_directories (${Qt5Core_INCLUDE_DIRS}) -include_directories (${Qt5Widgets_INCLUDE_DIRS}) +add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${QT_DIR}/bin/rcc" + ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC} +) -set (QT_LIBRARIES Qt5::Core Qt5::Widgets QT::Gui Qt5::Xml) +# grab the implementation and header files from src dirs +file(GLOB_RECURSE NITPICK_SRCS "src/*.cpp" "src/*.h") +GroupSources("src") +list(APPEND NITPICK_SRCS ${RESOURCES_RCC}) -if (WIN32) - # Do not show Console - set_property (TARGET nitpick PROPERTY WIN32_EXECUTABLE true) -endif() +find_package(Qt5 COMPONENTS Widgets) -target_zlib() -add_dependency_external_projects (quazip) -find_package (QuaZip REQUIRED) -target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) - -package_libraries_for_deployment() +# grab the ui files in ui +file (GLOB_RECURSE QT_UI_FILES ui/*.ui) +source_group("UI Files" FILES ${QT_UI_FILES}) -if (WIN32) - add_paths_to_fixup_libs (${QUAZIP_DLL_PATH}) +# have qt5 wrap them and generate the appropriate header files +qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") - find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) - - if (NOT WINDEPLOYQT_COMMAND) - message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.") +# add them to the nitpick source files +set(NITPICK_SRCS ${NITPICK_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") + +if (APPLE) + # configure CMake to use a custom Info.plist + set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) + + if (PRODUCTION_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick) + else () + if (DEV_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick-dev) + elseif (PR_BUILD) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick-pr) + endif () endif () - # add a post-build command to call windeployqt to copy Qt plugins - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> \"$\"" - ) - - # add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities) - # this also copied to the containing folder, to facilitate running from Visual Studio - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" - COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity" - ) - - # add a custom command to copy the SSL DLLs - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory "$ENV{VCPKG_ROOT}/installed/x64-windows/bin" "$" - ) -elseif (APPLE) - # add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities) - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" - ) + # set how the icon shows up in the Info.plist file + set(MACOSX_BUNDLE_ICON_FILE "${NITPICK_ICON_FILENAME}") + + # set where in the bundle to put the resources file + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + # append the discovered resources to our list of nitpick sources + list(APPEND NITPICK_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME}) +endif() + +# create the executable, make it a bundle on OS X +if (APPLE) + add_executable(${TARGET_NAME} MACOSX_BUNDLE ${NITPICK_SRCS} ${QM}) + + # make sure the output name for the .app bundle is correct + # Fix up the rpath so macdeployqt works + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +elseif (WIN32) + # configure an rc file for the chosen icon + set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME}") + set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") + configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT}) + + set(APP_FULL_NAME "High Fidelity Nitpick") + set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc") + configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) + + # add an executable that also has the icon itself and the configured rc file as resources + add_executable(${TARGET_NAME} WIN32 ${NITPICK_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT} ${CONFIGURE_VERSION_INFO_RC_OUTPUT}) +else () + add_executable(${TARGET_NAME} ${NITPICK_SRCS} ${QM}) endif () +add_dependencies(${TARGET_NAME} resources) + +# disable /OPT:REF and /OPT:ICF for the Debug builds +# This will prevent the following linker warnings +# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification +if (WIN32) + set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF") +endif() + +link_hifi_libraries(entities-renderer) + +# perform standard include and linking for found externals +foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) + if (${${EXTERNAL}_UPPERCASE}_REQUIRED) + find_package(${EXTERNAL} REQUIRED) + else () + find_package(${EXTERNAL}) + endif () + + if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE}) + add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE}) + + # include the library directories (ignoring warnings) + if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS) + set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR}) + endif () + + include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) + + # perform the system include hack for OS X to ignore warnings + if (APPLE) + foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}") + endforeach() + endif () + + if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES) + set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY}) + endif () + + if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE") + target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) + elseif (APPLE AND NOT INSTALLER_BUILD) + add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") + endif () + endif () +endforeach() + +# include headers for nitpick and NitpickConfig. +include_directories("${PROJECT_SOURCE_DIR}/src") + +if (UNIX AND NOT ANDROID) + if (CMAKE_SYSTEM_NAME MATCHES "Linux") + # Linux + target_link_libraries(${TARGET_NAME} pthread atomic) + else () + # OSX + target_link_libraries(${TARGET_NAME} pthread) + endif () +endif() + +# add a custom command to copy the empty AppData High Fidelity folder (i.e. - a valid folder with no entities) +if (WIN32) + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" + ) + + if (RELEASE_TYPE STREQUAL "DEV") + # This to enable running from the IDE + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity" + ) + endif () +elseif (APPLE) + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$/AppDataHighFidelity" + ) +endif() + +if (APPLE) + # setup install of OS X nitpick bundle + install(TARGETS ${TARGET_NAME} + BUNDLE DESTINATION ${NITPICK_INSTALL_DIR} + COMPONENT ${CLIENT_COMPONENT} + ) + + # call the fixup_nitpick macro to add required bundling commands for installation + fixup_nitpick() +elseif (WIN32) + # link target to external libraries + # setup install of executable and things copied by fixup/windeployqt + install( + DIRECTORY "$/" + DESTINATION ${NITPICK_INSTALL_DIR} + COMPONENT ${CLIENT_COMPONENT} + PATTERN "*.pdb" EXCLUDE + PATTERN "*.lib" EXCLUDE + PATTERN "*.exp" EXCLUDE + ) +endif() + +if (WIN32) + set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") + + set(TARGET_INSTALL_DIR ${NITPICK_INSTALL_DIR}) + set(TARGET_INSTALL_COMPONENT ${CLIENT_COMPONENT}) + + package_libraries_for_deployment() +endif() diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index 3a664a12e9..25f9001409 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -6,89 +6,61 @@ Nitpick is a stand alone application that provides a mechanism for regression te * The result, if any test failed, is a zipped folder describing the failure. Nitpick has 5 functions, separated into separate tabs: + 1. Creating tests, MD files and recursive scripts 1. Windows task bar utility (Windows only) 1. Running tests 1. Evaluating the results of running tests 1. Web interface -## Build (for developers) -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-vXXXX.exe`) -1. Click "OK" -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-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-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-vXXXX.exe) +## Installation +`nitpick` is packaged with High Fidelity PR and Development builds. +### Windows 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. Click the "add python to path" checkbox on the python installer + 1. After installation - add the path to python.exe to the Windows PATH environment variable. 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/ - 1. Open a new command prompt and run `aws configure` + 1. Open a new command prompt and run + `aws configure` 1. Enter the AWS account number 1. Enter the secret key 1. Leave region name and ouput format as default [None] - 1. Install the latest release of Boto3 via pip: `pip install boto3` + 1. Install the latest release of Boto3 via pip: + `pip install boto3` -1. Download the installer by browsing to [here]() -1. Double click on the installer and install to a convenient location -![](./setup_7z.PNG) - -1. __To run nitpick, double click **nitpick.exe**__ -#### Mac +1. (First time) Download adb (Android Debug Bridge) from *https://dl.google.com/android/repository/platform-tools-latest-windows.zip* + 1. Copy the downloaded file to (for example) **C:\adb** and extract in place. + Verify you see *adb.exe* in **C:\adb\platform-tools\\**. + 1. After installation - add the path to adb.exe to the Windows PATH environment variable (note that it is in *adb\platform-tools*). +### Mac 1. (first time) Install brew - In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)` + In a terminal: + `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` + Note that you will need to press RETURN again, and will then be asked for your password. 1. (First time) install Qt: - In a terminal: `brew install qt` -1. (First time) install Python from https://www.python.org/downloads/release/python-370/ (**macOS 64-bit installer** or **macOS 64-bit/32-bit installer**) - 1. After installation - In a terminal: run `open "/Applications/Python 3.6/Install Certificates.command"`. This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates. + In a terminal: +`brew install qt` +1. (First time) install Python from https://www.python.org/downloads/release/python-370/ (*macOS 64-bit installer* or *macOS 64-bit/32-bit installer*) + 1. After installation - In a terminal: run + `open "/Applications/Python 3.7/Install Certificates.command"`. +This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates. 1. Verify that `/usr/local/bin/python3` exists. 1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority: -In a terminal: `curl -O https://bootstrap.pypa.io/get-pip.py` -In a terminal: `python3 get-pip.py --user` + In a terminal: + `curl -O https://bootstrap.pypa.io/get-pip.py` + In a terminal: + `python3 get-pip.py --user` 1. Use pip to install the AWS CLI. - `pip3 install awscli --upgrade --user` + `pip3 install awscli --upgrade --user` This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin - 1. Open a new command prompt and run `~/Library/Python/3.7/bin/aws configure` + 1. Open a new command prompt and run + `~/Library/Python/3.7/bin/aws configure` 1. Enter the AWS account number 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. 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-vXXXX/* .` - -1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__ + 1. Install the latest release of Boto3 via pip: pip3 install boto3 +1. (First time)Install adb (the Android Debug Bridge) - in a terminal: + `brew cask install android-platform-tools` # Usage ## Create ![](./Create.PNG) @@ -167,7 +139,7 @@ nitpick.runRecursive(); In this case all recursive scripts, from the selected folder down, are created. Running this function in the tests root folder will create (or update) all the recursive scripts. -## Windows +## Windows (only) ![](./Windows.PNG) This tab is Windows-specific. It provides buttons to hide and show the task bar. diff --git a/tools/nitpick/compiledResources/resources.rcc b/tools/nitpick/compiledResources/resources.rcc new file mode 100644 index 0000000000..15f51ed7f4 Binary files /dev/null and b/tools/nitpick/compiledResources/resources.rcc differ diff --git a/tools/nitpick/icon/nitpick.icns b/tools/nitpick/icon/nitpick.icns new file mode 100644 index 0000000000..332539af2a Binary files /dev/null and b/tools/nitpick/icon/nitpick.icns differ diff --git a/tools/nitpick/icon/nitpick.ico b/tools/nitpick/icon/nitpick.ico new file mode 100644 index 0000000000..e3d852cb41 Binary files /dev/null and b/tools/nitpick/icon/nitpick.ico differ diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 59be26c383..63d5af9272 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -27,13 +27,30 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& workingDirectory, QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { - _testResults = testResults; _workingDirectory = workingDirectory; + // Verify filename is in correct format + // For example `D:/tt/TestResults--2019-02-10_17-30-57(local)[DESKTOP-6BO62Q9].zip` + QStringList parts = testResults.split('/'); + QString zipFilename = parts[parts.length() - 1]; + + QStringList zipFolderNameParts = zipFilename.split(QRegExp("[\\(\\)\\[\\]]"), QString::SkipEmptyParts); + + if (!QRegularExpression("TestResults--\\d{4}(-\\d\\d){2}_\\d\\d(-\\d\\d){2}").match(zipFolderNameParts[0]).hasMatch() || + !QRegularExpression("\\w").match(zipFolderNameParts[1]).hasMatch() || // build (local, build number or PR number) + !QRegularExpression("\\w").match(zipFolderNameParts[2]).hasMatch() // machine name + ) { + QMessageBox::critical(0, "Filename is in wrong format", "'" + zipFilename + "' is not in nitpick format"); + return; + } + + _testResults = testResults; + _urlLineEdit = urlLineEdit; _urlLineEdit->setEnabled(false); - extractTestFailuresFromZippedFolder(); + QString zipFilenameWithoutExtension = zipFilename.split('.')[0]; + extractTestFailuresFromZippedFolder(_workingDirectory + "/" + zipFilenameWithoutExtension); createHTMLFile(); if (updateAWSCheckBox->isChecked()) { @@ -44,14 +61,12 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, } } -void AWSInterface::extractTestFailuresFromZippedFolder() { +void AWSInterface::extractTestFailuresFromZippedFolder(const QString& folderName) { // For a test results zip file called `D:/tt/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ].zip` // the folder will be called `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]` // and, this folder will be in the working directory - QStringList parts = _testResults.split('/'); - QString zipFolderName = _workingDirectory + "/" + parts[parts.length() - 1].split('.')[0]; - if (QDir(zipFolderName).exists()) { - QDir dir = zipFolderName; + if (QDir(folderName).exists()) { + QDir dir = folderName; dir.removeRecursively(); } diff --git a/tools/nitpick/src/AWSInterface.h b/tools/nitpick/src/AWSInterface.h index fda250b115..d95b8ecf2f 100644 --- a/tools/nitpick/src/AWSInterface.h +++ b/tools/nitpick/src/AWSInterface.h @@ -16,7 +16,7 @@ #include #include -#include "ui/BusyWindow.h" +#include "BusyWindow.h" #include "PythonInterface.h" @@ -30,7 +30,7 @@ public: QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit); - void extractTestFailuresFromZippedFolder(); + void extractTestFailuresFromZippedFolder(const QString& folderName); void createHTMLFile(); void startHTMLpage(QTextStream& stream); diff --git a/tools/nitpick/src/AdbInterface.cpp b/tools/nitpick/src/AdbInterface.cpp new file mode 100644 index 0000000000..82ef1446e3 --- /dev/null +++ b/tools/nitpick/src/AdbInterface.cpp @@ -0,0 +1,38 @@ +// +// AdbInterface.cpp +// +// Created by Nissim Hadar on Feb 11, 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "AdbInterface.h" +#include + +#include +#include + +QString AdbInterface::getAdbCommand() { +#ifdef Q_OS_WIN + if (_adbCommand.isNull()) { + QString adbPath = PathUtils::getPathToExecutable("adb.exe"); + if (!adbPath.isNull()) { + _adbCommand = adbPath + _adbExe; + } else { + QMessageBox::critical(0, "python.exe not found", + "Please verify that pyton.exe is in the PATH"); + exit(-1); + } + } +#elif defined Q_OS_MAC + _adbCommand = "/usr/local/bin/adb"; + if (!QFile::exists(_adbCommand)) { + QMessageBox::critical(0, "adb not found", + "adb not found at " + _adbCommand); + exit(-1); + } +#endif + + return _adbCommand; +} diff --git a/tools/nitpick/src/AdbInterface.h b/tools/nitpick/src/AdbInterface.h new file mode 100644 index 0000000000..c1ce84c019 --- /dev/null +++ b/tools/nitpick/src/AdbInterface.h @@ -0,0 +1,30 @@ +// +// AdbInterface.h +// +// Created by Nissim Hadar on Feb 11, 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_AdbInterface_h +#define hifi_AdbInterface_h + +#include + +class AdbInterface { +public: + QString getAdbCommand(); + +private: +#ifdef Q_OS_WIN + const QString _adbExe{ "adb.exe" }; +#else + // Both Mac and Linux use "python" + const QString _adbExe{ "adb" }; +#endif + + QString _adbCommand; +}; + +#endif // hifi_AdbInterface_h diff --git a/tools/nitpick/src/ui/BusyWindow.cpp b/tools/nitpick/src/BusyWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.cpp rename to tools/nitpick/src/BusyWindow.cpp diff --git a/tools/nitpick/src/ui/BusyWindow.h b/tools/nitpick/src/BusyWindow.h similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.h rename to tools/nitpick/src/BusyWindow.h diff --git a/tools/nitpick/src/ui/MismatchWindow.cpp b/tools/nitpick/src/MismatchWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/MismatchWindow.cpp rename to tools/nitpick/src/MismatchWindow.cpp diff --git a/tools/nitpick/src/ui/MismatchWindow.h b/tools/nitpick/src/MismatchWindow.h similarity index 97% rename from tools/nitpick/src/ui/MismatchWindow.h rename to tools/nitpick/src/MismatchWindow.h index 30c29076b3..040e0b8bf1 100644 --- a/tools/nitpick/src/ui/MismatchWindow.h +++ b/tools/nitpick/src/MismatchWindow.h @@ -12,7 +12,7 @@ #include "ui_MismatchWindow.h" -#include "../common.h" +#include "common.h" class MismatchWindow : public QDialog, public Ui::MismatchWindow { Q_OBJECT diff --git a/tools/nitpick/src/ui/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp similarity index 65% rename from tools/nitpick/src/ui/Nitpick.cpp rename to tools/nitpick/src/Nitpick.cpp index 0bd397715b..d5bc6f6e5a 100644 --- a/tools/nitpick/src/ui/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -26,7 +26,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _signalMapper = new QSignalMapper(); - connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closeButton_clicked); + connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closePushbutton_clicked); connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about); connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content); @@ -35,10 +35,12 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.tabWidget->removeTab(1); #endif - _ui.statusLabel->setText(""); - _ui.plainTextEdit->setReadOnly(true); + _ui.statusLabelOnDesktop->setText(""); + _ui.statusLabelOnMobile->setText(""); + + _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v1.3.2"); + setWindowTitle("Nitpick - v2.1.2"); } Nitpick::~Nitpick() { @@ -48,8 +50,12 @@ Nitpick::~Nitpick() { delete _test; } - if (_testRunner) { - delete _testRunner; + if (_testRunnerDesktop) { + delete _testRunnerDesktop; + } + + if (_testRunnerMobile) { + delete _testRunnerMobile; } } @@ -80,10 +86,38 @@ void Nitpick::setup() { timeEdits.emplace_back(_ui.timeEdit3); timeEdits.emplace_back(_ui.timeEdit4); - if (_testRunner) { - delete _testRunner; + // Create the two test runners + if (_testRunnerDesktop) { + delete _testRunnerDesktop; } - _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlLineEdit, _ui.runNowButton); + _testRunnerDesktop = new TestRunnerDesktop( + dayCheckboxes, + timeEditCheckboxes, + timeEdits, + _ui.workingFolderRunOnDesktopLabel, + _ui.checkBoxServerless, + _ui.runLatestOnDesktopCheckBox, + _ui.urlOnDesktopLineEdit, + _ui.runNowPushbutton, + _ui.statusLabelOnDesktop + ); + + if (_testRunnerMobile) { + delete _testRunnerMobile; + } + _testRunnerMobile = new TestRunnerMobile( + _ui.workingFolderRunOnMobileLabel, + _ui.connectDevicePushbutton, + _ui.pullFolderPushbutton, + _ui.detectedDeviceLabel, + _ui.folderLineEdit, + _ui.downloadAPKPushbutton, + _ui.installAPKPushbutton, + _ui.runInterfacePushbutton, + _ui.runLatestOnMobileCheckBox, + _ui.urlOnMobileLineEdit, + _ui.statusLabelOnMobile + ); } void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine, @@ -98,9 +132,9 @@ void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine, void Nitpick::on_tabWidget_currentChanged(int index) { // Enable the GitHub edit boxes as required #ifdef Q_OS_WIN - if (index == 0 || index == 2 || index == 3) { + if (index == 0 || index == 2 || index == 3 || index == 4) { #else - if (index == 0 || index == 1 || index == 2) { + if (index == 0 || index == 1 || index == 2 || index == 3) { #endif _ui.userLineEdit->setDisabled(false); _ui.branchLineEdit->setDisabled(false); @@ -110,43 +144,43 @@ void Nitpick::on_tabWidget_currentChanged(int index) { } } -void Nitpick::on_evaluateTestsButton_clicked() { +void Nitpick::on_evaluateTestsPushbutton_clicked() { _test->startTestsEvaluation(false, false); } -void Nitpick::on_createRecursiveScriptButton_clicked() { +void Nitpick::on_createRecursiveScriptPushbutton_clicked() { _test->createRecursiveScript(); } -void Nitpick::on_createAllRecursiveScriptsButton_clicked() { +void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() { _test->createAllRecursiveScripts(); } -void Nitpick::on_createTestsButton_clicked() { +void Nitpick::on_createTestsPushbutton_clicked() { _test->createTests(); } -void Nitpick::on_createMDFileButton_clicked() { +void Nitpick::on_createMDFilePushbutton_clicked() { _test->createMDFile(); } -void Nitpick::on_createAllMDFilesButton_clicked() { +void Nitpick::on_createAllMDFilesPushbutton_clicked() { _test->createAllMDFiles(); } -void Nitpick::on_createTestAutoScriptButton_clicked() { +void Nitpick::on_createTestAutoScriptPushbutton_clicked() { _test->createTestAutoScript(); } -void Nitpick::on_createAllTestAutoScriptsButton_clicked() { +void Nitpick::on_createAllTestAutoScriptsPushbutton_clicked() { _test->createAllTestAutoScripts(); } -void Nitpick::on_createTestsOutlineButton_clicked() { +void Nitpick::on_createTestsOutlinePushbutton_clicked() { _test->createTestsOutline(); } -void Nitpick::on_createTestRailTestCasesButton_clicked() { +void Nitpick::on_createTestRailTestCasesPushbutton_clicked() { _test->createTestRailTestCases(); } @@ -154,29 +188,29 @@ void Nitpick::on_createTestRailRunButton_clicked() { _test->createTestRailRun(); } -void Nitpick::on_setWorkingFolderButton_clicked() { - _testRunner->setWorkingFolder(); +void Nitpick::on_setWorkingFolderRunOnDesktopPushbutton_clicked() { + _testRunnerDesktop->setWorkingFolderAndEnableControls(); } void Nitpick::enableRunTabControls() { - _ui.runNowButton->setEnabled(true); + _ui.runNowPushbutton->setEnabled(true); _ui.daysGroupBox->setEnabled(true); _ui.timesGroupBox->setEnabled(true); } -void Nitpick::on_runNowButton_clicked() { - _testRunner->run(); +void Nitpick::on_runNowPushbutton_clicked() { + _testRunnerDesktop->run(); } -void Nitpick::on_checkBoxRunLatest_clicked() { - _ui.urlLineEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); +void Nitpick::on_runLatestOnDesktopCheckBox_clicked() { + _ui.urlOnDesktopLineEdit->setEnabled(!_ui.runLatestOnDesktopCheckBox->isChecked()); } void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) { - _testRunner->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); + _testRunnerDesktop->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); } -void Nitpick::on_updateTestRailRunResultsButton_clicked() { +void Nitpick::on_updateTestRailRunResultsPushbutton_clicked() { _test->updateTestRailRunResult(); } @@ -184,7 +218,7 @@ void Nitpick::on_updateTestRailRunResultsButton_clicked() { // if (uState & ABS_AUTOHIDE) on_showTaskbarButton_clicked(); // else on_hideTaskbarButton_clicked(); // -void Nitpick::on_hideTaskbarButton_clicked() { +void Nitpick::on_hideTaskbarPushbutton_clicked() { #ifdef Q_OS_WIN APPBARDATA abd = { sizeof abd }; UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); @@ -194,7 +228,7 @@ void Nitpick::on_hideTaskbarButton_clicked() { #endif } -void Nitpick::on_showTaskbarButton_clicked() { +void Nitpick::on_showTaskbarPushbutton_clicked() { #ifdef Q_OS_WIN APPBARDATA abd = { sizeof abd }; UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); @@ -204,7 +238,7 @@ void Nitpick::on_showTaskbarButton_clicked() { #endif } -void Nitpick::on_closeButton_clicked() { +void Nitpick::on_closePushbutton_clicked() { exit(0); } @@ -216,7 +250,7 @@ void Nitpick::on_createXMLScriptRadioButton_clicked() { _test->setTestRailCreateMode(XML); } -void Nitpick::on_createWebPagePushButton_clicked() { +void Nitpick::on_createWebPagePushbutton_clicked() { _test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); } @@ -273,9 +307,13 @@ void Nitpick::saveFile(int index) { disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); if (_caller == _test) { _test->finishTestsEvaluation(); - } else if (_caller == _testRunner) { - _testRunner->downloadComplete(); + } else if (_caller == _testRunnerDesktop) { + _testRunnerDesktop->downloadComplete(); + } else if (_caller == _testRunnerMobile) { + _testRunnerMobile->downloadComplete(); } + + _ui.progressBar->setVisible(false); } else { _ui.progressBar->setValue(_numberOfFilesDownloaded); } @@ -305,10 +343,35 @@ QString Nitpick::getSelectedBranch() { return _ui.branchLineEdit->text(); } -void Nitpick::updateStatusLabel(const QString& status) { - _ui.statusLabel->setText(status); -} - void Nitpick::appendLogWindow(const QString& message) { _ui.plainTextEdit->appendPlainText(message); } + +// Test on Mobile +void Nitpick::on_setWorkingFolderRunOnMobilePushbutton_clicked() { + _testRunnerMobile->setWorkingFolderAndEnableControls(); +} + +void Nitpick::on_connectDevicePushbutton_clicked() { + _testRunnerMobile->connectDevice(); +} + +void Nitpick::on_runLatestOnMobileCheckBox_clicked() { + _ui.urlOnMobileLineEdit->setEnabled(!_ui.runLatestOnMobileCheckBox->isChecked()); +} + +void Nitpick::on_downloadAPKPushbutton_clicked() { + _testRunnerMobile->downloadAPK(); +} + +void Nitpick::on_installAPKPushbutton_clicked() { + _testRunnerMobile->installAPK(); +} + +void Nitpick::on_runInterfacePushbutton_clicked() { + _testRunnerMobile->runInterface(); +} + +void Nitpick::on_pullFolderPushbutton_clicked() { + _testRunnerMobile->pullFolder(); +} diff --git a/tools/nitpick/src/ui/Nitpick.h b/tools/nitpick/src/Nitpick.h similarity index 59% rename from tools/nitpick/src/ui/Nitpick.h rename to tools/nitpick/src/Nitpick.h index 08e41e0a90..36ec7e534b 100644 --- a/tools/nitpick/src/ui/Nitpick.h +++ b/tools/nitpick/src/Nitpick.h @@ -15,11 +15,11 @@ #include #include "ui_Nitpick.h" -#include "../Downloader.h" -#include "../Test.h" +#include "Downloader.h" +#include "Test.h" -#include "../TestRunner.h" -#include "../AWSInterface.h" +#include "TestRunnerDesktop.h" +#include "TestRunnerMobile.h" class Nitpick : public QMainWindow { Q_OBJECT @@ -49,56 +49,66 @@ public: void enableRunTabControls(); - void updateStatusLabel(const QString& status); void appendLogWindow(const QString& message); private slots: + void on_closePushbutton_clicked(); + void on_tabWidget_currentChanged(int index); - void on_evaluateTestsButton_clicked(); - void on_createRecursiveScriptButton_clicked(); - void on_createAllRecursiveScriptsButton_clicked(); - void on_createTestsButton_clicked(); + void on_evaluateTestsPushbutton_clicked(); + void on_createRecursiveScriptPushbutton_clicked(); + void on_createAllRecursiveScriptsPushbutton_clicked(); + void on_createTestsPushbutton_clicked(); - void on_createMDFileButton_clicked(); - void on_createAllMDFilesButton_clicked(); + void on_createMDFilePushbutton_clicked(); + void on_createAllMDFilesPushbutton_clicked(); - void on_createTestAutoScriptButton_clicked(); - void on_createAllTestAutoScriptsButton_clicked(); + void on_createTestAutoScriptPushbutton_clicked(); + void on_createAllTestAutoScriptsPushbutton_clicked(); - void on_createTestsOutlineButton_clicked(); + void on_createTestsOutlinePushbutton_clicked(); - void on_createTestRailTestCasesButton_clicked(); + void on_createTestRailTestCasesPushbutton_clicked(); void on_createTestRailRunButton_clicked(); - void on_setWorkingFolderButton_clicked(); - void on_runNowButton_clicked(); + void on_setWorkingFolderRunOnDesktopPushbutton_clicked(); + void on_runNowPushbutton_clicked(); - void on_checkBoxRunLatest_clicked(); + void on_runLatestOnDesktopCheckBox_clicked(); - void on_updateTestRailRunResultsButton_clicked(); + void on_updateTestRailRunResultsPushbutton_clicked(); - void on_hideTaskbarButton_clicked(); - void on_showTaskbarButton_clicked(); + void on_hideTaskbarPushbutton_clicked(); + void on_showTaskbarPushbutton_clicked(); void on_createPythonScriptRadioButton_clicked(); void on_createXMLScriptRadioButton_clicked(); - void on_createWebPagePushButton_clicked(); - - void on_closeButton_clicked(); + void on_createWebPagePushbutton_clicked(); void saveFile(int index); void about(); void content(); + // Run on Mobile controls + void on_setWorkingFolderRunOnMobilePushbutton_clicked(); + void on_connectDevicePushbutton_clicked(); + void on_runLatestOnMobileCheckBox_clicked(); + + void on_downloadAPKPushbutton_clicked(); + void on_installAPKPushbutton_clicked(); + void on_runInterfacePushbutton_clicked(); + + void on_pullFolderPushbutton_clicked(); + private: Ui::NitpickClass _ui; Test* _test{ nullptr }; - TestRunner* _testRunner{ nullptr }; - AWSInterface _awsInterface; + TestRunnerDesktop* _testRunnerDesktop{ nullptr }; + TestRunnerMobile* _testRunnerMobile{ nullptr }; std::vector _downloaders; diff --git a/tools/nitpick/src/PathUtils.cpp b/tools/nitpick/src/PathUtils.cpp new file mode 100644 index 0000000000..711570d568 --- /dev/null +++ b/tools/nitpick/src/PathUtils.cpp @@ -0,0 +1,31 @@ +// +// PathUtils.h +// +// Created by Nissim Hadar on 11 Feb 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PathUtils.h" + +#include +#include +#include + +QString PathUtils::getPathToExecutable(const QString& executableName) { + QString path = QProcessEnvironment::systemEnvironment().value("PATH"); + + QStringList pathLocations = path.replace('\\', '/').split(';'); + + foreach (QString pathLocation, pathLocations) { + if (pathLocation[pathLocation.length() - 1] != '/') { + pathLocation += '/'; + } + if (QFile::exists(pathLocation + executableName)) { + return pathLocation; + } + } + + return QString(); +} diff --git a/tools/nitpick/src/PathUtils.h b/tools/nitpick/src/PathUtils.h new file mode 100644 index 0000000000..72f6839e3d --- /dev/null +++ b/tools/nitpick/src/PathUtils.h @@ -0,0 +1,20 @@ +// +// PathUtils.h +// +// Created by Nissim Hadar on 11 Feb 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_PathUtils_h +#define hifi_PathUtils_h + +#include + +class PathUtils { +public: + static QString getPathToExecutable(const QString& executableName); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/PythonInterface.cpp b/tools/nitpick/src/PythonInterface.cpp index 9832ac9f8d..dcf4ecc682 100644 --- a/tools/nitpick/src/PythonInterface.cpp +++ b/tools/nitpick/src/PythonInterface.cpp @@ -8,36 +8,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "PythonInterface.h" +#include #include #include -#include -PythonInterface::PythonInterface() { +QString PythonInterface::getPythonCommand() { #ifdef Q_OS_WIN - if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) { - QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH"); - if (!QFile::exists(_pythonPath + "/" + _pythonExe)) { - QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + _pythonPath); + if (_pythonCommand.isNull()) { + QString pythonPath = PathUtils::getPathToExecutable("python.exe"); + if (!pythonPath.isNull()) { + _pythonCommand = pythonPath + _pythonExe; + } else { + QMessageBox::critical(0, "python.exe not found", + "Please verify that pyton.exe is in the PATH"); exit(-1); } - - _pythonCommand = _pythonPath + "/" + _pythonExe; - } else { - QMessageBox::critical(0, "PYTHON_PATH not defined", - "Please set PYTHON_PATH to directory containing the Python executable"); - exit(-1); } #elif defined Q_OS_MAC _pythonCommand = "/usr/local/bin/python3"; if (!QFile::exists(_pythonCommand)) { - QMessageBox::critical(0, "PYTHON_PATH not defined", - "python3 not found at " + _pythonCommand); + QMessageBox::critical(0, "python not found", + "python3 not found at " + _pythonCommand); exit(-1); } #endif -} -QString PythonInterface::getPythonCommand() { return _pythonCommand; } diff --git a/tools/nitpick/src/PythonInterface.h b/tools/nitpick/src/PythonInterface.h index 947b359037..7972d55cce 100644 --- a/tools/nitpick/src/PythonInterface.h +++ b/tools/nitpick/src/PythonInterface.h @@ -14,8 +14,6 @@ class PythonInterface { public: - PythonInterface(); - QString getPythonCommand(); private: diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index 19c49eac42..f1e950db88 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -19,12 +19,12 @@ #include #include -#include "ui/Nitpick.h" +#include "Nitpick.h" extern Nitpick* nitpick; #include -Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) { +Test::Test(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode) : _awsInterface(NULL) { _progressBar = progressBar; _checkBoxInteractiveMode = checkBoxInteractiveMode; @@ -758,47 +758,66 @@ void Test::createAllRecursiveScripts() { return; } + createAllRecursiveScripts(_testsRootDirectory); createRecursiveScript(_testsRootDirectory, false); - - QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir; - if (!isAValidDirectory(directory)) { - continue; - } - - // Only process directories that have sub-directories - bool hasNoSubDirectories{ true }; - QDirIterator it2(directory, QDirIterator::Subdirectories); - while (it2.hasNext()) { - QString directory2 = it2.next(); - - // Only process directories - QDir dir; - if (isAValidDirectory(directory2)) { - hasNoSubDirectories = false; - break; - } - } - - if (!hasNoSubDirectories) { - createRecursiveScript(directory, false); - } - } - QMessageBox::information(0, "Success", "Scripts have been created"); } -void Test::createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode) { - const QString recursiveTestsScriptName("testRecursive.js"); - const QString recursiveTestsFilename(topLevelDirectory + "/" + recursiveTestsScriptName); +void Test::createAllRecursiveScripts(const QString& directory) { + QDirIterator it(directory, QDirIterator::Subdirectories); + + while (it.hasNext()) { + QString nextDirectory = it.next(); + if (isAValidDirectory(nextDirectory)) { + createAllRecursiveScripts(nextDirectory); + createRecursiveScript(nextDirectory, false); + } + } +} + +void Test::createRecursiveScript(const QString& directory, bool interactiveMode) { + // If folder contains a test, then we are at a leaf + const QString testPathname{ directory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + return; + } + + // Directories are included in reverse order. The nitpick scripts use a stack mechanism, + // so this ensures that the tests run in alphabetical order (a convenience when debugging) + QStringList directories; + QDirIterator it(directory); + while (it.hasNext()) { + QString subDirectory = it.next(); + + // Only process directories + if (!isAValidDirectory(subDirectory)) { + continue; + } + + const QString testPathname{ subDirectory + "/" + TEST_FILENAME }; + if (QFileInfo(testPathname).exists()) { + // Current folder contains a test script + directories.push_front(testPathname); + } + + const QString testRecursivePathname{ subDirectory + "/" + TEST_RECURSIVE_FILENAME }; + if (QFileInfo(testRecursivePathname).exists()) { + // Current folder contains a recursive script + directories.push_front(testRecursivePathname); + } + } + + // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant + if (directories.length() == 0) { + return; + } + + // Open the recursive script file + const QString recursiveTestsFilename(directory + "/" + TEST_RECURSIVE_FILENAME); QFile recursiveTestsFile(recursiveTestsFilename); if (!recursiveTestsFile.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Failed to create \"" + recursiveTestsScriptName + "\" in directory \"" + topLevelDirectory + "\""); + "Failed to create \"" + TEST_RECURSIVE_FILENAME + "\" in directory \"" + directory + "\""); exit(-1); } @@ -812,71 +831,20 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact QString user = nitpick->getSelectedUser(); textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + - "/tests/utils/branchUtils.js\";" - << endl; - textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; - textStream << "var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl << endl; + "/tests/utils/branchUtils.js\";" + << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl << endl; - textStream << "var testsRootPath = nitpick.getTestsRootPath();" << endl << endl; - - // Wait 10 seconds before starting - textStream << "if (typeof Test !== 'undefined') {" << endl; - textStream << " Test.wait(10000);" << endl; - textStream << "};" << endl << endl; - - textStream << "nitpick.enableRecursive();" << endl; - textStream << "nitpick.enableAuto();" << endl << endl; - - // This is used to verify that the recursive test contains at least one test - bool testFound{ false }; - - // Directories are included in reverse order. The nitpick scripts use a stack mechanism, - // so this ensures that the tests run in alphabetical order (a convenience when debugging) - QStringList directories; - - // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - - QDirIterator it(topLevelDirectory, QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - // Only process directories - QDir dir(directory); - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (fileInfo.exists()) { - // Current folder contains a test - directories.push_front(testPathname); - - testFound = true; - } - } - - if (interactiveMode && !testFound) { - QMessageBox::information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); - recursiveTestsFile.close(); - return; - } - - // If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant - // The script will be closed and deleted - if (directories.length() == 0) { - recursiveTestsFile.close(); - QFile::remove(recursiveTestsFilename); - return; - } + // The 'depth' variable is used to signal when to start running the recursive scripts + textStream << "if (typeof depth === 'undefined') {" << endl; + textStream << " depth = 0;" << endl; + textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl; + textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl; + textStream << " nitpick.enableRecursive();" << endl; + textStream << " nitpick.enableAuto();" << endl; + textStream << "} else {" << endl; + textStream << " depth++" << endl; + textStream << "}" << endl << endl; // Now include the test scripts for (int i = 0; i < directories.length(); ++i) { @@ -884,7 +852,11 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact } textStream << endl; - textStream << "nitpick.runRecursive();" << endl; + textStream << "if (depth > 0) {" << endl; + textStream << " depth--;" << endl; + textStream << "} else {" << endl; + textStream << " nitpick.runRecursive();" << endl; + textStream << "}" << endl << endl; recursiveTestsFile.close(); } @@ -928,7 +900,6 @@ void Test::createTestsOutline() { QString directory = it.next(); // Only process directories - QDir dir; if (!isAValidDirectory(directory)) { continue; } @@ -1001,11 +972,15 @@ void Test::createTestRailTestCases() { return; } + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + if (_testRailCreateMode == PYTHON) { - _testRailInterface.createTestSuitePython(_testDirectory, outputDirectory, nitpick->getSelectedUser(), + _testRailInterface->createTestSuitePython(_testDirectory, outputDirectory, nitpick->getSelectedUser(), nitpick->getSelectedBranch()); } else { - _testRailInterface.createTestSuiteXML(_testDirectory, outputDirectory, nitpick->getSelectedUser(), + _testRailInterface->createTestSuiteXML(_testDirectory, outputDirectory, nitpick->getSelectedUser(), nitpick->getSelectedBranch()); } } @@ -1018,7 +993,12 @@ void Test::createTestRailRun() { return; } - _testRailInterface.createTestRailRun(outputDirectory); + + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + + _testRailInterface->createTestRailRun(outputDirectory); } void Test::updateTestRailRunResult() { @@ -1034,7 +1014,12 @@ void Test::updateTestRailRunResult() { return; } - _testRailInterface.updateTestRailRunResults(testResults, tempDirectory); + + if (!_testRailInterface) { + _testRailInterface = new TestRailInterface; + } + + _testRailInterface->updateTestRailRunResults(testResults, tempDirectory); } QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { @@ -1112,7 +1097,7 @@ void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) { void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { QString testResults = QFileDialog::getOpenFileName(nullptr, "Please select the zipped test results to update from", nullptr, - "Zipped Test Results (*.zip)"); + "Zipped Test Results (TestResults--*.zip)"); if (testResults.isNull()) { return; } @@ -1123,5 +1108,9 @@ void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) { return; } - _awsInterface.createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit); + if (!_awsInterface) { + _awsInterface = new AWSInterface; + } + + _awsInterface->createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit); } \ No newline at end of file diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index 9ef7c5627a..166c71688d 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -18,7 +18,7 @@ #include "AWSInterface.h" #include "ImageComparer.h" -#include "ui/MismatchWindow.h" +#include "MismatchWindow.h" #include "TestRailInterface.h" class Step { @@ -72,9 +72,11 @@ public: void updateTestRailRunResult(); - void createRecursiveScript(); void createAllRecursiveScripts(); - void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode); + void createAllRecursiveScripts(const QString& directory); + + void createRecursiveScript(); + void createRecursiveScript(const QString& directory, bool interactiveMode); int compareImageLists(); int checkTextResults(); @@ -109,7 +111,8 @@ private: bool _isRunningFromCommandLine{ false }; bool _isRunningInAutomaticTestRun{ false }; - const QString TEST_FILENAME { "test.js" }; + const QString TEST_FILENAME{ "test.js" }; + const QString TEST_RECURSIVE_FILENAME{ "testRecursive.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; @@ -156,10 +159,10 @@ private: bool _exitWhenComplete{ false }; - TestRailInterface _testRailInterface; + TestRailInterface* _testRailInterface; TestRailCreateMode _testRailCreateMode { PYTHON }; - AWSInterface _awsInterface; + AWSInterface* _awsInterface; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/nitpick/src/TestRailInterface.cpp b/tools/nitpick/src/TestRailInterface.cpp index 1d7aa0a32f..6ed13a72b6 100644 --- a/tools/nitpick/src/TestRailInterface.cpp +++ b/tools/nitpick/src/TestRailInterface.cpp @@ -20,7 +20,7 @@ #include #include -TestRailInterface::TestRailInterface() { +TestRailInterface::TestRailInterface() : _pythonInterface(NULL) { _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); diff --git a/tools/nitpick/src/TestRailInterface.h b/tools/nitpick/src/TestRailInterface.h index 6843ca0142..566600e71a 100644 --- a/tools/nitpick/src/TestRailInterface.h +++ b/tools/nitpick/src/TestRailInterface.h @@ -11,10 +11,10 @@ #ifndef hifi_test_testrail_interface_h #define hifi_test_testrail_interface_h -#include "ui/BusyWindow.h" -#include "ui/TestRailTestCasesSelectorWindow.h" -#include "ui/TestRailRunSelectorWindow.h" -#include "ui/TestRailResultsSelectorWindow.h" +#include "BusyWindow.h" +#include "TestRailTestCasesSelectorWindow.h" +#include "TestRailRunSelectorWindow.h" +#include "TestRailResultsSelectorWindow.h" #include #include diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp b/tools/nitpick/src/TestRailResultsSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp rename to tools/nitpick/src/TestRailResultsSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.h b/tools/nitpick/src/TestRailResultsSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.h rename to tools/nitpick/src/TestRailResultsSelectorWindow.h diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp b/tools/nitpick/src/TestRailRunSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp rename to tools/nitpick/src/TestRailRunSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.h b/tools/nitpick/src/TestRailRunSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.h rename to tools/nitpick/src/TestRailRunSelectorWindow.h diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp b/tools/nitpick/src/TestRailTestCasesSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp rename to tools/nitpick/src/TestRailTestCasesSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h b/tools/nitpick/src/TestRailTestCasesSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h rename to tools/nitpick/src/TestRailTestCasesSelectorWindow.h diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 9aca2bf3e6..54246de80b 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -1,7 +1,7 @@ // // TestRunner.cpp // -// Created by Nissim Hadar on 1 Sept 2018. +// Created by Nissim Hadar on 23 Jan 2019. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -9,70 +9,12 @@ // #include "TestRunner.h" -#include -#include -#include +#include -#include "ui/Nitpick.h" +#include "Nitpick.h" extern Nitpick* nitpick; -#ifdef Q_OS_WIN -#include -#include -#endif - -// TODO: for debug -#include - -TestRunner::TestRunner(std::vector dayCheckboxes, - std::vector timeEditCheckboxes, - std::vector timeEdits, - QLabel* workingFolderLabel, - QCheckBox* runServerless, - QCheckBox* runLatest, - QLineEdit* url, - QPushButton* runNow, - QObject* parent) : - QObject(parent) { - _dayCheckboxes = dayCheckboxes; - _timeEditCheckboxes = timeEditCheckboxes; - _timeEdits = timeEdits; - _workingFolderLabel = workingFolderLabel; - _runServerless = runServerless; - _runLatest = runLatest; - _url = url; - _runNow = runNow; - - _installerThread = new QThread(); - _installerWorker = new Worker(); - - _installerWorker->moveToThread(_installerThread); - _installerThread->start(); - connect(this, SIGNAL(startInstaller()), _installerWorker, SLOT(runCommand())); - connect(_installerWorker, SIGNAL(commandComplete()), this, SLOT(installationComplete())); - - _interfaceThread = new QThread(); - _interfaceWorker = new Worker(); - - _interfaceThread->start(); - _interfaceWorker->moveToThread(_interfaceThread); - connect(this, SIGNAL(startInterface()), _interfaceWorker, SLOT(runCommand())); - connect(_interfaceWorker, SIGNAL(commandComplete()), this, SLOT(interfaceExecutionComplete())); -} - -TestRunner::~TestRunner() { - delete _installerThread; - delete _installerWorker; - - delete _interfaceThread; - delete _interfaceWorker; - - if (_timer) { - delete _timer; - } -} - -void TestRunner::setWorkingFolder() { +void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) { // Everything will be written to this folder QString previousSelection = _workingFolder; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -80,8 +22,8 @@ void TestRunner::setWorkingFolder() { parent += "/"; } - _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, - QFileDialog::ShowDirsOnly); + _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a working folder for temporary files", parent, + QFileDialog::ShowDirsOnly); // If user canceled then restore previous selection and return if (_workingFolder == "") { @@ -89,643 +31,25 @@ void TestRunner::setWorkingFolder() { return; } -#ifdef Q_OS_WIN - _installationFolder = _workingFolder + "/High Fidelity"; -#elif defined Q_OS_MAC - _installationFolder = _workingFolder + "/High_Fidelity"; -#endif + workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); + // This file is used for debug purposes. _logFile.setFileName(_workingFolder + "/log.txt"); - - nitpick->enableRunTabControls(); - _workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); - - _timer = new QTimer(this); - connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); - _timer->start(30 * 1000); //time specified in ms - -#ifdef Q_OS_MAC - // Create MAC shell scripts - QFile script; - - // This script waits for a process to start - script.setFileName(_workingFolder + "/waitForStart.sh"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'waitForStart.sh'"); - exit(-1); - } - - script.write("#!/bin/sh\n\n"); - script.write("PROCESS=\"$1\"\n"); - script.write("until (pgrep -x $PROCESS >nul)\n"); - script.write("do\n"); - script.write("\techo waiting for \"$1\" to start\n"); - script.write("\tsleep 2\n"); - script.write("done\n"); - script.write("echo \"$1\" \"started\"\n"); - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); - - // The Mac shell command returns immediately. This little script waits for a process to finish - script.setFileName(_workingFolder + "/waitForFinish.sh"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'waitForFinish.sh'"); - exit(-1); - } - - script.write("#!/bin/sh\n\n"); - script.write("PROCESS=\"$1\"\n"); - script.write("while (pgrep -x $PROCESS >nul)\n"); - script.write("do\n"); - script.write("\techo waiting for \"$1\" to finish\n"); - script.write("\tsleep 2\n"); - script.write("done\n"); - script.write("echo \"$1\" \"finished\"\n"); - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); - - // Create an AppleScript to resize Interface. This is needed so that snapshots taken - // with the primary camera will be the correct size. - // This will be run from a normal shell script - script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.scpt"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'setInterfaceSizeAndPosition.scpt'"); - exit(-1); - } - - script.write("set width to 960\n"); - script.write("set height to 540\n"); - script.write("set x to 100\n"); - script.write("set y to 100\n\n"); - script.write("tell application \"System Events\" to tell application process \"interface\" to tell window 1 to set {size, position} to {{width, height}, {x, y}}\n"); - - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); - - script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.sh"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'setInterfaceSizeAndPosition.sh'"); - exit(-1); - } - - script.write("#!/bin/sh\n\n"); - script.write("echo resizing interface\n"); - script.write(("osascript " + _workingFolder + "/setInterfaceSizeAndPosition.scpt\n").toStdString().c_str()); - script.write("echo resize complete\n"); - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); -#endif } -void TestRunner::run() { - _runNow->setEnabled(false); - - _testStartDateTime = QDateTime::currentDateTime(); - _automatedTestIsRunning = true; - - // Initial setup - _branch = nitpick->getSelectedBranch(); - _user = nitpick->getSelectedUser(); - - // This will be restored at the end of the tests - saveExistingHighFidelityAppDataFolder(); - +void TestRunner::downloadBuildXml(void* caller) { // Download the latest High Fidelity build XML. // Note that this is not needed for PR builds (or whenever `Run Latest` is unchecked) // It is still downloaded, to simplify the flow + buildXMLDownloaded = false; + QStringList urls; QStringList filenames; urls << DEV_BUILD_XML_URL; filenames << DEV_BUILD_XML_FILENAME; - updateStatusLabel("Downloading Build XML"); - - buildXMLDownloaded = false; - nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); - - // `downloadComplete` will run after download has completed -} - -void TestRunner::downloadComplete() { - if (!buildXMLDownloaded) { - // Download of Build XML has completed - buildXMLDownloaded = true; - - // Download the High Fidelity installer - QStringList urls; - QStringList filenames; - if (_runLatest->isChecked()) { - parseBuildInformation(); - - _installerFilename = INSTALLER_FILENAME_LATEST; - - urls << _buildInformation.url; - filenames << _installerFilename; - } else { - QString urlText = _url->text(); - urls << urlText; - _installerFilename = getInstallerNameFromURL(urlText); - filenames << _installerFilename; - } - - updateStatusLabel("Downloading installer"); - - nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); - - // `downloadComplete` will run again after download has completed - - } else { - // Download of Installer has completed - appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + - QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - _testStartDateTime.date().toString("ddd, MMM d, yyyy")); - - updateStatusLabel("Installing"); - - // Kill any existing processes that would interfere with installation - killProcesses(); - - runInstaller(); - } -} - -void TestRunner::runInstaller() { - // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) - // To allow installation, the installer is run using the `system` command - - QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; - - QString installerFullPath = _workingFolder + "/" + _installerFilename; - - QString commandLine; -#ifdef Q_OS_WIN - commandLine = "\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder); -#elif defined Q_OS_MAC - // Create installation shell script - QFile script; - script.setFileName(_workingFolder + "/install_app.sh"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'install_app.sh'"); - exit(-1); - } - - if (!QDir().exists(_installationFolder)) { - QDir().mkdir(_installationFolder); - } - - // This script installs High Fidelity. It is run as "yes | install_app.sh... so "yes" is killed at the end - script.write("#!/bin/sh\n\n"); - script.write("VOLUME=`hdiutil attach \"$1\" | grep Volumes | awk '{print $3}'`\n"); - - QString folderName {"High Fidelity"}; - if (!_runLatest->isChecked()) { - folderName += QString(" - ") + getPRNumberFromURL(_url->text()); - } - - script.write((QString("cp -rf \"$VOLUME/") + folderName + "/interface.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str()); - script.write((QString("cp -rf \"$VOLUME/") + folderName + "/Sandbox.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str()); - - script.write("hdiutil detach \"$VOLUME\"\n"); - script.write("killall yes\n"); - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); - commandLine = "yes | " + _workingFolder + "/install_app.sh " + installerFullPath; -#endif - appendLog(commandLine); - _installerWorker->setCommandLine(commandLine); - emit startInstaller(); -} - -void TestRunner::installationComplete() { - verifyInstallationSucceeded(); - - createSnapshotFolder(); - - updateStatusLabel("Running tests"); - - if (!_runServerless->isChecked()) { - startLocalServerProcesses(); - } - - runInterfaceWithTestScript(); -} - -void TestRunner::verifyInstallationSucceeded() { - // Exit if the executables are missing. - // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error -#ifdef Q_OS_WIN - QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe"); - QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe"); - QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe"); - - if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { - QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); - exit(-1); - } -#endif -} - -void TestRunner::saveExistingHighFidelityAppDataFolder() { - QString dataDirectory{ "NOT FOUND" }; -#ifdef Q_OS_WIN - dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; -#elif defined Q_OS_MAC - dataDirectory = QDir::homePath() + "/Library/Application Support"; -#endif - if (_runLatest->isChecked()) { - _appDataFolder = dataDirectory + "/High Fidelity"; - } else { - // We are running a PR build - _appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text()); - } - - _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; - if (QDir(_savedAppDataFolder).exists()) { - _savedAppDataFolder.removeRecursively(); - } - if (_appDataFolder.exists()) { - // The original folder is saved in a unique name - _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); - } - - // Copy an "empty" AppData folder (i.e. no entities) - copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path()); -} - -void TestRunner::createSnapshotFolder() { - _snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME; - - // Just delete all PNGs from the folder if it already exists - if (QDir(_snapshotFolder).exists()) { - // Note that we cannot use just a `png` filter, as the filenames include periods - // Also, delete any `jpg` and `txt` files - // The idea is to leave only previous zipped result folders - QDirIterator it(_snapshotFolder); - while (it.hasNext()) { - QString filename = it.next(); - if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") { - QFile::remove(filename); - } - } - - } else { - QDir().mkdir(_snapshotFolder); - } -} - -void TestRunner::killProcesses() { -#ifdef Q_OS_WIN - try { - QStringList processesToKill = QStringList() << "interface.exe" - << "assignment-client.exe" - << "domain-server.exe" - << "server-console.exe"; - - // Loop until all pending processes to kill have actually died - QStringList pendingProcessesToKill; - do { - pendingProcessesToKill.clear(); - - // Get list of running tasks - HANDLE processSnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (processSnapHandle == INVALID_HANDLE_VALUE) { - throw("Process snapshot creation failure"); - } - - PROCESSENTRY32 processEntry32; - processEntry32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(processSnapHandle, &processEntry32)) { - CloseHandle(processSnapHandle); - throw("Process32First failed"); - } - - // Kill any task in the list - do { - foreach (QString process, processesToKill) - if (QString(processEntry32.szExeFile) == process) { - QString commandLine = "taskkill /im " + process + " /f >nul"; - system(commandLine.toStdString().c_str()); - pendingProcessesToKill << process; - } - } while (Process32Next(processSnapHandle, &processEntry32)); - - QThread::sleep(2); - } while (!pendingProcessesToKill.isEmpty()); - - } catch (QString errorMessage) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); - exit(-1); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); - exit(-1); - } -#elif defined Q_OS_MAC - QString commandLine; - - commandLine = QString("killall interface") + "; " + _workingFolder +"/waitForFinish.sh interface"; - system(commandLine.toStdString().c_str()); - - commandLine = QString("killall Sandbox") + "; " + _workingFolder +"/waitForFinish.sh Sandbox"; - system(commandLine.toStdString().c_str()); - - commandLine = QString("killall Console") + "; " + _workingFolder +"/waitForFinish.sh Console"; - system(commandLine.toStdString().c_str()); -#endif -} - -void TestRunner::startLocalServerProcesses() { - QString commandLine; - -#ifdef Q_OS_WIN - commandLine = - "start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\""; - system(commandLine.toStdString().c_str()); - - commandLine = - "start \"assignment-client.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\" -n 6"; - system(commandLine.toStdString().c_str()); - -#elif defined Q_OS_MAC - commandLine = "open \"" +_installationFolder + "/Sandbox.app\""; - system(commandLine.toStdString().c_str()); -#endif - - // Give server processes time to stabilize - QThread::sleep(20); -} - -void TestRunner::runInterfaceWithTestScript() { - QString url = QString("hifi://localhost"); - if (_runServerless->isChecked()) { - // Move to an empty area - url = "file:///~serverless/tutorial.json"; - } else { - url = "hifi://localhost"; - } - - QString deleteScript = - QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js"; - - QString testScript = - QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; - - QString commandLine; -#ifdef Q_OS_WIN - QString exeFile; - // First, run script to delete any entities in test area - // Note that this will run to completion before continuing - exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; - commandLine = "start /wait \"\" " + exeFile + - " --url " + url + - " --no-updater" + - " --no-login-suggestion" - " --testScript " + deleteScript + " quitWhenFinished"; - - system(commandLine.toStdString().c_str()); - - // Now run the test suite - exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; - commandLine = exeFile + - " --url " + url + - " --no-updater" + - " --no-login-suggestion" - " --testScript " + testScript + " quitWhenFinished" + - " --testResultsLocation " + _snapshotFolder; - - _interfaceWorker->setCommandLine(commandLine); - emit startInterface(); -#elif defined Q_OS_MAC - QFile script; - script.setFileName(_workingFolder + "/runInterfaceTests.sh"); - if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open 'runInterfaceTests.sh'"); - exit(-1); - } - - script.write("#!/bin/sh\n\n"); - - // First, run script to delete any entities in test area - commandLine = - "open -W \"" +_installationFolder + "/interface.app\" --args" + - " --url " + url + - " --no-updater" + - " --no-login-suggestion" - " --testScript " + deleteScript + " quitWhenFinished\n"; - - script.write(commandLine.toStdString().c_str()); - - // On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process - // has started. - // Before starting interface, start a process that will resize interface 10s after it opens - commandLine = _workingFolder +"/waitForStart.sh interface && sleep 10 && " + _workingFolder +"/setInterfaceSizeAndPosition.sh &\n"; - script.write(commandLine.toStdString().c_str()); - - commandLine = - "open \"" +_installationFolder + "/interface.app\" --args" + - " --url " + url + - " --no-updater" + - " --no-login-suggestion" - " --testScript " + testScript + " quitWhenFinished" + - " --testResultsLocation " + _snapshotFolder + - " && " + _workingFolder +"/waitForFinish.sh interface\n"; - - script.write(commandLine.toStdString().c_str()); - - script.close(); - script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); - - commandLine = _workingFolder + "/runInterfaceTests.sh"; - _interfaceWorker->setCommandLine(commandLine); - - emit startInterface(); -#endif - - // Helpful for debugging - appendLog(commandLine); -} - -void TestRunner::interfaceExecutionComplete() { - QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt"); - if (!testCompleted.exists()) { - QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated"); - } - - evaluateResults(); - - killProcesses(); - - // The High Fidelity AppData folder will be restored after evaluation has completed -} - -void TestRunner::evaluateResults() { - updateStatusLabel("Evaluating results"); - nitpick->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); -} - -void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) { - addBuildNumberToResults(zippedFolder); - restoreHighFidelityAppDataFolder(); - - updateStatusLabel("Testing complete"); - - QDateTime currentDateTime = QDateTime::currentDateTime(); - - QString completionText = QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" + - QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + - currentDateTime.date().toString("ddd, MMM d, yyyy"); - - if (numberOfFailures == 0) { - completionText += "; no failures"; - } else if (numberOfFailures == 1) { - completionText += "; 1 failure"; - } else { - completionText += QString("; ") + QString::number(numberOfFailures) + " failures"; - } - appendLog(completionText); - - _automatedTestIsRunning = false; - - _runNow->setEnabled(true); -} - -void TestRunner::addBuildNumberToResults(QString zippedFolderName) { - QString augmentedFilename; - if (!_runLatest->isChecked()) { - augmentedFilename = zippedFolderName.replace("local", getPRNumberFromURL(_url->text())); - } else { - augmentedFilename = zippedFolderName.replace("local", _buildInformation.build); - } - QFile::rename(zippedFolderName, augmentedFilename); -} - -void TestRunner::restoreHighFidelityAppDataFolder() { - _appDataFolder.removeRecursively(); - - if (_savedAppDataFolder != QDir()) { - _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); - } -} - -// Copies a folder recursively -void TestRunner::copyFolder(const QString& source, const QString& destination) { - try { - if (!QFileInfo(source).isDir()) { - // just a file copy - QFile::copy(source, destination); - } else { - QDir destinationDir(destination); - if (!destinationDir.cdUp()) { - throw("'source '" + source + "'seems to be a root folder"); - } - - if (!destinationDir.mkdir(QFileInfo(destination).fileName())) { - throw("Could not create destination folder '" + destination + "'"); - } - - QStringList fileNames = - QDir(source).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); - - foreach (const QString& fileName, fileNames) { - copyFolder(QString(source + "/" + fileName), QString(destination + "/" + fileName)); - } - } - } catch (QString errorMessage) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); - exit(-1); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); - exit(-1); - } -} - -void TestRunner::checkTime() { - // No processing is done if a test is running - if (_automatedTestIsRunning) { - return; - } - - QDateTime now = QDateTime::currentDateTime(); - - // Check day of week - if (!_dayCheckboxes.at(now.date().dayOfWeek() - 1)->isChecked()) { - return; - } - - // Check the time - bool timeToRun{ false }; - - for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { - if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) && - (_timeEdits[i]->time().minute() == now.time().minute())) { - timeToRun = true; - break; - } - } - - if (timeToRun) { - run(); - } -} - -void TestRunner::updateStatusLabel(const QString& message) { - nitpick->updateStatusLabel(message); -} - -void TestRunner::appendLog(const QString& message) { - if (!_logFile.open(QIODevice::Append | QIODevice::Text)) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), - "Could not open the log file"); - exit(-1); - } - - _logFile.write(message.toStdString().c_str()); - _logFile.write("\n"); - _logFile.close(); - - nitpick->appendLogWindow(message); -} - -QString TestRunner::getInstallerNameFromURL(const QString& url) { - // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe - // On Mac, replace `exe` with `dmg` - try { - QStringList urlParts = url.split("/"); - return urlParts[urlParts.size() - 1]; - } catch (QString errorMessage) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); - exit(-1); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); - exit(-1); - } -} - -QString TestRunner::getPRNumberFromURL(const QString& url) { - try { - QStringList urlParts = url.split("/"); - QStringList filenameParts = urlParts[urlParts.size() - 1].split("-"); - if (filenameParts.size() <= 3) { -#ifdef Q_OS_WIN - throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; -#elif defined Q_OS_MAC - throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`"; -#endif - } - return filenameParts[filenameParts.size() - 2]; - } catch (QString errorMessage) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); - exit(-1); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); - exit(-1); - } + nitpick->downloadFiles(urls, _workingFolder, filenames, caller); } void TestRunner::parseBuildInformation() { @@ -802,15 +126,48 @@ void TestRunner::parseBuildInformation() { } _buildInformation.url = element.text(); - } catch (QString errorMessage) { + } + catch (QString errorMessage) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); exit(-1); - } catch (...) { + } + catch (...) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); exit(-1); } } +QString TestRunner::getInstallerNameFromURL(const QString& url) { + // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe + // On Mac, replace `exe` with `dmg` + try { + QStringList urlParts = url.split("/"); + return urlParts[urlParts.size() - 1]; + } + catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } + catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + +void TestRunner::appendLog(const QString& message) { + if (!_logFile.open(QIODevice::Append | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open the log file"); + exit(-1); + } + + _logFile.write(message.toStdString().c_str()); + _logFile.write("\n"); + _logFile.close(); + + nitpick->appendLogWindow(message); +} + void Worker::setCommandLine(const QString& commandLine) { _commandLine = commandLine; } diff --git a/tools/nitpick/src/TestRunner.h b/tools/nitpick/src/TestRunner.h index 00f0f66ecf..d2468ec2fa 100644 --- a/tools/nitpick/src/TestRunner.h +++ b/tools/nitpick/src/TestRunner.h @@ -1,7 +1,7 @@ // // TestRunner.h // -// Created by Nissim Hadar on 1 Sept 2018. +// Created by Nissim Hadar on 23 Jan 2019. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -16,10 +16,9 @@ #include #include #include -#include -#include #include -#include + +class Worker; class BuildInformation { public: @@ -27,67 +26,28 @@ public: QString url; }; -class Worker; - -class TestRunner : public QObject { - Q_OBJECT +class TestRunner { public: - explicit TestRunner(std::vector dayCheckboxes, - std::vector timeEditCheckboxes, - std::vector timeEdits, - QLabel* workingFolderLabel, - QCheckBox* runServerless, - QCheckBox* runLatest, - QLineEdit* url, - QPushButton* runNow, - QObject* parent = 0); + void setWorkingFolder(QLabel* workingFolderLabel); + void downloadBuildXml(void* caller); + void parseBuildInformation(); + QString getInstallerNameFromURL(const QString& url); - ~TestRunner(); - - void setWorkingFolder(); - - void run(); - - void downloadComplete(); - void runInstaller(); - void verifyInstallationSucceeded(); - - void saveExistingHighFidelityAppDataFolder(); - void restoreHighFidelityAppDataFolder(); - - void createSnapshotFolder(); - - void killProcesses(); - void startLocalServerProcesses(); - - void runInterfaceWithTestScript(); - - void evaluateResults(); - void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); - void addBuildNumberToResults(QString zippedFolderName); - - void copyFolder(const QString& source, const QString& destination); - - void updateStatusLabel(const QString& message); void appendLog(const QString& message); - QString getInstallerNameFromURL(const QString& url); - QString getPRNumberFromURL(const QString& url); +protected: + QLabel* _workingFolderLabel; + QLabel* _statusLabel; + QLineEdit* _url; + QCheckBox* _runLatest; - void parseBuildInformation(); + QString _workingFolder; -private slots: - void checkTime(); - void installationComplete(); - void interfaceExecutionComplete(); + const QString DEV_BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; + const QString DEV_BUILD_XML_FILENAME{ "dev-builds.xml" }; -signals: - void startInstaller(); - void startInterface(); - void startResize(); - -private: - bool _automatedTestIsRunning{ false }; + bool buildXMLDownloaded; + BuildInformation _buildInformation; #ifdef Q_OS_WIN const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" }; @@ -97,47 +57,10 @@ private: const QString INSTALLER_FILENAME_LATEST{ "" }; #endif - QString _installerURL; - QString _installerFilename; - const QString DEV_BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" }; - const QString DEV_BUILD_XML_FILENAME{ "dev-builds.xml" }; - - bool buildXMLDownloaded; - - QDir _appDataFolder; - QDir _savedAppDataFolder; - - QString _workingFolder; - QString _installationFolder; - QString _snapshotFolder; - - const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; - const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; - - QString _branch; - QString _user; - - std::vector _dayCheckboxes; - std::vector _timeEditCheckboxes; - std::vector _timeEdits; - QLabel* _workingFolderLabel; - QCheckBox* _runServerless; - QCheckBox* _runLatest; - QLineEdit* _url; - QPushButton* _runNow; - QTimer* _timer; - - QFile _logFile; - QDateTime _testStartDateTime; - QThread* _installerThread; - QThread* _interfaceThread; - - Worker* _installerWorker; - Worker* _interfaceWorker; - - BuildInformation _buildInformation; +private: + QFile _logFile; }; class Worker : public QObject { @@ -150,10 +73,9 @@ public slots: signals: void commandComplete(); - void startInstaller(); - void startInterface(); - + private: QString _commandLine; }; -#endif // hifi_testRunner_h + +#endif diff --git a/tools/nitpick/src/TestRunnerDesktop.cpp b/tools/nitpick/src/TestRunnerDesktop.cpp new file mode 100644 index 0000000000..e45d895886 --- /dev/null +++ b/tools/nitpick/src/TestRunnerDesktop.cpp @@ -0,0 +1,686 @@ +// +// TestRunnerDesktop.cpp +// +// Created by Nissim Hadar on 1 Sept 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRunnerDesktop.h" + +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +#include "Nitpick.h" +extern Nitpick* nitpick; + +TestRunnerDesktop::TestRunnerDesktop( + std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector timeEdits, + QLabel* workingFolderLabel, + QCheckBox* runServerless, + QCheckBox* runLatest, + QLineEdit* url, + QPushButton* runNow, + QLabel* statusLabel, + + QObject* parent +) : QObject(parent) +{ + _dayCheckboxes = dayCheckboxes; + _timeEditCheckboxes = timeEditCheckboxes; + _timeEdits = timeEdits; + _workingFolderLabel = workingFolderLabel; + _runServerless = runServerless; + _runLatest = runLatest; + _url = url; + _runNow = runNow; + _statusLabel = statusLabel; + + _installerThread = new QThread(); + _installerWorker = new InstallerWorker(); + + _installerWorker->moveToThread(_installerThread); + _installerThread->start(); + connect(this, SIGNAL(startInstaller()), _installerWorker, SLOT(runCommand())); + connect(_installerWorker, SIGNAL(commandComplete()), this, SLOT(installationComplete())); + + _interfaceThread = new QThread(); + _interfaceWorker = new InterfaceWorker(); + + _interfaceThread->start(); + _interfaceWorker->moveToThread(_interfaceThread); + connect(this, SIGNAL(startInterface()), _interfaceWorker, SLOT(runCommand())); + connect(_interfaceWorker, SIGNAL(commandComplete()), this, SLOT(interfaceExecutionComplete())); +} + +TestRunnerDesktop::~TestRunnerDesktop() { + delete _installerThread; + delete _installerWorker; + + delete _interfaceThread; + delete _interfaceWorker; + + if (_timer) { + delete _timer; + } +} + +void TestRunnerDesktop::setWorkingFolderAndEnableControls() { + setWorkingFolder(_workingFolderLabel); + +#ifdef Q_OS_WIN + _installationFolder = _workingFolder + "/High Fidelity"; +#elif defined Q_OS_MAC + _installationFolder = _workingFolder + "/High_Fidelity"; +#endif + + nitpick->enableRunTabControls(); + + _timer = new QTimer(this); + connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime())); + _timer->start(30 * 1000); //time specified in ms + +#ifdef Q_OS_MAC + // Create MAC shell scripts + QFile script; + + // This script waits for a process to start + script.setFileName(_workingFolder + "/waitForStart.sh"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'waitForStart.sh'"); + exit(-1); + } + + script.write("#!/bin/sh\n\n"); + script.write("PROCESS=\"$1\"\n"); + script.write("until (pgrep -x $PROCESS >nul)\n"); + script.write("do\n"); + script.write("\techo waiting for \"$1\" to start\n"); + script.write("\tsleep 2\n"); + script.write("done\n"); + script.write("echo \"$1\" \"started\"\n"); + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + + // The Mac shell command returns immediately. This little script waits for a process to finish + script.setFileName(_workingFolder + "/waitForFinish.sh"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'waitForFinish.sh'"); + exit(-1); + } + + script.write("#!/bin/sh\n\n"); + script.write("PROCESS=\"$1\"\n"); + script.write("while (pgrep -x $PROCESS >nul)\n"); + script.write("do\n"); + script.write("\techo waiting for \"$1\" to finish\n"); + script.write("\tsleep 2\n"); + script.write("done\n"); + script.write("echo \"$1\" \"finished\"\n"); + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + + // Create an AppleScript to resize Interface. This is needed so that snapshots taken + // with the primary camera will be the correct size. + // This will be run from a normal shell script + script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.scpt"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'setInterfaceSizeAndPosition.scpt'"); + exit(-1); + } + + script.write("set width to 960\n"); + script.write("set height to 540\n"); + script.write("set x to 100\n"); + script.write("set y to 100\n\n"); + script.write("tell application \"System Events\" to tell application process \"interface\" to tell window 1 to set {size, position} to {{width, height}, {x, y}}\n"); + + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + + script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.sh"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'setInterfaceSizeAndPosition.sh'"); + exit(-1); + } + + script.write("#!/bin/sh\n\n"); + script.write("echo resizing interface\n"); + script.write(("osascript " + _workingFolder + "/setInterfaceSizeAndPosition.scpt\n").toStdString().c_str()); + script.write("echo resize complete\n"); + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); +#endif +} + +void TestRunnerDesktop::run() { + _runNow->setEnabled(false); + + _testStartDateTime = QDateTime::currentDateTime(); + _automatedTestIsRunning = true; + + // Initial setup + _branch = nitpick->getSelectedBranch(); + _user = nitpick->getSelectedUser(); + + // This will be restored at the end of the tests + saveExistingHighFidelityAppDataFolder(); + + _statusLabel->setText("Downloading Build XML"); + downloadBuildXml((void*)this); + + // `downloadComplete` will run after download has completed +} + +void TestRunnerDesktop::downloadComplete() { + if (!buildXMLDownloaded) { + // Download of Build XML has completed + buildXMLDownloaded = true; + + // Download the High Fidelity installer + QStringList urls; + QStringList filenames; + if (_runLatest->isChecked()) { + parseBuildInformation(); + + _installerFilename = INSTALLER_FILENAME_LATEST; + + urls << _buildInformation.url; + filenames << _installerFilename; + } else { + QString urlText = _url->text(); + urls << urlText; + _installerFilename = getInstallerNameFromURL(urlText); + filenames << _installerFilename; + } + + _statusLabel->setText("Downloading installer"); + + nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); + + // `downloadComplete` will run again after download has completed + + } else { + // Download of Installer has completed + appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" + + QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + _testStartDateTime.date().toString("ddd, MMM d, yyyy")); + + _statusLabel->setText("Installing"); + + // Kill any existing processes that would interfere with installation + killProcesses(); + + runInstaller(); + } +} + +void TestRunnerDesktop::runInstaller() { + // Qt cannot start an installation process using QProcess::start (Qt Bug 9761) + // To allow installation, the installer is run using the `system` command + + QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) }; + + QString installerFullPath = _workingFolder + "/" + _installerFilename; + + QString commandLine; +#ifdef Q_OS_WIN + commandLine = "\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder); +#elif defined Q_OS_MAC + // Create installation shell script + QFile script; + script.setFileName(_workingFolder + "/install_app.sh"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'install_app.sh'"); + exit(-1); + } + + if (!QDir().exists(_installationFolder)) { + QDir().mkdir(_installationFolder); + } + + // This script installs High Fidelity. It is run as "yes | install_app.sh... so "yes" is killed at the end + script.write("#!/bin/sh\n\n"); + script.write("VOLUME=`hdiutil attach \"$1\" | grep Volumes | awk '{print $3}'`\n"); + + QString folderName {"High Fidelity"}; + if (!_runLatest->isChecked()) { + folderName += QString(" - ") + getPRNumberFromURL(_url->text()); + } + + script.write((QString("cp -rf \"$VOLUME/") + folderName + "/interface.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str()); + script.write((QString("cp -rf \"$VOLUME/") + folderName + "/Sandbox.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str()); + + script.write("hdiutil detach \"$VOLUME\"\n"); + script.write("killall yes\n"); + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + commandLine = "yes | " + _workingFolder + "/install_app.sh " + installerFullPath; +#endif + appendLog(commandLine); + _installerWorker->setCommandLine(commandLine); + emit startInstaller(); +} + +void TestRunnerDesktop::installationComplete() { + verifyInstallationSucceeded(); + + createSnapshotFolder(); + + _statusLabel->setText("Running tests"); + + if (!_runServerless->isChecked()) { + startLocalServerProcesses(); + } + + runInterfaceWithTestScript(); +} + +void TestRunnerDesktop::verifyInstallationSucceeded() { + // Exit if the executables are missing. + // On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error +#ifdef Q_OS_WIN + QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe"); + QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe"); + QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe"); + + if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) { + QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled"); + exit(-1); + } +#endif +} + +void TestRunnerDesktop::saveExistingHighFidelityAppDataFolder() { + QString dataDirectory{ "NOT FOUND" }; +#ifdef Q_OS_WIN + dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming"; +#elif defined Q_OS_MAC + dataDirectory = QDir::homePath() + "/Library/Application Support"; +#endif + if (_runLatest->isChecked()) { + _appDataFolder = dataDirectory + "/High Fidelity"; + } else { + // We are running a PR build + _appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text()); + } + + _savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME; + if (QDir(_savedAppDataFolder).exists()) { + _savedAppDataFolder.removeRecursively(); + } + if (_appDataFolder.exists()) { + // The original folder is saved in a unique name + _appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path()); + } + + // Copy an "empty" AppData folder (i.e. no entities) + QDir canonicalAppDataFolder; +#ifdef Q_OS_WIN + canonicalAppDataFolder = QDir::currentPath() + "/AppDataHighFidelity"; +#elif defined Q_OS_MAC + canonicalAppDataFolder = QCoreApplication::applicationDirPath() + "/AppDataHighFidelity"; +#endif + if (canonicalAppDataFolder.exists()) { + copyFolder(canonicalAppDataFolder.path(), _appDataFolder.path()); + } else { + QMessageBox::critical(0, "Internal error", "The nitpick AppData folder cannot be found at:\n" + canonicalAppDataFolder.path()); + exit(-1); + } +} + +void TestRunnerDesktop::createSnapshotFolder() { + _snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME; + + // Just delete all PNGs from the folder if it already exists + if (QDir(_snapshotFolder).exists()) { + // Note that we cannot use just a `png` filter, as the filenames include periods + // Also, delete any `jpg` and `txt` files + // The idea is to leave only previous zipped result folders + QDirIterator it(_snapshotFolder); + while (it.hasNext()) { + QString filename = it.next(); + if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") { + QFile::remove(filename); + } + } + + } else { + QDir().mkdir(_snapshotFolder); + } +} + +void TestRunnerDesktop::killProcesses() { +#ifdef Q_OS_WIN + try { + QStringList processesToKill = QStringList() << "interface.exe" + << "assignment-client.exe" + << "domain-server.exe" + << "server-console.exe"; + + // Loop until all pending processes to kill have actually died + QStringList pendingProcessesToKill; + do { + pendingProcessesToKill.clear(); + + // Get list of running tasks + HANDLE processSnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (processSnapHandle == INVALID_HANDLE_VALUE) { + throw("Process snapshot creation failure"); + } + + PROCESSENTRY32 processEntry32; + processEntry32.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(processSnapHandle, &processEntry32)) { + CloseHandle(processSnapHandle); + throw("Process32First failed"); + } + + // Kill any task in the list + do { + foreach (QString process, processesToKill) + if (QString(processEntry32.szExeFile) == process) { + QString commandLine = "taskkill /im " + process + " /f >nul"; + system(commandLine.toStdString().c_str()); + pendingProcessesToKill << process; + } + } while (Process32Next(processSnapHandle, &processEntry32)); + + QThread::sleep(2); + } while (!pendingProcessesToKill.isEmpty()); + + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +#elif defined Q_OS_MAC + QString commandLine; + + commandLine = QString("killall interface") + "; " + _workingFolder +"/waitForFinish.sh interface"; + system(commandLine.toStdString().c_str()); + + commandLine = QString("killall Sandbox") + "; " + _workingFolder +"/waitForFinish.sh Sandbox"; + system(commandLine.toStdString().c_str()); + + commandLine = QString("killall Console") + "; " + _workingFolder +"/waitForFinish.sh Console"; + system(commandLine.toStdString().c_str()); +#endif +} + +void TestRunnerDesktop::startLocalServerProcesses() { + QString commandLine; + +#ifdef Q_OS_WIN + commandLine = + "start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\""; + system(commandLine.toStdString().c_str()); + + commandLine = + "start \"assignment-client.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\" -n 6"; + system(commandLine.toStdString().c_str()); + +#elif defined Q_OS_MAC + commandLine = "open \"" +_installationFolder + "/Sandbox.app\""; + system(commandLine.toStdString().c_str()); +#endif + + // Give server processes time to stabilize + QThread::sleep(20); +} + +void TestRunnerDesktop::runInterfaceWithTestScript() { + QString url = QString("hifi://localhost"); + if (_runServerless->isChecked()) { + // Move to an empty area + url = "file:///~serverless/tutorial.json"; + } else { + url = "hifi://localhost"; + } + + QString deleteScript = + QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js"; + + QString testScript = + QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; + + QString commandLine; +#ifdef Q_OS_WIN + QString exeFile; + // First, run script to delete any entities in test area + // Note that this will run to completion before continuing + exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; + commandLine = "start /wait \"\" " + exeFile + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + deleteScript + " quitWhenFinished"; + + system(commandLine.toStdString().c_str()); + + // Now run the test suite + exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; + commandLine = exeFile + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + testScript + " quitWhenFinished" + + " --testResultsLocation " + _snapshotFolder; + + _interfaceWorker->setCommandLine(commandLine); + emit startInterface(); +#elif defined Q_OS_MAC + QFile script; + script.setFileName(_workingFolder + "/runInterfaceTests.sh"); + if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), + "Could not open 'runInterfaceTests.sh'"); + exit(-1); + } + + script.write("#!/bin/sh\n\n"); + + // First, run script to delete any entities in test area + commandLine = + "open -W \"" +_installationFolder + "/interface.app\" --args" + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + deleteScript + " quitWhenFinished\n"; + + script.write(commandLine.toStdString().c_str()); + + // On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process + // has started. + // Before starting interface, start a process that will resize interface 10s after it opens + commandLine = _workingFolder +"/waitForStart.sh interface && sleep 10 && " + _workingFolder +"/setInterfaceSizeAndPosition.sh &\n"; + script.write(commandLine.toStdString().c_str()); + + commandLine = + "open \"" +_installationFolder + "/interface.app\" --args" + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + testScript + " quitWhenFinished" + + " --testResultsLocation " + _snapshotFolder + + " && " + _workingFolder +"/waitForFinish.sh interface\n"; + + script.write(commandLine.toStdString().c_str()); + + script.close(); + script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner); + + commandLine = _workingFolder + "/runInterfaceTests.sh"; + _interfaceWorker->setCommandLine(commandLine); + + emit startInterface(); +#endif + + // Helpful for debugging + appendLog(commandLine); +} + +void TestRunnerDesktop::interfaceExecutionComplete() { + QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt"); + if (!testCompleted.exists()) { + QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated"); + } + + evaluateResults(); + + killProcesses(); + + // The High Fidelity AppData folder will be restored after evaluation has completed +} + +void TestRunnerDesktop::evaluateResults() { + _statusLabel->setText("Evaluating results"); + nitpick->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user); +} + +void TestRunnerDesktop::automaticTestRunEvaluationComplete(const QString& zippedFolder, int numberOfFailures) { + addBuildNumberToResults(zippedFolder); + restoreHighFidelityAppDataFolder(); + + _statusLabel->setText("Testing complete"); + + QDateTime currentDateTime = QDateTime::currentDateTime(); + + QString completionText = QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" + + QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " + + currentDateTime.date().toString("ddd, MMM d, yyyy"); + + if (numberOfFailures == 0) { + completionText += "; no failures"; + } else if (numberOfFailures == 1) { + completionText += "; 1 failure"; + } else { + completionText += QString("; ") + QString::number(numberOfFailures) + " failures"; + } + appendLog(completionText); + + _automatedTestIsRunning = false; + + _runNow->setEnabled(true); +} + +void TestRunnerDesktop::addBuildNumberToResults(const QString& zippedFolderName) { + QString augmentedFilename { zippedFolderName }; + if (!_runLatest->isChecked()) { + augmentedFilename.replace("local", getPRNumberFromURL(_url->text())); + } else { + augmentedFilename.replace("local", _buildInformation.build); + } + + if (!QFile::rename(zippedFolderName, augmentedFilename)) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not rename '" + zippedFolderName + "' to '" + augmentedFilename); + exit(-1); + + } +} + +void TestRunnerDesktop::restoreHighFidelityAppDataFolder() { + _appDataFolder.removeRecursively(); + + if (_savedAppDataFolder != QDir()) { + _appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path()); + } +} + +// Copies a folder recursively +void TestRunnerDesktop::copyFolder(const QString& source, const QString& destination) { + try { + if (!QFileInfo(source).isDir()) { + // just a file copy + QFile::copy(source, destination); + } else { + QDir destinationDir(destination); + if (!destinationDir.cdUp()) { + throw("'source '" + source + "'seems to be a root folder"); + } + + if (!destinationDir.mkdir(QFileInfo(destination).fileName())) { + throw("Could not create destination folder '" + destination + "'"); + } + + QStringList fileNames = + QDir(source).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + + foreach (const QString& fileName, fileNames) { + copyFolder(QString(source + "/" + fileName), QString(destination + "/" + fileName)); + } + } + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} + +void TestRunnerDesktop::checkTime() { + // No processing is done if a test is running + if (_automatedTestIsRunning) { + return; + } + + QDateTime now = QDateTime::currentDateTime(); + + // Check day of week + if (!_dayCheckboxes.at(now.date().dayOfWeek() - 1)->isChecked()) { + return; + } + + // Check the time + bool timeToRun{ false }; + + for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) { + if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) && + (_timeEdits[i]->time().minute() == now.time().minute())) { + timeToRun = true; + break; + } + } + + if (timeToRun) { + run(); + } +} + +QString TestRunnerDesktop::getPRNumberFromURL(const QString& url) { + try { + QStringList urlParts = url.split("/"); + QStringList filenameParts = urlParts[urlParts.size() - 1].split("-"); + if (filenameParts.size() <= 3) { +#ifdef Q_OS_WIN + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; +#elif defined Q_OS_MAC + throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`"; +#endif + } + return filenameParts[filenameParts.size() - 2]; + } catch (QString errorMessage) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage); + exit(-1); + } catch (...) { + QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error"); + exit(-1); + } +} diff --git a/tools/nitpick/src/TestRunnerDesktop.h b/tools/nitpick/src/TestRunnerDesktop.h new file mode 100644 index 0000000000..140a81f465 --- /dev/null +++ b/tools/nitpick/src/TestRunnerDesktop.h @@ -0,0 +1,124 @@ +// +// TestRunnerDesktop.h +// +// Created by Nissim Hadar on 1 Sept 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_testRunnerDesktop_h +#define hifi_testRunnerDesktop_h + +#include +#include +#include +#include +#include +#include + +#include "TestRunner.h" + +class InterfaceWorker; +class InstallerWorker; + +class TestRunnerDesktop : public QObject, public TestRunner { + Q_OBJECT +public: + explicit TestRunnerDesktop( + std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector timeEdits, + QLabel* workingFolderLabel, + QCheckBox* runServerless, + QCheckBox* runLatest, + QLineEdit* url, + QPushButton* runNow, + QLabel* statusLabel, + + QObject* parent = 0 + ); + + ~TestRunnerDesktop(); + + void setWorkingFolderAndEnableControls(); + + void run(); + + void downloadComplete(); + void runInstaller(); + void verifyInstallationSucceeded(); + + void saveExistingHighFidelityAppDataFolder(); + void restoreHighFidelityAppDataFolder(); + + void createSnapshotFolder(); + + void killProcesses(); + void startLocalServerProcesses(); + + void runInterfaceWithTestScript(); + + void evaluateResults(); + void automaticTestRunEvaluationComplete(const QString& zippedFolderName, int numberOfFailures); + void addBuildNumberToResults(const QString& zippedFolderName); + + void copyFolder(const QString& source, const QString& destination); + + QString getPRNumberFromURL(const QString& url); + +private slots: + void checkTime(); + void installationComplete(); + void interfaceExecutionComplete(); + +signals: + void startInstaller(); + void startInterface(); + void startResize(); + +private: + bool _automatedTestIsRunning{ false }; + + QString _installerURL; + QString _installerFilename; + + QDir _appDataFolder; + QDir _savedAppDataFolder; + + QString _installationFolder; + QString _snapshotFolder; + + const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; + + QString _branch; + QString _user; + + std::vector _dayCheckboxes; + std::vector _timeEditCheckboxes; + std::vector _timeEdits; + QLabel* _workingFolderLabel; + QCheckBox* _runServerless; + QPushButton* _runNow; + QTimer* _timer; + QThread* _installerThread; + QThread* _interfaceThread; + + InstallerWorker* _installerWorker; + InterfaceWorker* _interfaceWorker; +}; + +class InstallerWorker : public Worker { + Q_OBJECT +signals: + void startInstaller(); +}; + +class InterfaceWorker : public Worker { + Q_OBJECT +signals: + void startInterface(); +}; +#endif diff --git a/tools/nitpick/src/TestRunnerMobile.cpp b/tools/nitpick/src/TestRunnerMobile.cpp new file mode 100644 index 0000000000..ab276f3337 --- /dev/null +++ b/tools/nitpick/src/TestRunnerMobile.cpp @@ -0,0 +1,189 @@ +// +// TestRunnerMobile.cpp +// +// Created by Nissim Hadar on 22 Jan 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TestRunnerMobile.h" + +#include +#include +#include + +#include "Nitpick.h" +extern Nitpick* nitpick; + +TestRunnerMobile::TestRunnerMobile( + QLabel* workingFolderLabel, + QPushButton *connectDeviceButton, + QPushButton *pullFolderButton, + QLabel* detectedDeviceLabel, + QLineEdit *folderLineEdit, + QPushButton* downloadAPKPushbutton, + QPushButton* installAPKPushbutton, + QPushButton* runInterfacePushbutton, + QCheckBox* runLatest, + QLineEdit* url, + QLabel* statusLabel, + + QObject* parent +) : QObject(parent), _adbInterface(NULL) +{ + _workingFolderLabel = workingFolderLabel; + _connectDeviceButton = connectDeviceButton; + _pullFolderButton = pullFolderButton; + _detectedDeviceLabel = detectedDeviceLabel; + _folderLineEdit = folderLineEdit; + _downloadAPKPushbutton = downloadAPKPushbutton; + _installAPKPushbutton = installAPKPushbutton; + _runInterfacePushbutton = runInterfacePushbutton; + _runLatest = runLatest; + _url = url; + _statusLabel = statusLabel; + + folderLineEdit->setText("/sdcard/DCIM/TEST"); + + modelNames["SM_G955U1"] = "Samsung S8+ unlocked"; +} + +TestRunnerMobile::~TestRunnerMobile() { +} + +void TestRunnerMobile::setWorkingFolderAndEnableControls() { + setWorkingFolder(_workingFolderLabel); + + _connectDeviceButton->setEnabled(true); +} + +void TestRunnerMobile::connectDevice() { +#if defined Q_OS_WIN || defined Q_OS_MAC + if (!_adbInterface) { + _adbInterface = new AdbInterface(); + } + + QString devicesFullFilename{ _workingFolder + "/devices.txt" }; + QString command = _adbInterface->getAdbCommand() + " devices -l > " + devicesFullFilename; + system(command.toStdString().c_str()); + + if (!QFile::exists(devicesFullFilename)) { + QMessageBox::critical(0, "Internal error", "devicesFullFilename not found"); + exit (-1); + } + + // Device should be in second line + QFile devicesFile(devicesFullFilename); + devicesFile.open(QIODevice::ReadOnly | QIODevice::Text); + QString line1 = devicesFile.readLine(); + QString line2 = devicesFile.readLine(); + + const QString DEVICE{ "device" }; + if (line2.contains("unauthorized")) { + QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device"); + } else if (line2.contains(DEVICE)) { + // Make sure only 1 device + QString line3 = devicesFile.readLine(); + if (line3.contains(DEVICE)) { + QMessageBox::critical(0, "Too many devices detected", "Tests will run only if a single device is attached"); + } else { + // Line looks like this: 988a1b47335239434b device product:dream2qlteue model:SM_G955U1 device:dream2qlteue transport_id:2 + QStringList tokens = line2.split(QRegExp("[\r\n\t ]+")); + QString deviceID = tokens[0]; + + QString modelID = tokens[3].split(':')[1]; + QString modelName = "UKNOWN"; + if (modelNames.count(modelID) == 1) { + modelName = modelNames[modelID]; + } + + _detectedDeviceLabel->setText(modelName + " [" + deviceID + "]"); + _pullFolderButton->setEnabled(true); + _folderLineEdit->setEnabled(true); + _downloadAPKPushbutton->setEnabled(true); + } + } +#endif +} + +void TestRunnerMobile::downloadAPK() { + downloadBuildXml((void*)this); +} + + +void TestRunnerMobile::downloadComplete() { + if (!buildXMLDownloaded) { + // Download of Build XML has completed + buildXMLDownloaded = true; + + // Download the High Fidelity installer + QStringList urls; + QStringList filenames; + if (_runLatest->isChecked()) { + parseBuildInformation(); + + _installerFilename = INSTALLER_FILENAME_LATEST; + + + // Replace the `exe` extension with `apk` + _installerFilename = _installerFilename.replace(_installerFilename.length() - 3, 3, "apk"); + _buildInformation.url = _buildInformation.url.replace(_buildInformation.url.length() - 3, 3, "apk"); + + urls << _buildInformation.url; + filenames << _installerFilename; + } else { + QString urlText = _url->text(); + urls << urlText; + _installerFilename = getInstallerNameFromURL(urlText); + filenames << _installerFilename; + } + + _statusLabel->setText("Downloading installer"); + + nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this); + } else { + _statusLabel->setText("Installer download complete"); + _installAPKPushbutton->setEnabled(true); + } +} + +void TestRunnerMobile::installAPK() { +#if defined Q_OS_WIN || defined Q_OS_MAC + if (!_adbInterface) { + _adbInterface = new AdbInterface(); + } + + _statusLabel->setText("Installing"); + QString command = _adbInterface->getAdbCommand() + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt"; + system(command.toStdString().c_str()); + _statusLabel->setText("Installation complete"); + _runInterfacePushbutton->setEnabled(true); +#endif +} + +void TestRunnerMobile::runInterface() { +#if defined Q_OS_WIN || defined Q_OS_MAC + if (!_adbInterface) { + _adbInterface = new AdbInterface(); + } + + _statusLabel->setText("Starting Interface"); + QString command = _adbInterface->getAdbCommand() + " shell monkey -p io.highfidelity.hifiinterface -v 1"; + system(command.toStdString().c_str()); + _statusLabel->setText("Interface started"); +#endif +} + +void TestRunnerMobile::pullFolder() { +#if defined Q_OS_WIN || defined Q_OS_MAC + if (!_adbInterface) { + _adbInterface = new AdbInterface(); + } + + _statusLabel->setText("Pulling folder"); + QString command = _adbInterface->getAdbCommand() + " pull " + _folderLineEdit->text() + " " + _workingFolder + _installerFilename; + system(command.toStdString().c_str()); + _statusLabel->setText("Pull complete"); +#endif +} diff --git a/tools/nitpick/src/TestRunnerMobile.h b/tools/nitpick/src/TestRunnerMobile.h new file mode 100644 index 0000000000..52c2ba096d --- /dev/null +++ b/tools/nitpick/src/TestRunnerMobile.h @@ -0,0 +1,77 @@ +// +// TestRunnerMobile.h +// +// Created by Nissim Hadar on 22 Jan 2019. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_testRunnerMobile_h +#define hifi_testRunnerMobile_h + +#include +#include +#include +#include + +#include "TestRunner.h" +#include "AdbInterface.h" + +class TestRunnerMobile : public QObject, public TestRunner { + Q_OBJECT +public: + explicit TestRunnerMobile( + QLabel* workingFolderLabel, + QPushButton *connectDeviceButton, + QPushButton *pullFolderButton, + QLabel* detectedDeviceLabel, + QLineEdit *folderLineEdit, + QPushButton* downloadAPKPushbutton, + QPushButton* installAPKPushbutton, + QPushButton* runInterfacePushbutton, + QCheckBox* runLatest, + QLineEdit* url, + QLabel* statusLabel, + + QObject* parent = 0 + ); + ~TestRunnerMobile(); + + void setWorkingFolderAndEnableControls(); + void connectDevice(); + + void downloadComplete(); + void downloadAPK(); + void runInterface(); + + void installAPK(); + + void pullFolder(); + +private: + QPushButton* _connectDeviceButton; + QPushButton* _pullFolderButton; + QLabel* _detectedDeviceLabel; + QLineEdit* _folderLineEdit; + QPushButton* _downloadAPKPushbutton; + QPushButton* _installAPKPushbutton; + QPushButton* _runInterfacePushbutton; + +#ifdef Q_OS_WIN + const QString _adbExe{ "adb.exe" }; +#else + // Both Mac and Linux use "adb" + const QString _adbExe{ "adb" }; +#endif + + QString _installerFilename; + + QString _adbCommand; + + std::map modelNames; + + AdbInterface* _adbInterface; +}; +#endif diff --git a/tools/nitpick/src/main.cpp b/tools/nitpick/src/main.cpp index 089a72e6ce..a2784a40b3 100644 --- a/tools/nitpick/src/main.cpp +++ b/tools/nitpick/src/main.cpp @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include -#include "ui/Nitpick.h" +#include "Nitpick.h" #include diff --git a/tools/nitpick/src/ui/BusyWindow.ui b/tools/nitpick/ui/BusyWindow.ui similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.ui rename to tools/nitpick/ui/BusyWindow.ui diff --git a/tools/nitpick/src/ui/MismatchWindow.ui b/tools/nitpick/ui/MismatchWindow.ui similarity index 100% rename from tools/nitpick/src/ui/MismatchWindow.ui rename to tools/nitpick/ui/MismatchWindow.ui diff --git a/tools/nitpick/src/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui similarity index 72% rename from tools/nitpick/src/ui/Nitpick.ui rename to tools/nitpick/ui/Nitpick.ui index 78f7dcf2bf..79bdfd158b 100644 --- a/tools/nitpick/src/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -20,7 +20,7 @@ Nitpick - + 470 @@ -49,10 +49,10 @@ Create - + - 195 + 210 60 220 40 @@ -62,7 +62,7 @@ Create Tests - + 70 @@ -75,7 +75,7 @@ Create MD file - + 320 @@ -85,13 +85,13 @@ - Create all MD files + Create all MD files - + - 195 + 210 120 220 40 @@ -101,7 +101,7 @@ Create Tests Outline - + 70 @@ -114,7 +114,7 @@ Create Recursive Script - + 320 @@ -124,10 +124,10 @@ - Create all Recursive Scripts + Create all Recursive Scripts - + 70 @@ -140,7 +140,7 @@ Create testAuto script - + 320 @@ -158,7 +158,7 @@ Windows - + 200 @@ -171,7 +171,7 @@ Hide Windows Taskbar - + 200 @@ -187,9 +187,9 @@ - Run + Test on Desktop - + false @@ -420,26 +420,26 @@ - + 10 20 - 161 - 28 + 160 + 30 Set Working Folder - + 190 20 - 321 - 31 + 320 + 30 @@ -469,7 +469,7 @@ Status: - + 350 @@ -501,7 +501,7 @@ false - + 20 @@ -533,7 +533,7 @@ URL - + false @@ -547,6 +547,201 @@ + + + Test on Mobile + + + + false + + + + 10 + 90 + 160 + 30 + + + + Connect Device + + + + + + 190 + 96 + 320 + 30 + + + + (not detected) + + + + + + 10 + 20 + 160 + 30 + + + + Set Working Folder + + + + + + 190 + 20 + 320 + 30 + + + + (not set...) + + + + + false + + + + 460 + 410 + 160 + 30 + + + + Pull folder + + + + + false + + + + 10 + 410 + 440 + 30 + + + + + + false + + + + 170 + 170 + 451 + 21 + + + + + + + 20 + 170 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Latest + + + true + + + + + false + + + + 10 + 210 + 160 + 30 + + + + Download APK + + + + + + 300 + 60 + 41 + 31 + + + + Status: + + + + + + 350 + 60 + 271 + 31 + + + + ####### + + + + + false + + + + 10 + 250 + 160 + 30 + + + + Install APK + + + + + false + + + + 10 + 300 + 160 + 30 + + + + Run Interface + + + Evaluate @@ -567,7 +762,7 @@ Interactive Mode - + 330 @@ -585,7 +780,7 @@ Web Interface - + 240 @@ -614,7 +809,7 @@ true - + 240 @@ -627,7 +822,7 @@ Create Run - + 240 @@ -678,7 +873,7 @@ Amazon Web Services - + true @@ -719,10 +914,10 @@ groupBox - updateTestRailRunResultsButton + updateTestRailRunResultsPushbutton createPythonScriptRadioButton - createTestRailRunButton - createTestRailTestCasesButton + createTestRailRunPushbutton + createTestRailTestCasesPushbutton createXMLScriptRadioButton groupBox_2 @@ -851,17 +1046,17 @@ userLineEdit branchLineEdit - createTestsButton - createMDFileButton - createAllMDFilesButton - createTestsOutlineButton - createRecursiveScriptButton - createAllRecursiveScriptsButton - createTestAutoScriptButton - createAllTestAutoScriptsButton - hideTaskbarButton - showTaskbarButton - runNowButton + createTestsPushbutton + createMDFilePushbutton + createAllMDFilesPushbutton + createTestsOutlinePushbutton + createRecursiveScriptPushbutton + createAllRecursiveScriptsPushbutton + createTestAutoScriptPushbutton + createAllTestAutoScriptsPushbutton + hideTaskbarPushbutton + showTaskbarPushbutton + runNowPushbutton sundayCheckBox wednesdayCheckBox tuesdayCheckBox @@ -877,22 +1072,22 @@ timeEdit2checkBox timeEdit3checkBox timeEdit4checkBox - setWorkingFolderButton + setWorkingFolderRunOnDesktopPushbutton plainTextEdit checkBoxServerless - checkBoxRunLatest - urlLineEdit + runLatestOnDesktopCheckBox + urlOnDesktopLineEdit checkBoxInteractiveMode - evaluateTestsButton - updateTestRailRunResultsButton + evaluateTestsPushbutton + updateTestRailRunResultsPushbutton createPythonScriptRadioButton - createTestRailRunButton - createTestRailTestCasesButton + createTestRailRunPushbutton + createTestRailTestCasesPushbutton createXMLScriptRadioButton - createWebPagePushButton + createWebPagePushbutton updateAWSCheckBox awsURLLineEdit - closeButton + closePushbutton tabWidget diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui b/tools/nitpick/ui/TestRailResultsSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui rename to tools/nitpick/ui/TestRailResultsSelectorWindow.ui diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.ui b/tools/nitpick/ui/TestRailRunSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.ui rename to tools/nitpick/ui/TestRailRunSelectorWindow.ui diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui b/tools/nitpick/ui/TestRailTestCasesSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui rename to tools/nitpick/ui/TestRailTestCasesSelectorWindow.ui diff --git a/tools/noramlizeFrame.py b/tools/noramlizeFrame.py new file mode 100644 index 0000000000..f1012f6428 --- /dev/null +++ b/tools/noramlizeFrame.py @@ -0,0 +1,62 @@ +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 = sourceKtx[1:] + while sourceKtx.startswith('/'): + sourceKtx = sourceKtx[1:] + sourceKtx = os.path.join(self.resDir, sourceKtx) + 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/20190114_1629.json") +fp.process() + + +#C:\Users\bdavi\git\hifi\interface\resources\meshes \ No newline at end of file diff --git a/tools/skeleton-dump/CMakeLists.txt b/tools/skeleton-dump/CMakeLists.txt index baec1d163b..7d4248d171 100644 --- a/tools/skeleton-dump/CMakeLists.txt +++ b/tools/skeleton-dump/CMakeLists.txt @@ -2,3 +2,5 @@ set(TARGET_NAME skeleton-dump) setup_hifi_project(Core) setup_memory_debugger() link_hifi_libraries(shared fbx hfm graphics gpu gl animation) + +include_hifi_library_headers(image) diff --git a/tools/vhacd-util/CMakeLists.txt b/tools/vhacd-util/CMakeLists.txt index aa6642c610..90cfdf878a 100644 --- a/tools/vhacd-util/CMakeLists.txt +++ b/tools/vhacd-util/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME vhacd-util) setup_hifi_project(Core) link_hifi_libraries(shared fbx hfm graphics gpu gl) +include_hifi_library_headers(image) + add_dependency_external_projects(vhacd) find_package(VHACD REQUIRED)