diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6956fd22c3..1ae139e69b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,8 +80,9 @@ endif()
if (ANDROID)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
+ add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
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..a210c8300e 100644
--- a/android/apps/framePlayer/build.gradle
+++ b/android/apps/framePlayer/build.gradle
@@ -3,10 +3,10 @@ apply plugin: 'com.android.application'
android {
signingConfigs {
release {
- keyAlias 'key0'
- keyPassword 'password'
- storeFile file('C:/android/keystore.jks')
- storePassword 'password'
+ storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
+ storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
+ keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
+ keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
}
}
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..6428044df0 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
@@ -31,6 +31,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;
@@ -166,8 +167,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/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..899f9cb955
--- /dev/null
+++ b/android/apps/questFramePlayer/build.gradle
@@ -0,0 +1,51 @@
+apply plugin: 'com.android.application'
+
+android {
+ signingConfigs {
+ release {
+ storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
+ storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
+ keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
+ keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
+ }
+ }
+
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "io.highfidelity.frameplayer"
+ minSdkVersion 25
+ targetSdkVersion 28
+ ndk { abiFilters 'arm64-v8a' }
+ externalNativeBuild {
+ cmake {
+ arguments '-DHIFI_ANDROID=1',
+ '-DHIFI_ANDROID_APP=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
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ }
+
+ externalNativeBuild.cmake.path '../../../CMakeLists.txt'
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
+ implementation project(':oculus')
+ implementation project(':qt')
+}
diff --git a/android/apps/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..40e1863d69 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();
}
//---------------------------------------------------------------------------
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/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/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..81b9935aa5 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -265,7 +265,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)
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/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 100%
rename from interface/resources/qml/+android/Web3DSurface.qml
rename to interface/resources/qml/+android_interface/Web3DSurface.qml
diff --git a/interface/resources/qml/+webengine/Browser.qml b/interface/resources/qml/+webengine/Browser.qml
new file mode 100644
index 0000000000..52157bdf42
--- /dev/null
+++ b/interface/resources/qml/+webengine/Browser.qml
@@ -0,0 +1,275 @@
+import QtQuick 2.5
+import QtWebChannel 1.0
+import QtWebEngine 1.5
+
+import controlsUit 1.0
+import stylesUit 1.0
+import "qrc:////qml//windows"
+
+ScrollingWindow {
+ id: root
+ HifiConstants { id: hifi }
+ //HifiStyles.HifiConstants { id: hifistyles }
+ title: "Browser"
+ resizable: true
+ destroyOnHidden: true
+ width: 800
+ height: 600
+ property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
+ property alias url: webview.url
+ property alias webView: webview
+
+ signal loadingChanged(int status)
+
+ x: 100
+ y: 100
+
+ Component.onCompleted: {
+ focus = true
+ shown = true
+ addressBar.text = webview.url
+ }
+
+ function setProfile(profile) {
+ webview.profile = profile;
+ }
+
+ function showPermissionsBar(){
+ permissionsContainer.visible=true;
+ }
+
+ function hidePermissionsBar(){
+ permissionsContainer.visible=false;
+ }
+
+ function allowPermissions(){
+ webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
+ hidePermissionsBar();
+ }
+
+ function setAutoAdd(auto) {
+ desktop.setAutoAdd(auto);
+ }
+
+ Item {
+ id:item
+ width: pane.contentWidth
+ implicitHeight: pane.scrollHeight
+
+ Row {
+ id: buttons
+ spacing: 4
+ anchors.top: parent.top
+ anchors.topMargin: 8
+ anchors.left: parent.left
+ anchors.leftMargin: 8
+ HiFiGlyphs {
+ id: back;
+ enabled: webview.canGoBack;
+ text: hifi.glyphs.backward
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
+ }
+
+ HiFiGlyphs {
+ id: forward;
+ enabled: webview.canGoForward;
+ text: hifi.glyphs.forward
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
+ }
+
+ HiFiGlyphs {
+ id: reload;
+ enabled: webview.canGoForward;
+ text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
+ }
+
+ }
+
+ Item {
+ id: border
+ height: 48
+ anchors.top: parent.top
+ anchors.topMargin: 8
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ anchors.left: buttons.right
+ anchors.leftMargin: 8
+
+ Item {
+ id: barIcon
+ width: parent.height
+ height: parent.height
+ Image {
+ source: webview.icon;
+ x: (parent.height - height) / 2
+ y: (parent.width - width) / 2
+ sourceSize: Qt.size(width, height);
+ verticalAlignment: Image.AlignVCenter;
+ horizontalAlignment: Image.AlignHCenter
+ }
+ }
+
+ TextField {
+ id: addressBar
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ anchors.left: barIcon.right
+ anchors.leftMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ focus: true
+ colorScheme: hifi.colorSchemes.dark
+ placeholderText: "Enter URL"
+ Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i")
+ Keys.onPressed: {
+ switch(event.key) {
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ event.accepted = true
+ if (text.indexOf("http") != 0) {
+ text = "http://" + text;
+ }
+ root.hidePermissionsBar();
+ root.keyboardRaised = false;
+ webview.url = text;
+ break;
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id:permissionsContainer
+ visible:false
+ color: "#000000"
+ width: parent.width
+ anchors.top: buttons.bottom
+ height:40
+ z:100
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "black" }
+ GradientStop { position: 1.0; color: "grey" }
+ }
+
+ RalewayLight {
+ id: permissionsInfo
+ anchors.right:permissionsRow.left
+ anchors.rightMargin: 32
+ anchors.topMargin:8
+ anchors.top:parent.top
+ text: "This site wants to use your microphone/camera"
+ size: 18
+ color: hifi.colors.white
+ }
+
+ Row {
+ id: permissionsRow
+ spacing: 4
+ anchors.top:parent.top
+ anchors.topMargin: 8
+ anchors.right: parent.right
+ visible: true
+ z:101
+
+ Button {
+ id:allow
+ text: "Allow"
+ color: hifi.buttons.blue
+ colorScheme: root.colorScheme
+ width: 120
+ enabled: true
+ onClicked: root.allowPermissions();
+ z:101
+ }
+
+ Button {
+ id:block
+ text: "Block"
+ color: hifi.buttons.red
+ colorScheme: root.colorScheme
+ width: 120
+ enabled: true
+ onClicked: root.hidePermissionsBar();
+ z:101
+ }
+ }
+ }
+
+ WebView {
+ 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 ]
+
+ 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
+
+
+ Keys.onPressed: {
+ switch(event.key) {
+ case Qt.Key_L:
+ if (event.modifiers == Qt.ControlModifier) {
+ event.accepted = true
+ addressBar.selectAll()
+ addressBar.forceActiveFocus()
+ }
+ break;
+ }
+ }
+} // dialog
diff --git a/interface/resources/qml/+webengine/InfoView.qml b/interface/resources/qml/+webengine/InfoView.qml
new file mode 100644
index 0000000000..eb190c3c45
--- /dev/null
+++ b/interface/resources/qml/+webengine/InfoView.qml
@@ -0,0 +1,50 @@
+//
+// InfoView.qml
+//
+// Created by Bradley Austin Davis on 27 Apr 2015
+// 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
+//
+
+import QtQuick 2.5
+import Hifi 1.0 as Hifi
+
+import controlsUit 1.0
+import "qrc:////qml//windows" as Windows
+
+Windows.ScrollingWindow {
+ id: root
+ width: 800
+ height: 800
+ resizable: true
+
+ Hifi.InfoView {
+ id: infoView
+ width: pane.contentWidth
+ implicitHeight: pane.scrollHeight
+
+ WebView {
+ id: webview
+ objectName: "WebView"
+ anchors.fill: parent
+ url: infoView.url
+ }
+ }
+
+ Component.onCompleted: {
+ centerWindow(root);
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ centerWindow(root);
+ }
+ }
+
+ function centerWindow() {
+ desktop.centerOnVisible(root);
+ }
+
+}
diff --git a/interface/resources/qml/+webengine/QmlWebWindow.qml b/interface/resources/qml/+webengine/QmlWebWindow.qml
new file mode 100644
index 0000000000..2e3718f6f5
--- /dev/null
+++ b/interface/resources/qml/+webengine/QmlWebWindow.qml
@@ -0,0 +1,108 @@
+//
+// QmlWebWindow.qml
+//
+// Created by Bradley Austin Davis on 17 Dec 2015
+// 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
+//
+
+import QtQuick 2.5
+import QtWebEngine 1.1
+import QtWebChannel 1.0
+
+import "qrc:////qml//windows" as Windows
+import controlsUit 1.0 as Controls
+import stylesUit 1.0
+
+Windows.ScrollingWindow {
+ id: root
+ HifiConstants { id: hifi }
+ title: "WebWindow"
+ resizable: true
+ shown: false
+ // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
+ destroyOnCloseButton: false
+ property alias source: webview.url
+ property alias scriptUrl: webview.userScriptUrl
+
+ // This is for JS/QML communication, which is unused in a WebWindow,
+ // but not having this here results in spurious warnings about a
+ // missing signal
+ signal sendToScript(var message);
+
+ signal moved(vector2d position);
+ signal resized(size size);
+
+ function notifyMoved() {
+ moved(Qt.vector2d(x, y));
+ }
+
+ function notifyResized() {
+ resized(Qt.size(width, height));
+ }
+
+ onXChanged: notifyMoved();
+ onYChanged: notifyMoved();
+
+ onWidthChanged: notifyResized();
+ onHeightChanged: notifyResized();
+
+ onShownChanged: {
+ keyboardEnabled = HMD.active;
+ }
+
+ Item {
+ width: pane.contentWidth
+ implicitHeight: pane.scrollHeight
+
+ 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..78ffb51e67 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -1,16 +1,14 @@
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"
ScrollingWindow {
id: root
HifiConstants { id: hifi }
- HifiStyles.HifiConstants { id: hifistyles }
+ //HifiStyles.HifiConstants { id: hifistyles }
title: "Browser"
resizable: true
destroyOnHidden: true
@@ -32,7 +30,6 @@ ScrollingWindow {
}
function setProfile(profile) {
- webview.profile = profile;
}
function showPermissionsBar(){
@@ -44,7 +41,6 @@ ScrollingWindow {
}
function allowPermissions(){
- webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
hidePermissionsBar();
}
@@ -68,7 +64,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 +73,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 +82,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 +198,10 @@ ScrollingWindow {
}
}
- WebView {
+ ProxyWebView {
id: webview
+ anchors.centerIn: parent
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 ]
-
- 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/InfoView.qml b/interface/resources/qml/InfoView.qml
index 8c5900b4c3..5c2c7fcff9 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 {
+ ProxyWebView {
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..a40168039e 100644
--- a/interface/resources/qml/QmlWebWindow.qml
+++ b/interface/resources/qml/QmlWebWindow.qml
@@ -9,8 +9,6 @@
//
import QtQuick 2.5
-import QtWebEngine 1.1
-import QtWebChannel 1.0
import "windows" as Windows
import controlsUit 1.0 as Controls
@@ -60,49 +58,9 @@ Windows.ScrollingWindow {
Controls.WebView {
id: webview
url: "about:blank"
+ property string userScriptUrl: ""
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/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/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..adcc472831
--- /dev/null
+++ b/interface/resources/qml/controlsUit/ProxyWebView.qml
@@ -0,0 +1,30 @@
+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 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/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..bcc2a2821c 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;
@@ -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/BlocksWebView.qml b/interface/resources/qml/hifi/tablet/BlocksWebView.qml
index 1e9eb3beb4..03fce0a112 100644
--- a/interface/resources/qml/hifi/tablet/BlocksWebView.qml
+++ b/interface/resources/qml/hifi/tablet/BlocksWebView.qml
@@ -1,6 +1,4 @@
import QtQuick 2.0
-import QtWebEngine 1.2
-
import "../../controls" as Controls
Controls.TabletWebView {
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/Application.cpp b/interface/src/Application.cpp
index daf2dd6363..e16a54ec74 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -235,6 +235,7 @@
#include "commerce/Ledger.h"
#include "commerce/Wallet.h"
#include "commerce/QmlCommerce.h"
+#include "commerce/QmlMarketplace.h"
#include "ResourceRequestObserver.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
@@ -761,6 +762,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) {
@@ -1138,10 +1144,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();
@@ -3005,7 +3009,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());
};
@@ -6309,6 +6329,8 @@ void Application::update(float deltaTime) {
_physicsEngine->processTransaction(transaction);
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
+ _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
+
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
});
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/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/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 92d9270d20..6fef47da8e 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;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 0d27988543..c53eae65d4 100755
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1732,6 +1732,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();
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index 26d69841d0..253cc891ee 100755
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -41,7 +41,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;
}
@@ -108,7 +108,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());
@@ -127,7 +127,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/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/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp
index 24ba4435e2..d476357bab 100644
--- a/interface/src/raypick/RayPick.cpp
+++ b/interface/src/raypick/RayPick.cpp
@@ -56,7 +56,8 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
}
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 {
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..63758d0633 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -41,6 +41,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.0
– 1.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 +59,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 +76,7 @@ public:
bool noiseReductionEnabled() const;
float getInputVolume() const;
float getInputLevel() const;
+ bool isClipping() const;
QString getContext() const;
void showMicMeter(bool show);
@@ -217,6 +220,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 +248,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 +258,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/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/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 f017fe2348..96e4e67261 100644
--- a/libraries/animation/src/AnimPoleVectorConstraint.cpp
+++ b/libraries/animation/src/AnimPoleVectorConstraint.cpp
@@ -95,7 +95,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/Rig.cpp b/libraries/animation/src/Rig.cpp
index 7842ec0804..d09de36a14 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) {
@@ -494,10 +493,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;
@@ -1329,7 +1326,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
@@ -1338,7 +1335,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/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/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 07c1ca9a32..daef0e411a 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -412,6 +412,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());
@@ -1733,15 +1736,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);
}
}
@@ -1972,12 +1977,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/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/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp
index bcbe5308c7..3e188afbdf 100644
--- a/libraries/avatars/src/ClientTraitsHandler.cpp
+++ b/libraries/avatars/src/ClientTraitsHandler.cpp
@@ -107,11 +107,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
if (initialSend || *simpleIt == Updated) {
if (traitType == AvatarTraits::SkeletonModelURL) {
- bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
-
// keep track of our skeleton version in case we get an override back
_currentSkeletonVersion = _currentTraitVersion;
}
+ bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
}
++simpleIt;
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index c71b296a74..2b3a915235 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
@@ -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..725416e2cc 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.h
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.h
@@ -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);
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 9515ef94b5..ae9fdf572a 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -955,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; });
@@ -1260,7 +1243,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
return false;
}
- if (_lastTextures != entity->getTextures()) {
+ if (_textures != entity->getTextures()) {
return true;
}
@@ -1414,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();
@@ -1490,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) {
@@ -1559,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/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/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/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/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 41e4f43a5d..c5d72d2d7b 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -88,13 +88,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 +180,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())) {
@@ -3506,3 +3511,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..27b207b6f3 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -561,6 +561,7 @@ public:
virtual void addGrab(GrabPointer grab) override;
virtual void removeGrab(GrabPointer grab) override;
+ virtual void disableGrab(GrabPointer grab) override;
signals:
void requestRenderUpdate();
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 7cafaece7a..c1488a5893 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -1414,8 +1414,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
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/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/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 0ca1c6c9e6..41b660f722 100644
--- a/libraries/fbx/src/FSTReader.cpp
+++ b/libraries/fbx/src/FSTReader.cpp
@@ -84,7 +84,7 @@ 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);
@@ -92,7 +92,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) {
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(" = ");
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 f7493ad88b..33be3a4e4a
--- a/libraries/fbx/src/GLTFSerializer.cpp
+++ b/libraries/fbx/src/GLTFSerializer.cpp
@@ -352,9 +352,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);
@@ -719,7 +724,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 +835,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,
@@ -926,7 +946,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 +958,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 +999,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) {
@@ -1006,11 +1035,16 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
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.contains("data:image/jpeg;base64,") || url.contains("data:image/png;base64,")) {
+ fbxtex.content = requestEmbeddedData(url);
+ }
}
return fbxtex;
}
@@ -1189,8 +1223,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();
@@ -1303,8 +1335,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..57ea126a7b
--- a/libraries/fbx/src/GLTFSerializer.h
+++ b/libraries/fbx/src/GLTFSerializer.h
@@ -772,6 +772,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..1cf331cd1a 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
@@ -426,6 +426,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..671d4e11d7 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h
@@ -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/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index e02f12819e..a8b5ec85e8 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;
}
}
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..90ce8c853a 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,24 @@ 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 {
glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0,
b._subresource);
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