diff --git a/CMakeLists.txt b/CMakeLists.txt index 6956fd22c3..4d616e1f3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ 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) endif () 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/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/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/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/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/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/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/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/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/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/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..351c43286c --- /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, + false, + 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..24ef528673 --- /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 QtGraphicalEffects 1.0 +import QtWebEngine 1.5 +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 ? hifi.colors.blueHighlight : hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + MouseArea { + anchors.fill: parent + + onClicked: { + if (model.link) { + 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: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + textFormat: Text.RichText + wrapMode: Text.Wrap + onLinkActivated: { + 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/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/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/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..b198edc245 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 @@ -3323,21 +3323,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 +3349,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 +3375,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 } @@ -5300,6 +5289,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/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..07a9e570bd --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -0,0 +1,135 @@ +// +// 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; + QUrlQuery request; + send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); +} + +void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) { + QString endpoint = QString("items/") + marketplaceItemId + "/like"; + QUrlQuery request; + send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request); +} + +void QmlMarketplace::getMarketplaceCategories() { + QString endpoint = "categories"; + QUrlQuery request; + send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); +} + + +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, + authType, + method, + callbackParams, + QByteArray(), + NULL, + QVariantMap(), + request); + +} + +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..f954198371 --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.h @@ -0,0 +1,68 @@ +// +// 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); + QJsonObject apiResponse(const QString& label, QNetworkReply* reply); + QJsonObject failResponse(const QString& label, QNetworkReply* reply); +}; + +#endif // hifi_QmlMarketplace_h diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b6e4df0d40..b390ab7119 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -133,3 +133,11 @@ bool PlatformInfoScriptingInterface::hasRiftControllers() { bool PlatformInfoScriptingInterface::hasViveControllers() { return qApp->hasViveControllers(); } + +bool PlatformInfoScriptingInterface::has3DHTML() { +#if defined(Q_OS_ANDROID) + return false; +#else + return true; +#endif +} diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 3ed57965c9..aece09b008 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -65,6 +65,12 @@ public slots: * @function Window.hasRift * @returns {boolean} true if running on Windows, otherwise false.*/ bool hasViveControllers(); + + /**jsdoc + * Returns true if device supports 3d HTML + * @function Window.hasRift + * @returns {boolean} true if device supports 3d HTML, otherwise false.*/ + bool has3DHTML(); }; #endif // hifi_PlatformInfoScriptingInterface_h diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 789a2a2bdf..799d7ea182 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -59,7 +59,7 @@ void AddressBarDialog::loadHome() { auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); if (homeLocation == "") { - homeLocation = DEFAULT_HIFI_ADDRESS; + homeLocation = DEFAULT_HOME_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp index 07e1c17f77..28b8bb4a9f 100644 --- a/libraries/animation/src/AnimBlendLinearMove.cpp +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -151,10 +151,10 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn if (_phase < 0.0f) { _phase = 0.0f; } - + // detect loop trigger events if (_phase >= 1.0f) { - triggersOut.setTrigger(_id + "Loop"); + triggersOut.setTrigger(_id + "OnLoop"); _phase = glm::fract(_phase); } diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0f921984b1..eb9ebd33dd 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -209,15 +209,14 @@ public: void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); } void unset(const QString& key) { _map.erase(key); } - void setTrigger(const QString& key) { _triggers.insert(key); } - void clearTriggers() { _triggers.clear(); } + void setTrigger(const QString& key) { _map[key] = AnimVariant(true); } void setRigToGeometryTransform(const glm::mat4& rigToGeometry) { _rigToGeometryMat = rigToGeometry; _rigToGeometryRot = glmExtractRotation(rigToGeometry); } - void clearMap() { _map.clear(); } + void clearMap() { _map.clear(); _triggers.clear(); } bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); } const AnimVariant& get(const QString& key) const { @@ -238,7 +237,7 @@ public: // For stat debugging. std::map toDebugMap() const; -#ifdef NDEBUG +#ifndef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; for (auto& pair : _map) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index bc4dca54f2..7842ec0804 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1207,9 +1207,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } _lastAnimVars = _animVars; - _animVars.clearTriggers(); _animVars = triggersOut; - _networkVars.clearTriggers(); _networkVars = networkTriggersOut; _lastContext = context; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 07c1ca9a32..b8626c813e 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()); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4e95774efb..c733cfa291 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -632,9 +632,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // include jointData if there is room for the most minimal section. i.e. no translations or rotations. IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) { - // Allow for faux joints + translation bit-vector: - const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) - + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; + // Minimum space required for another rotation joint - + // size of joint + following translation bit-vector + translation scale + faux joints: + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + + sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE; + auto startSection = destinationBuffer; // compute maxTranslationDimension before we send any joint data. @@ -724,6 +726,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; + // Note minSizeForJoint is conservative since there isn't a following bit-vector + scale. if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c71b296a74..44025fc8f4 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(); } 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/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(sampler, node, keys::sampler, [](const json& node) { return readSampler(node); }); TexturePointer result; @@ -325,8 +358,6 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { auto& texture = *result; readOptional(texture._source, node, keys::source); - std::string ktxFile; - readOptional(ktxFile, node, keys::ktxFile); if (!ktxFile.empty()) { if (QFileInfo(ktxFile.c_str()).isRelative()) { ktxFile = basedir + "/" + ktxFile; @@ -359,6 +390,7 @@ ShaderPointer Deserializer::readShader(const json& node) { // FIXME support procedural shaders Shader::Type type = node[keys::type]; + std::string name = node[keys::name]; uint32_t id = node[keys::id]; ShaderPointer result; switch (type) { @@ -374,6 +406,9 @@ ShaderPointer Deserializer::readShader(const json& node) { default: throw std::runtime_error("not implemented"); } + if (result->getSource().name != name) { + throw std::runtime_error("Bad name match"); + } return result; } @@ -747,12 +782,6 @@ StereoState readStereoState(const json& node) { FramePointer Deserializer::deserializeFrame() { { std::string filename{ basename + ".json" }; - if (0 == basename.find("assets:")) { - auto lastSlash = basename.rfind('/'); - basedir = basename.substr(0, lastSlash); - } else { - basedir = QFileInfo(basename.c_str()).absolutePath().toStdString(); - } storage::FileStorage mappedFile(filename.c_str()); frameNode = json::parse(std::string((const char*)mappedFile.data(), mappedFile.size())); } @@ -808,7 +837,7 @@ FramePointer Deserializer::deserializeFrame() { swapchains = readArray(frameNode, keys::swapchains, [this](const json& node) { return readSwapchain(node); }); - queries = readArray(frameNode, keys::queries, [this](const json& node) { return readQuery(node); }); + queries = readArray(frameNode, keys::queries, [](const json& node) { return readQuery(node); }); frame.framebuffer = framebuffers[frameNode[keys::framebuffer].get()]; frame.view = readMat4(frameNode[keys::view]); frame.pose = readMat4(frameNode[keys::pose]); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index f74b337ee7..cf77c1cad5 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -208,7 +208,7 @@ void AccountManager::setSessionID(const QUuid& sessionID) { } } -QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) { +QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query) { QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); @@ -227,6 +227,7 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth:: } else { requestURL.setPath("/" + path); } + requestURL.setQuery(query); if (authType != AccountManagerAuth::None ) { if (hasValidAccessToken()) { @@ -263,13 +264,14 @@ void AccountManager::sendRequest(const QString& path, Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), Q_ARG(QHttpMultiPart*, dataMultiPart), - Q_ARG(QVariantMap, propertyMap)); + Q_ARG(QVariantMap, propertyMap), + Q_ARG(QUrlQuery, query)); return; } QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = createRequest(path, authType); + QNetworkRequest networkRequest = createRequest(path, authType, query); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 477488031d..2ccebdd73c 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -61,7 +61,7 @@ class AccountManager : public QObject, public Dependency { public: AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); - QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType); + QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query = QUrlQuery()); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index e6957728e8..9145b4a79e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -30,7 +30,8 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; +const QString DEFAULT_HOME_ADDRESS = "file:///~/serverless/tutorial.json"; const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 5318822cdc..450b71023c 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -24,6 +24,7 @@ extern const QString DEFAULT_HIFI_ADDRESS; extern const QString REDIRECT_HIFI_ADDRESS; +extern const QString DEFAULT_HOME_ADDRESS; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; const QString INDEX_PATH = "/"; diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index a79f0a0c2b..8ab502e951 100755 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -729,12 +729,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -744,14 +738,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - const unsigned char *start = dataBytes; for (int i = 0; i < length; i++) { dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]); @@ -764,12 +751,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -779,14 +760,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - int bit = 0; unsigned char current = 0; const unsigned char *start = dataBytes; @@ -797,7 +771,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto result[i] = (bool)(current & (1 << bit)); bit = (bit + 1) % BITS_IN_BYTE; } - return (dataBytes - start) + (int)sizeof(uint16_t); } diff --git a/libraries/oculusMobile/CMakeLists.txt b/libraries/oculusMobile/CMakeLists.txt new file mode 100644 index 0000000000..213721722c --- /dev/null +++ b/libraries/oculusMobile/CMakeLists.txt @@ -0,0 +1,11 @@ +if (ANDROID) + set(TARGET_NAME oculusMobile) + # don't use the setup_hifi_library macro, we don't want ANY qt dependencies + file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc") + add_library(${TARGET_NAME} SHARED ${LIB_SRCS}) + target_glm() + target_egl() + target_glad() + target_oculus_mobile() + target_link_libraries(${TARGET_NAME} android log) +endif() diff --git a/libraries/oculusMobile/src/ovr/Forward.h b/libraries/oculusMobile/src/ovr/Forward.h new file mode 100644 index 0000000000..5881dde9a7 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Forward.h @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2018/11/23 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#include +#include + +namespace ovr { + using Mutex = std::mutex; + using Condition = std::condition_variable; + using Lock = std::unique_lock; + using Task = std::function; +} \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.cpp b/libraries/oculusMobile/src/ovr/Framebuffer.cpp new file mode 100644 index 0000000000..4c4fd2a983 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Framebuffer.cpp @@ -0,0 +1,93 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Framebuffer.h" + +#include +#include +#include + +#include +#include + +using namespace ovr; + +void Framebuffer::updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix ) const { + auto& layerTexture = layer.Textures[eye]; + layerTexture.ColorSwapChain = _swapChain; + layerTexture.SwapChainIndex = _index; + if (projectionMatrix) { + layerTexture.TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection( projectionMatrix ); + } + layerTexture.TextureRect = { 0, 0, 1, 1 }; +} + +void Framebuffer::create(const glm::uvec2& size) { + _size = size; + _index = 0; + _validTexture = false; + + // Depth renderbuffer + glGenRenderbuffers(1, &_depth); + glBindRenderbuffer(GL_RENDERBUFFER, _depth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _size.x, _size.y); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + // Framebuffer + glGenFramebuffers(1, &_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + _swapChain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D, GL_RGBA8, _size.x, _size.y, 1, 3); + _length = vrapi_GetTextureSwapChainLength(_swapChain); + if (!_length) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Unable to count swap chain textures"); + return; + } + + for (int i = 0; i < _length; ++i) { + GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, i); + glBindTexture(GL_TEXTURE_2D, chainTexId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + glBindTexture(GL_TEXTURE_2D, 0); +} + +void Framebuffer::destroy() { + if (0 != _fbo) { + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } + if (0 != _depth) { + glDeleteRenderbuffers(1, &_depth); + _depth = 0; + } + if (_swapChain != nullptr) { + vrapi_DestroyTextureSwapChain(_swapChain); + _swapChain = nullptr; + } + _index = -1; + _length = -1; +} + +void Framebuffer::advance() { + _index = (_index + 1) % _length; + _validTexture = false; +} + +void Framebuffer::bind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + if (!_validTexture) { + GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, _index); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, chainTexId, 0); + _validTexture = true; + } +} diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.h b/libraries/oculusMobile/src/ovr/Framebuffer.h new file mode 100644 index 0000000000..5127574462 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Framebuffer.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include + +#include + +namespace ovr { + +struct Framebuffer { +public: + void updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix = nullptr) const; + void create(const glm::uvec2& size); + void advance(); + void destroy(); + void bind(); + + uint32_t _depth { 0 }; + uint32_t _fbo{ 0 }; + int _length{ -1 }; + int _index{ -1 }; + bool _validTexture{ false }; + glm::uvec2 _size; + ovrTextureSwapChain* _swapChain{ nullptr }; +}; + +} // namespace ovr \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp new file mode 100644 index 0000000000..449ba67084 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/GLContext.cpp @@ -0,0 +1,182 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "GLContext.h" + +#include +#include +#include + +#include + +#if !defined(EGL_OPENGL_ES3_BIT_KHR) +#define EGL_OPENGL_ES3_BIT_KHR 0x0040 +#endif + +using namespace ovr; + +static void* getGlProcessAddress(const char *namez) { + auto result = eglGetProcAddress(namez); + return (void*)result; +} + + +void GLContext::initModule() { + static std::once_flag once; + std::call_once(once, [&]{ + gladLoadGLES2Loader(getGlProcessAddress); + }); +} + +void APIENTRY debugMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) { + if (type == GL_DEBUG_TYPE_PERFORMANCE_KHR) { + return; + } + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: + case GL_DEBUG_SEVERITY_MEDIUM: + break; + default: + return; + } + + __android_log_write(ANDROID_LOG_WARN, "QQQ_GL", message); +} + +GLContext::~GLContext() { + destroy(); +} + +EGLConfig GLContext::findConfig(EGLDisplay display) { + // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample + // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in + // settings, and that is completely wasted for our warp target. + std::vector configs; + { + const int MAX_CONFIGS = 1024; + EGLConfig configsBuffer[MAX_CONFIGS]; + EGLint numConfigs = 0; + if (eglGetConfigs(display, configsBuffer, MAX_CONFIGS, &numConfigs) == EGL_FALSE) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed to fetch configs"); + return 0; + } + configs.resize(numConfigs); + memcpy(configs.data(), configsBuffer, sizeof(EGLConfig) * numConfigs); + } + + std::vector> configAttribs{ + { EGL_RED_SIZE, 8 }, { EGL_GREEN_SIZE, 8 }, { EGL_BLUE_SIZE, 8 }, { EGL_ALPHA_SIZE, 8 }, + { EGL_DEPTH_SIZE, 0 }, { EGL_STENCIL_SIZE, 0 }, { EGL_SAMPLES, 0 }, + }; + + auto matchAttrib = [&](EGLConfig config, const std::pair& attribAndValue) { + EGLint value = 0; + eglGetConfigAttrib(display, config, attribAndValue.first, &value); + return (attribAndValue.second == value); + }; + + auto matchAttribFlags = [&](EGLConfig config, const std::pair& attribAndValue) { + EGLint value = 0; + eglGetConfigAttrib(display, config, attribAndValue.first, &value); + return (value & attribAndValue.second) == attribAndValue.second; + }; + + auto matchConfig = [&](EGLConfig config) { + if (!matchAttribFlags(config, { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR})) { + return false; + } + // The pbuffer config also needs to be compatible with normal window rendering + // so it can share textures with the window context. + if (!matchAttribFlags(config, { EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT})) { + return false; + } + + for (const auto& attrib : configAttribs) { + if (!matchAttrib(config, attrib)) { + return false; + } + } + + return true; + }; + + + for (const auto& config : configs) { + if (matchConfig(config)) { + return config; + } + } + + return 0; +} + +bool GLContext::makeCurrent() { + return eglMakeCurrent(display, surface, surface, context) != EGL_FALSE; +} + +void GLContext::doneCurrent() { + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +bool GLContext::create(EGLDisplay display, EGLContext shareContext) { + this->display = display; + + auto config = findConfig(display); + + if (config == 0) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglChooseConfig"); + return false; + } + + EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + + context = eglCreateContext(display, config, shareContext, contextAttribs); + if (context == EGL_NO_CONTEXT) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreateContext"); + return false; + } + + const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE }; + surface = eglCreatePbufferSurface(display, config, surfaceAttribs); + if (surface == EGL_NO_SURFACE) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreatePbufferSurface"); + return false; + } + + if (!makeCurrent()) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglMakeCurrent"); + return false; + } + + ovr::GLContext::initModule(); + +#ifndef NDEBUG + glDebugMessageCallback(debugMessageCallback, this); + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); +#endif + return true; +} + +void GLContext::destroy() { + if (context != EGL_NO_CONTEXT) { + eglDestroyContext(display, context); + context = EGL_NO_CONTEXT; + } + + if (surface != EGL_NO_SURFACE) { + eglDestroySurface(display, surface); + surface = EGL_NO_SURFACE; + } +} diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h new file mode 100644 index 0000000000..04f96e8d47 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/GLContext.h @@ -0,0 +1,37 @@ +// +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +#include +#include + +namespace ovr { + +struct GLContext { + using Pointer = std::shared_ptr; + EGLSurface surface{ EGL_NO_SURFACE }; + EGLContext context{ EGL_NO_CONTEXT }; + EGLDisplay display{ EGL_NO_DISPLAY }; + + ~GLContext(); + static EGLConfig findConfig(EGLDisplay display); + bool makeCurrent(); + void doneCurrent(); + bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT); + void destroy(); + operator bool() const { return context != EGL_NO_CONTEXT; } + static void initModule(); +}; + +} + + +#define CHECK_GL_ERROR() if(false) {} \ No newline at end of file diff --git a/libraries/oculusMobile/src/ovr/Helpers.cpp b/libraries/oculusMobile/src/ovr/Helpers.cpp new file mode 100644 index 0000000000..a48d37311e --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Helpers.cpp @@ -0,0 +1,38 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Helpers.h" + +#include +#include +#include +#include + +using namespace ovr; + +void Fov::extend(const Fov& other) { + for (size_t i = 0; i < 4; ++i) { + leftRightUpDown[i] = std::max(leftRightUpDown[i], other.leftRightUpDown[i]); + } +} + +void Fov::extract(const ovrMatrix4f& mat) { + auto& fs = leftRightUpDown; + ovrMatrix4f_ExtractFov( &mat, fs, fs + 1, fs + 2, fs + 3); +} + +glm::mat4 Fov::withZ(float nearZ, float farZ) const { + const auto& fs = leftRightUpDown; + return ovr::toGlm(ovrMatrix4f_CreateProjectionAsymmetricFov(fs[0], fs[1], fs[2], fs[3], nearZ, farZ)); +} + +glm::mat4 Fov::withZ(const glm::mat4& other) const { + // FIXME + return withZ(0.01f, 1000.0f); +} + diff --git a/libraries/oculusMobile/src/ovr/Helpers.h b/libraries/oculusMobile/src/ovr/Helpers.h new file mode 100644 index 0000000000..2bd0b7f603 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/Helpers.h @@ -0,0 +1,94 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include +#include +#include +#include + +namespace ovr { + +struct Fov { + float leftRightUpDown[4]; + Fov() {} + Fov(const ovrMatrix4f& mat) { extract(mat); } + void extract(const ovrMatrix4f& mat); + void extend(const Fov& other); + glm::mat4 withZ(const glm::mat4& other) const; + glm::mat4 withZ(float nearZ, float farZ) const; +}; + +// Convenience method for looping over each eye with a lambda +static inline void for_each_eye(const std::function& f) { + f(VRAPI_EYE_LEFT); + f(VRAPI_EYE_RIGHT); +} + +static inline void for_each_hand(const std::function& f) { + f(VRAPI_HAND_LEFT); + f(VRAPI_HAND_RIGHT); +} + +static inline glm::mat4 toGlm(const ovrMatrix4f& om) { + return glm::transpose(glm::make_mat4(&om.M[0][0])); +} + +static inline glm::vec3 toGlm(const ovrVector3f& ov) { + return glm::make_vec3(&ov.x); +} + +static inline glm::vec2 toGlm(const ovrVector2f& ov) { + return glm::make_vec2(&ov.x); +} + +static inline glm::quat toGlm(const ovrQuatf& oq) { + return glm::make_quat(&oq.x); +} + +static inline glm::mat4 toGlm(const ovrPosef& op) { + glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); + glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); + return translation * orientation; +} + +static inline ovrMatrix4f fromGlm(const glm::mat4& m) { + ovrMatrix4f result; + glm::mat4 transposed(glm::transpose(m)); + memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); + return result; +} + +static inline ovrVector3f fromGlm(const glm::vec3& v) { + return { v.x, v.y, v.z }; +} + +static inline ovrVector2f fromGlm(const glm::vec2& v) { + return { v.x, v.y }; +} + +static inline ovrQuatf fromGlm(const glm::quat& q) { + return { q.x, q.y, q.z, q.w }; +} + +static inline ovrPosef poseFromGlm(const glm::mat4& m) { + glm::vec3 translation = glm::vec3(m[3]) / m[3].w; + glm::quat orientation = glm::quat_cast(m); + ovrPosef result; + result.Orientation = fromGlm(orientation); + result.Position = fromGlm(translation); + return result; +} + +} + + + + + diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.cpp b/libraries/oculusMobile/src/ovr/TaskQueue.cpp new file mode 100644 index 0000000000..5506a35acd --- /dev/null +++ b/libraries/oculusMobile/src/ovr/TaskQueue.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis on 2018/11/23 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TaskQueue.h" + +using namespace ovr; + +void TaskQueue::submitTaskBlocking(Lock& lock, const Task& newTask) { + _task = newTask; + _taskPending = true; + _taskCondition.wait(lock, [=]() -> bool { return !_taskPending; }); +} + +void TaskQueue::submitTaskBlocking(const Task& task) { + Lock lock(_mutex); + submitTaskBlocking(lock, task); +} + +void TaskQueue::pollTask() { + Lock lock(_mutex); + if (_taskPending) { + _task(); + _taskPending = false; + _taskCondition.notify_one(); + } +} + +void TaskQueue::withLock(const Task& task) { + Lock lock(_mutex); + task(); +} + +void TaskQueue::withLockConditional(const LockTask& task) { + Lock lock(_mutex); + task(lock); +} diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.h b/libraries/oculusMobile/src/ovr/TaskQueue.h new file mode 100644 index 0000000000..4ec055ece9 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/TaskQueue.h @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include + +namespace ovr { + +using Mutex = std::mutex; +using Condition = std::condition_variable; +using Lock = std::unique_lock; +using Task = std::function; +using LockTask = std::function; + +class TaskQueue { +public: + // Execute a task on another thread + void submitTaskBlocking(const Task& task); + void submitTaskBlocking(Lock& lock, const Task& task); + void pollTask(); + + void withLock(const Task& task); + void withLockConditional(const LockTask& task); +private: + Mutex _mutex; + Task _task; + bool _taskPending{ false }; + Condition _taskCondition; +}; + +} + + + + + diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp new file mode 100644 index 0000000000..de2b4e1ff6 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -0,0 +1,337 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "VrHandler.h" + +#include +#include + +#include + +#include +#include +#include +//#include + +#include "GLContext.h" +#include "Helpers.h" +#include "Framebuffer.h" + + + +using namespace ovr; + +static thread_local bool isRenderThread { false }; + +struct VrSurface : public TaskQueue { + using HandlerTask = VrHandler::HandlerTask; + + JavaVM* vm{nullptr}; + jobject oculusActivity{ nullptr }; + ANativeWindow* nativeWindow{ nullptr }; + + VrHandler* handler{nullptr}; + ovrMobile* session{nullptr}; + bool resumed { false }; + GLContext vrglContext; + Framebuffer eyeFbos[2]; + uint32_t readFbo{0}; + std::atomic presentIndex{1}; + double displayTime{0}; + + static constexpr float EYE_BUFFER_SCALE = 1.0f; + + void onCreate(JNIEnv* env, jobject activity) { + env->GetJavaVM(&vm); + oculusActivity = env->NewGlobalRef(activity); + } + + void setResumed(bool newResumed) { + this->resumed = newResumed; + submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); }); + } + + void setNativeWindow(ANativeWindow* newNativeWindow) { + auto oldNativeWindow = nativeWindow; + nativeWindow = newNativeWindow; + if (oldNativeWindow) { + ANativeWindow_release(oldNativeWindow); + } + submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); }); + } + + void init() { + if (!handler) { + return; + } + + EGLContext currentContext = eglGetCurrentContext(); + EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + vrglContext.create(currentDisplay, currentContext); + vrglContext.makeCurrent(); + + glm::uvec2 eyeTargetSize; + withEnv([&](JNIEnv* env){ + ovrJava java{ vm, env, oculusActivity }; + eyeTargetSize = glm::uvec2 { + vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH) * EYE_BUFFER_SCALE, + vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT) * EYE_BUFFER_SCALE, + }; + }); + __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "QQQ Eye Size %d, %d", eyeTargetSize.x, eyeTargetSize.y); + ovr::for_each_eye([&](ovrEye eye) { + eyeFbos[eye].create(eyeTargetSize); + }); + glGenFramebuffers(1, &readFbo); + vrglContext.doneCurrent(); + } + + void shutdown() { + } + + void setHandler(VrHandler *newHandler) { + withLock([&] { + isRenderThread = newHandler != nullptr; + if (handler != newHandler) { + shutdown(); + handler = newHandler; + init(); + if (handler) { + updateVrMode(); + } + } + }); + } + + void submitRenderThreadTask(const HandlerTask &task) { + withLockConditional([&](Lock &lock) { + if (handler != nullptr) { + submitTaskBlocking(lock, [&] { + task(handler); + }); + } + }); + } + + void withEnv(const std::function& f) { + JNIEnv* env = nullptr; + bool attached = false; + vm->GetEnv((void**)&env, JNI_VERSION_1_6); + if (!env) { + attached = true; + vm->AttachCurrentThread(&env, nullptr); + } + f(env); + if (attached) { + vm->DetachCurrentThread(); + } + } + + void updateVrMode() { + // For VR mode to be valid, the activity must be between an onResume and + // an onPause call and must additionally have a valid native window handle + bool vrReady = resumed && nullptr != nativeWindow; + // If we're IN VR mode, we'll have a non-null ovrMobile pointer in session + bool vrRunning = session != nullptr; + if (vrReady != vrRunning) { + if (vrRunning) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode"); + vrapi_LeaveVrMode(session); + session = nullptr; + oculusActivity = nullptr; + } else { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_EnterVrMode"); + withEnv([&](JNIEnv* env){ + ovrJava java{ vm, env, oculusActivity }; + ovrModeParms modeParms = vrapi_DefaultModeParms(&java); + modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW; + modeParms.Display = (unsigned long long) vrglContext.display; + modeParms.ShareContext = (unsigned long long) vrglContext.context; + modeParms.WindowSurface = (unsigned long long) nativeWindow; + session = vrapi_EnterVrMode(&modeParms); + ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL); + vrapi_SetTrackingTransform( session, trackingTransform ); + vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self()); + vrapi_SetClockLevels(session, 2, 4); + vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC); + vrapi_SetDisplayRefreshRate(session, 72); + }); + } + } + } + + void presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) { + ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2(); + layer.HeadPose = tracking.HeadPose; + if (sourceTexture) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, sourceTexture, 0); + GLenum framebufferStatus = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE != framebufferStatus) { + __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "incomplete framebuffer"); + } + } + GLenum invalidateAttachment = GL_COLOR_ATTACHMENT0; + + ovr::for_each_eye([&](ovrEye eye) { + const auto &eyeTracking = tracking.Eye[eye]; + auto &eyeFbo = eyeFbos[eye]; + const auto &destSize = eyeFbo._size; + eyeFbo.bind(); + glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &invalidateAttachment); + if (sourceTexture) { + auto sourceWidth = sourceSize.x / 2; + auto sourceX = (eye == VRAPI_EYE_LEFT) ? 0 : sourceWidth; + glBlitFramebuffer( + sourceX, 0, sourceX + sourceWidth, sourceSize.y, + 0, 0, destSize.x, destSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + eyeFbo.updateLayer(eye, layer, &eyeTracking.ProjectionMatrix); + eyeFbo.advance(); + }); + if (sourceTexture) { + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &invalidateAttachment); + glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + } + glFlush(); + + ovrLayerHeader2 *layerHeader = &layer.Header; + ovrSubmitFrameDescription2 frameDesc = {}; + frameDesc.SwapInterval = 2; + frameDesc.FrameIndex = presentIndex; + frameDesc.DisplayTime = displayTime; + frameDesc.LayerCount = 1; + frameDesc.Layers = &layerHeader; + vrapi_SubmitFrame2(session, &frameDesc); + ++presentIndex; + } + + ovrTracking2 beginFrame() { + displayTime = vrapi_GetPredictedDisplayTime(session, presentIndex); + return vrapi_GetPredictedTracking2(session, displayTime); + } +}; + +static VrSurface SURFACE; + +bool VrHandler::vrActive() const { + return SURFACE.session != nullptr; +} + +void VrHandler::setHandler(VrHandler* handler) { + SURFACE.setHandler(handler); +} + +void VrHandler::pollTask() { + SURFACE.pollTask(); +} + +void VrHandler::makeCurrent() { + if (!SURFACE.vrglContext.makeCurrent()) { + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to make GL current"); + } +} + +void VrHandler::doneCurrent() { + SURFACE.vrglContext.doneCurrent(); +} + +uint32_t VrHandler::currentPresentIndex() const { + return SURFACE.presentIndex; +} + +ovrTracking2 VrHandler::beginFrame() { + return SURFACE.beginFrame(); +} + +void VrHandler::presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) const { + SURFACE.presentFrame(sourceTexture, sourceSize, tracking); +} + +bool VrHandler::withOvrJava(const OvrJavaTask& task) { + SURFACE.withEnv([&](JNIEnv* env){ + ovrJava java{ SURFACE.vm, env, SURFACE.oculusActivity }; + task(&java); + }); + return true; +} + +bool VrHandler::withOvrMobile(const OvrMobileTask &task) { + auto sessionTask = [&]()->bool{ + if (!SURFACE.session) { + return false; + } + task(SURFACE.session); + return true; + }; + + if (isRenderThread) { + return sessionTask(); + } + + bool result = false; + SURFACE.withLock([&]{ + result = sessionTask(); + }); + return result; +} + + +void VrHandler::initVr(const char* appId) { + withOvrJava([&](const ovrJava* java){ + ovrInitParms initParms = vrapi_DefaultInitParms(java); + initParms.GraphicsAPI = VRAPI_GRAPHICS_API_OPENGL_ES_3; + if (vrapi_Initialize(&initParms) != VRAPI_INITIALIZE_SUCCESS) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed vrapi init"); + } + }); + + // if (appId) { + // auto platformInitResult = ovr_PlatformInitializeAndroid(appId, activity.object(), env); + // if (ovrPlatformInitialize_Success != platformInitResult) { + // __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed ovr platform init"); + // } + // } +} + +void VrHandler::shutdownVr() { + vrapi_Shutdown(); +} + +extern "C" { + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) { + __android_log_write(ANDROID_LOG_WARN, "QQQ", "oculusMobile::JNI_OnLoad"); + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnCreate(JNIEnv* env, jobject obj) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.onCreate(env, obj); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnDestroy(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnResume(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setResumed(true); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnPause(JNIEnv*, jclass) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setResumed(false); +} + +JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnSurfaceChanged(JNIEnv* env, jclass, jobject surface) { + __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); + SURFACE.setNativeWindow(surface ? ANativeWindow_fromSurface( env, surface ) : nullptr); +} + +} // extern "C" diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h new file mode 100644 index 0000000000..3c36ee8626 --- /dev/null +++ b/libraries/oculusMobile/src/ovr/VrHandler.h @@ -0,0 +1,47 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include +#include + +#include "TaskQueue.h" + +typedef struct ovrMobile ovrMobile; +namespace ovr { + +class VrHandler { +public: + using HandlerTask = std::function; + using OvrMobileTask = std::function; + using OvrJavaTask = std::function; + static void setHandler(VrHandler* handler); + static bool withOvrMobile(const OvrMobileTask& task); + +protected: + static void initVr(const char* appId = nullptr); + static void shutdownVr(); + static bool withOvrJava(const OvrJavaTask& task); + + uint32_t currentPresentIndex() const; + ovrTracking2 beginFrame(); + void presentFrame(uint32_t textureId, const glm::uvec2& size, const ovrTracking2& tracking) const; + + bool vrActive() const; + void pollTask(); + void makeCurrent(); + void doneCurrent(); +}; + +} + + + + + diff --git a/libraries/oculusMobilePlugin/CMakeLists.txt b/libraries/oculusMobilePlugin/CMakeLists.txt new file mode 100644 index 0000000000..b404c51f02 --- /dev/null +++ b/libraries/oculusMobilePlugin/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Created by Bradley Austin Davis on 2018/11/15 +# Copyright 2013-2018 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html +# + +if (ANDROID) + set(TARGET_NAME oculusMobilePlugin) + setup_hifi_library(AndroidExtras Multimedia) + + # if we were passed an Oculus App ID for entitlement checks, send that along + if (DEFINED ENV{OCULUS_APP_ID}) + target_compile_definitions(${TARGET_NAME} -DOCULUS_APP_ID="$ENV{OCULUS_APP_ID}") + endif () + + link_hifi_libraries( + shared task gl shaders gpu controllers ui qml + plugins ui-plugins display-plugins input-plugins + audio-client networking render-utils + render graphics + oculusMobile + ${PLATFORM_GL_BACKEND} + ) + + include_hifi_library_headers(octree) + target_oculus_mobile() +endif() diff --git a/libraries/oculusMobilePlugin/src/Logging.cpp b/libraries/oculusMobilePlugin/src/Logging.cpp new file mode 100644 index 0000000000..3d8628b0cb --- /dev/null +++ b/libraries/oculusMobilePlugin/src/Logging.cpp @@ -0,0 +1,4 @@ +#include "Logging.h" + +Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") +Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus") diff --git a/libraries/oculusMobilePlugin/src/Logging.h b/libraries/oculusMobilePlugin/src/Logging.h new file mode 100644 index 0000000000..066712ef6a --- /dev/null +++ b/libraries/oculusMobilePlugin/src/Logging.h @@ -0,0 +1,13 @@ +// +// Created by Bradley Austin Davis on 2018/11/20 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(displayplugins) +Q_DECLARE_LOGGING_CATEGORY(oculusLog) diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp new file mode 100644 index 0000000000..8de563ee4c --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp @@ -0,0 +1,694 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OculusMobileControllerManager.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Logging.h" +#include + +const char* OculusMobileControllerManager::NAME = "Oculus"; +const quint64 LOST_TRACKING_DELAY = 3000000; + +namespace ovr { + + controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.Pose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(handPose.Pose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); + pose.valid = true; + return pose; + } + + controller::Pose toControllerPose(ovrHandedness hand, + const ovrRigidBodyPosef& handPose, + const ovrRigidBodyPosef& lastHandPose) { + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = + glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.Pose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(lastHandPose.Pose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = toGlm(lastHandPose.AngularVelocity); + pose.velocity = toGlm(lastHandPose.LinearVelocity); + pose.valid = true; + return pose; + } + +} + + +class OculusMobileInputDevice : public controller::InputDevice { + friend class OculusMobileControllerManager; +public: + using Pointer = std::shared_ptr; + static Pointer check(ovrMobile* session); + + OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps); + void updateHands(ovrMobile* session); + + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + void focusOutEvent() override; + bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; + +private: + void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, + ovrHandedness hand, const ovrRigidBodyPosef& handPose); + void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, + ovrHandedness hand, const ovrRigidBodyPosef& handPose); + void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, + const ovrRigidBodyPosef& headPose); + + // perform an action when the TouchDevice mutex is acquired. + using Locker = std::unique_lock; + + template + void withLock(F&& f) { Locker locker(_lock); f(); } + + mutable std::recursive_mutex _lock; + ovrTracking2 _headTracking; + struct HandData { + HandData() { + state.Header.ControllerType =ovrControllerType_TrackedRemote; + } + + float hapticDuration { 0.0f }; + float hapticStrength { 0.0f }; + bool valid{ false }; + bool lostTracking{ false }; + quint64 regainTrackingDeadline; + ovrRigidBodyPosef lastPose; + ovrInputTrackedRemoteCapabilities caps; + ovrInputStateTrackedRemote state; + ovrResult stateResult{ ovrError_NotInitialized }; + ovrTracking tracking; + ovrResult trackingResult{ ovrError_NotInitialized }; + bool setHapticFeedback(float strength, float duration) { +#if 0 + bool success = true; + bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + if (strength == 0.0f) { + hapticStrength = 0.0f; + hapticDuration = 0.0f; + } else { + hapticStrength = (duration > hapticDuration) ? strength : hapticStrength; + if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) { + success = false; + } + hapticDuration = std::max(duration, hapticDuration); + } + }); + return success && sessionSuccess; +#else + return true; +#endif + } + + void stopHapticPulse() { + ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f); + }); + } + + bool isValid() const { + return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess); + } + + void update(ovrMobile* session, double time = 0.0) { + const auto& deviceId = caps.Header.DeviceID; + stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header); + trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking); + } + }; + std::array _hands; +}; + +OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) { + Pointer result; + + std::vector devicesCaps; + { + uint32_t deviceIndex { 0 }; + ovrInputCapabilityHeader capsHeader; + while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) { + if (capsHeader.Type == ovrControllerType_TrackedRemote) { + ovrInputTrackedRemoteCapabilities caps; + caps.Header = capsHeader; + vrapi_GetInputDeviceCapabilities(session, &caps.Header); + devicesCaps.push_back(caps); + } + ++deviceIndex; + } + } + if (!devicesCaps.empty()) { + result.reset(new OculusMobileInputDevice(session, devicesCaps)); + } + return result; +} + +static OculusMobileInputDevice::Pointer oculusMobileControllers; + +bool OculusMobileControllerManager::isHandController() const { + return oculusMobileControllers.operator bool(); +} + +bool OculusMobileControllerManager::isSupported() const { + return true; +} + +bool OculusMobileControllerManager::activate() { + InputPlugin::activate(); + checkForConnectedDevices(); + return true; +} + +void OculusMobileControllerManager::checkForConnectedDevices() { + if (oculusMobileControllers) { + return; + } + + ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + oculusMobileControllers = OculusMobileInputDevice::check(session); + if (oculusMobileControllers) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(oculusMobileControllers); + } + }); +} + +void OculusMobileControllerManager::deactivate() { + InputPlugin::deactivate(); + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + if (oculusMobileControllers) { + userInputMapper->removeDevice(oculusMobileControllers->getDeviceID()); + oculusMobileControllers.reset(); + } +} + +void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + PerformanceTimer perfTimer("OculusMobileInputDevice::update"); + + checkForConnectedDevices(); + + if (!oculusMobileControllers) { + return; + } + + bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){ + oculusMobileControllers->updateHands(session); + }); + + if (updated) { + oculusMobileControllers->update(deltaTime, inputCalibrationData); + } +} + +void OculusMobileControllerManager::pluginFocusOutEvent() { + if (oculusMobileControllers) { + oculusMobileControllers->focusOutEvent(); + } +} + +QStringList OculusMobileControllerManager::getSubdeviceNames() { + QStringList devices; + if (oculusMobileControllers) { + devices << oculusMobileControllers->getName(); + } + return devices; +} + +using namespace controller; + +static const std::vector> BUTTON_MAP { { + { ovrButton_Up, DU }, + { ovrButton_Down, DD }, + { ovrButton_Left, DL }, + { ovrButton_Right, DR }, + { ovrButton_Enter, START }, + { ovrButton_Back, BACK }, + { ovrButton_X, X }, + { ovrButton_Y, Y }, + { ovrButton_A, A }, + { ovrButton_B, B }, + { ovrButton_LThumb, LS }, + { ovrButton_RThumb, RS }, + //{ ovrButton_LShoulder, LB }, + //{ ovrButton_RShoulder, RB }, +} }; + +static const std::vector> LEFT_TOUCH_MAP { { + { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH }, + { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_LThumb, LS_TOUCH }, + { ovrTouch_ThumbUp, LEFT_THUMB_UP }, + { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_IndexPointing, LEFT_INDEX_POINT }, +} }; + + +static const std::vector> RIGHT_TOUCH_MAP { { + { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH }, + { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH }, + { ovrTouch_RThumb, RS_TOUCH }, + { ovrTouch_ThumbUp, RIGHT_THUMB_UP }, + { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH }, + { ovrTouch_IndexPointing, RIGHT_INDEX_POINT }, +} }; + +void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _buttonPressedMap.clear(); + + int numTrackedControllers = 0; + quint64 currentTime = usecTimestampNow(); + handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose); + + static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED; + ovr::for_each_hand([&](ovrHandedness hand) { + size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1; + int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND; + auto& handData = _hands[handIndex]; + const auto& tracking = handData.tracking; + ++numTrackedControllers; + + // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) +// if (!hasInputFocus) { +// _poseStateMap.erase(controller); +// _poseStateMap[controller].valid = false; +// return; +// } + + if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) { + _poseStateMap.erase(controller); + handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose); + handData.lostTracking = false; + handData.lastPose = tracking.HeadPose; + return; + } + + if (handData.lostTracking) { + if (currentTime > handData.regainTrackingDeadline) { + _poseStateMap.erase(controller); + _poseStateMap[controller].valid = false; + return; + } + } else { + quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY; + handData.regainTrackingDeadline = deadlineToRegainTracking; + handData.lostTracking = true; + } + handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose); + }); + + + using namespace controller; + // Axes + { + const auto& inputState = _hands[0].state; + _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x; + _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y; + _axisStateMap[LT].value = inputState.IndexTrigger; + _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger; + for (const auto& pair : BUTTON_MAP) { + if (inputState.Buttons & pair.first) { + _buttonPressedMap.insert(pair.second); + qDebug()<<"AAAA:BUTTON PRESSED "<The Controller.Hardware.OculusTouch object has properties representing Oculus Rift. The property values are + * integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or + * Controller.Standard items in a {@link RouteObject} mapping.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyTypeDataDescription
Buttons
Anumbernumber"A" button pressed.
Bnumbernumber"B" button pressed.
Xnumbernumber"X" button pressed.
Ynumbernumber"Y" button pressed.
LeftApplicationMenunumbernumberLeft application menu button pressed. + *
RightApplicationMenunumbernumberRight application menu button pressed. + *
Sticks
LXnumbernumberLeft stick x-axis scale.
LYnumbernumberLeft stick y-axis scale.
RXnumbernumberRight stick x-axis scale.
RYnumbernumberRight stick y-axis scale.
LSnumbernumberLeft stick button pressed.
RSnumbernumberRight stick button pressed.
LSTouchnumbernumberLeft stick is touched.
RSTouchnumbernumberRight stick is touched.
Triggers
LTnumbernumberLeft trigger scale.
RTnumbernumberRight trigger scale.
LeftGripnumbernumberLeft grip scale.
RightGripnumbernumberRight grip scale.
Finger Abstractions
LeftPrimaryThumbTouchnumbernumberLeft thumb touching primary thumb + * button.
LeftSecondaryThumbTouchnumbernumberLeft thumb touching secondary thumb + * button.
LeftThumbUpnumbernumberLeft thumb not touching primary or secondary + * thumb buttons.
RightPrimaryThumbTouchnumbernumberRight thumb touching primary thumb + * button.
RightSecondaryThumbTouchnumbernumberRight thumb touching secondary thumb + * button.
RightThumbUpnumbernumberRight thumb not touching primary or secondary + * thumb buttons.
LeftPrimaryIndexTouchnumbernumberLeft index finger is touching primary + * index finger control.
LeftIndexPointnumbernumberLeft index finger is pointing, not touching + * primary or secondary index finger controls.
RightPrimaryIndexTouchnumbernumberRight index finger is touching primary + * index finger control.
RightIndexPointnumbernumberRight index finger is pointing, not touching + * primary or secondary index finger controls.
Avatar Skeleton
Headnumber{@link Pose}Head pose.
LeftHandnumber{@link Pose}Left hand pose.
RightHandnumber{@link Pose}right hand pose.
+ * @typedef {object} Controller.Hardware-OculusTouch + */ +controller::Input::NamedVector OculusMobileInputDevice::getAvailableInputs() const { + using namespace controller; + QVector availableInputs{ + // buttons + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + + // trackpad analogs + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + + // triggers + makePair(LT, "LT"), + makePair(RT, "RT"), + + // trigger buttons + //makePair(LB, "LB"), + //makePair(RB, "RB"), + + // side grip triggers + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), + + // joystick buttons + makePair(LS, "LS"), + makePair(RS, "RS"), + + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + makePair(HEAD, "Head"), + + makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), + makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), + makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), + makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), + makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), + makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + makePair(LEFT_THUMB_UP, "LeftThumbUp"), + makePair(RIGHT_THUMB_UP, "RightThumbUp"), + makePair(LEFT_INDEX_POINT, "LeftIndexPoint"), + makePair(RIGHT_INDEX_POINT, "RightIndexPoint"), + + makePair(BACK, "LeftApplicationMenu"), + makePair(START, "RightApplicationMenu"), + }; + return availableInputs; +} + +OculusMobileInputDevice::OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps) : controller::InputDevice("OculusTouch") { + qWarning() << "QQQ" << __FUNCTION__ << "Found " << devicesCaps.size() << "devices"; + for (const auto& deviceCaps : devicesCaps) { + size_t handIndex = -1; + if (deviceCaps.ControllerCapabilities & ovrControllerCaps_LeftHand) { + handIndex = 0; + } else if (deviceCaps.ControllerCapabilities & ovrControllerCaps_RightHand) { + handIndex = 1; + } else { + continue; + } + HandData& handData = _hands[handIndex]; + handData.state.Header.ControllerType = ovrControllerType_TrackedRemote; + handData.valid = true; + handData.caps = deviceCaps; + handData.update(session); + } +} + +void OculusMobileInputDevice::updateHands(ovrMobile* session) { + _headTracking = vrapi_GetPredictedTracking2(session, 0.0); + for (auto& hand : _hands) { + hand.update(session); + } +} + +QString OculusMobileInputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json"; + return MAPPING_JSON; +} + +// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class +InputPluginList getInputPlugins() { + InputPlugin* PLUGIN_POOL[] = { + new KeyboardMouseDevice(), + new OculusMobileControllerManager(), + nullptr + }; + + InputPluginList result; + for (int i = 0; PLUGIN_POOL[i]; ++i) { + InputPlugin* plugin = PLUGIN_POOL[i]; + if (plugin->isSupported()) { + result.push_back(InputPluginPointer(plugin)); + } + } + return result; +} \ No newline at end of file diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h new file mode 100644 index 0000000000..43ead8d6a2 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h @@ -0,0 +1,43 @@ +// +// Created by Bradley Austin Davis on 2016/03/04 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi__OculusMobileControllerManager +#define hifi__OculusMobileControllerManager + +#include +#include +#include + +#include + +#include +#include + +class OculusMobileControllerManager : public InputPlugin { +Q_OBJECT +public: + // Plugin functions + bool isSupported() const override; + const QString getName() const override { return NAME; } + bool isHandController() const override; + bool isHeadController() const override { return true; } + QStringList getSubdeviceNames() override; + + bool activate() override; + void deactivate() override; + + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + +private: + static const char* NAME; + + void checkForConnectedDevices(); +}; + +#endif // hifi__OculusMobileControllerManager diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp new file mode 100644 index 0000000000..34ba130c71 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -0,0 +1,269 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OculusMobileDisplayPlugin.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ovr; + +const char* OculusMobileDisplayPlugin::NAME { "Oculus Rift" }; +//thread_local bool renderThread = false; +#define OCULUS_APP_ID 2331695256865113 + +OculusMobileDisplayPlugin::OculusMobileDisplayPlugin() { + +} + +OculusMobileDisplayPlugin::~OculusMobileDisplayPlugin() { +} + +void OculusMobileDisplayPlugin::init() { + Parent::init(); + initVr(); + + emit deviceConnected(getName()); +} + +void OculusMobileDisplayPlugin::deinit() { + shutdownVr(); + Parent::deinit(); +} + +bool OculusMobileDisplayPlugin::internalActivate() { + _renderTargetSize = { 1024, 512 }; + _cullingProjection = ovr::toGlm(ovrMatrix4f_CreateProjectionFov(90.0f, 90.0f, 0.0f, 0.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); + + + withOvrJava([&](const ovrJava* java){ + _renderTargetSize = glm::uvec2{ + vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH), + vrapi_GetSystemPropertyInt(java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT), + }; + }); + + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = _cullingProjection; + }); + + // This must come after the initialization, so that the values calculated + // above are available during the customizeContext call (when not running + // in threaded present mode) + return Parent::internalActivate(); +} + +void OculusMobileDisplayPlugin::internalDeactivate() { + Parent::internalDeactivate(); + // ovr::releaseRenderSession(_session); +} + +void OculusMobileDisplayPlugin::customizeContext() { + qWarning() << "QQQ" << __FUNCTION__ << "done"; + gl::initModuleGl(); + _mainContext = _container->getPrimaryWidget()->context(); + _mainContext->makeCurrent(); + ovr::VrHandler::setHandler(this); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::customizeContext(); + qWarning() << "QQQ" << __FUNCTION__ << "done"; +} + +void OculusMobileDisplayPlugin::uncustomizeContext() { + ovr::VrHandler::setHandler(nullptr); + _mainContext->doneCurrent(); + _mainContext->makeCurrent(); + Parent::uncustomizeContext(); +} + +QRectF OculusMobileDisplayPlugin::getPlayAreaRect() { + QRectF result; + VrHandler::withOvrMobile([&](ovrMobile* session){ + ovrPosef pose; + ovrVector3f scale; + if (ovrSuccess != vrapi_GetBoundaryOrientedBoundingBox(session, &pose, &scale)) { + return; + } + // FIXME extract the center from the pose + glm::vec2 center { 0 }; + glm::vec2 dimensions = glm::vec2(scale.x, scale.z); + dimensions *= 2.0f; + result = QRectF(center.x, center.y, dimensions.x, dimensions.y); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + result = ovr::Fov{ trackingState.Eye[eye].ProjectionMatrix }.withZ(baseProjection); + }); + return result; +} + +glm::mat4 OculusMobileDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + glm::mat4 result = baseProjection; + VrHandler::withOvrMobile([&](ovrMobile* session){ + auto trackingState = vrapi_GetPredictedTracking2(session, 0.0); + ovr::Fov fovs[2]; + for (size_t i = 0; i < 2; ++i) { + fovs[i].extract(trackingState.Eye[i].ProjectionMatrix); + } + fovs[0].extend(fovs[1]); + return fovs[0].withZ(baseProjection); + }); + return result; +} + +void OculusMobileDisplayPlugin::resetSensors() { + VrHandler::withOvrMobile([&](ovrMobile* session){ + vrapi_RecenterPose(session); + }); + _currentRenderFrameInfo.renderPose = glm::mat4(); // identity +} + +float OculusMobileDisplayPlugin::getTargetFrameRate() const { + float result = 0.0f; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = vrapi_GetSystemPropertyFloat(java, VRAPI_SYS_PROP_DISPLAY_REFRESH_RATE); + }); + return result; +} + +bool OculusMobileDisplayPlugin::isHmdMounted() const { + bool result = false; + VrHandler::withOvrJava([&](const ovrJava* java){ + result = VRAPI_FALSE != vrapi_GetSystemStatusInt(java, VRAPI_SYS_STATUS_MOUNTED); + }); + return result; +} + +static void goToDevMobile() { + auto addressManager = DependencyManager::get(); + auto currentAddress = addressManager->currentAddress().toString().toStdString(); + if (std::string::npos == currentAddress.find("dev-mobile")) { + addressManager->handleLookupString("hifi://dev-mobile/495.236,501.017,482.434/0,0.97452,0,-0.224301"); + //addressManager->handleLookupString("hifi://dev-mobile/504,498,491/0,0,0,0"); + //addressManager->handleLookupString("hifi://dev-mobile/0,-1,1"); + } +} + +// Called on the render thread, establishes the rough tracking for the upcoming +bool OculusMobileDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + bool result = false; + _currentRenderFrameInfo = FrameInfo(); + ovrTracking2 trackingState = {}; + static bool resetTrackingTransform = true; + static glm::mat4 transformOffset; + + VrHandler::withOvrMobile([&](ovrMobile* session){ + if (resetTrackingTransform) { + auto pose = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_FLOOR_LEVEL); + transformOffset = glm::inverse(ovr::toGlm(pose)); + vrapi_SetTrackingTransform( session, pose); + resetTrackingTransform = false; + } + // Find a better way of + _currentRenderFrameInfo.predictedDisplayTime = vrapi_GetPredictedDisplayTime(session, currentPresentIndex() + 2); + trackingState = vrapi_GetPredictedTracking2(session, _currentRenderFrameInfo.predictedDisplayTime); + result = true; + }); + + + + if (result) { + _currentRenderFrameInfo.renderPose = transformOffset; + withNonPresentThreadLock([&] { + _currentRenderFrameInfo.sensorSampleTime = trackingState.HeadPose.TimeInSeconds; + _currentRenderFrameInfo.renderPose = transformOffset * ovr::toGlm(trackingState.HeadPose.Pose); + _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + _frameInfos[frameIndex] = _currentRenderFrameInfo; + _ipd = vrapi_GetInterpupillaryDistance(&trackingState); + ovr::for_each_eye([&](ovrEye eye){ + _eyeProjections[eye] = ovr::toGlm(trackingState.Eye[eye].ProjectionMatrix); + _eyeOffsets[eye] = glm::translate(mat4(), vec3{ _ipd * (eye == VRAPI_EYE_LEFT ? -0.5f : 0.5f), 0.0f, 0.0f }); + }); + }); + } + + // static uint32_t count = 0; + // if ((++count % 1000) == 0) { + // AbstractViewStateInterface::instance()->postLambdaEvent([] { + // goToDevMobile(); + // }); + // } + + return result && Parent::beginFrameRender(frameIndex); +} + +ovrTracking2 presentTracking; + +void OculusMobileDisplayPlugin::updatePresentPose() { + static QAndroidJniEnvironment* jniEnv = nullptr; + if (nullptr == jniEnv) { + jniEnv = new QAndroidJniEnvironment(); + } + VrHandler::withOvrMobile([&](ovrMobile* session){ + presentTracking = beginFrame(); + _currentPresentFrameInfo.sensorSampleTime = vrapi_GetTimeInSeconds(); + _currentPresentFrameInfo.predictedDisplayTime = presentTracking.HeadPose.TimeInSeconds; + _currentPresentFrameInfo.presentPose = ovr::toGlm(presentTracking.HeadPose.Pose); + }); +} + +void OculusMobileDisplayPlugin::internalPresent() { + VrHandler::pollTask(); + + if (!vrActive()) { + QThread::msleep(1); + return; + } + + auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() }; + VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking); + _presentRate.increment(); +} + +DisplayPluginList getDisplayPlugins() { + static DisplayPluginList result; + static std::once_flag once; + std::call_once(once, [&]{ + auto plugin = std::make_shared(); + plugin->isSupported(); + result.push_back(plugin); + }); + return result; +} + diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h new file mode 100644 index 0000000000..4a0a21e995 --- /dev/null +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2018/11/15 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +#include + +#include +#include +#include + +#include +#include + +typedef struct ovrTextureSwapChain ovrTextureSwapChain; +typedef struct ovrMobile ovrMobile; +typedef struct ANativeWindow ANativeWindow; + +class OculusMobileDisplayPlugin : public HmdDisplayPlugin, public ovr::VrHandler { + using Parent = HmdDisplayPlugin; +public: + OculusMobileDisplayPlugin(); + virtual ~OculusMobileDisplayPlugin(); + bool isSupported() const override { return true; }; + bool hasAsyncReprojection() const override { return true; } + bool getSupportsAutoSwitch() override final { return false; } + QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; } + + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; + + // Stereo specific methods + void resetSensors() override final; + bool beginFrameRender(uint32_t frameIndex) override; + + QRectF getPlayAreaRect() override; + float getTargetFrameRate() const override; + void init() override; + void deinit() override; + +protected: + const QString getName() const override { return NAME; } + + bool internalActivate() override; + void internalDeactivate() override; + + void customizeContext() override; + void uncustomizeContext() override; + + void updatePresentPose() override; + void internalPresent() override; + void hmdPresent() override { throw std::runtime_error("Unused"); } + bool isHmdMounted() const override; + + static const char* NAME; + mutable gl::Context* _mainContext{ nullptr }; + uint32_t _readFbo; +}; + diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 13a1328065..1ebb488458 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -124,6 +124,11 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _rigidBody->setGravity(_currentGravity * _currentUp); // set flag to enable custom contactAddedCallback _rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); + + // enable CCD + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + btCollisionShape* shape = _rigidBody->getCollisionShape(); assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); @@ -455,6 +460,12 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const // it's ok to change offset immediately -- there are no thread safety issues here _shapeLocalOffset = minCorner + 0.5f * scale; + + if (_rigidBody) { + // update CCD with new _radius + _rigidBody->setCcdSweptSphereRadius(_radius); + _rigidBody->setCcdMotionThreshold(_radius); + } } void CharacterController::setCollisionless(bool collisionless) { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index a82931064a..ce9cb20c21 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -115,17 +115,30 @@ void EntityMotionState::updateServerPhysicsVariables() { } void EntityMotionState::handleDeactivation() { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero + if (_entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + // Some non-physical event (script-call or network-packet) has modified the entity's transform and/or velocities + // at the last minute before deactivation --> the values stored in _server* and _body are stale. + // We assume the EntityMotionState is the last to know, so we copy from EntityItem and let things sort themselves out. + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); + } else { + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero + } } // virtual diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a571a81109..4453a7d9f0 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -27,6 +27,23 @@ #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" +static bool flipNormalsMyAvatarVsBackfacingTriangles(btManifoldPoint& cp, + const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) { + if (colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) { + auto triShape = static_cast(colObj1Wrap->getCollisionShape()); + const btVector3* v = triShape->m_vertices1; + btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]); + float nDotF = btDot(faceNormal, cp.m_normalWorldOnB); + if (nDotF <= 0.0f) { + faceNormal.normalize(); + // flip the contact normal to be aligned with the face normal + cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal; + } + } + // return value is currently ignored but to be future-proof: return false when not modifying friction + return false; +} PhysicsEngine::PhysicsEngine(const glm::vec3& offset) : _originOffset(offset), @@ -904,6 +921,16 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) { } } +void PhysicsEngine::enableGlobalContactAddedCallback(bool enabled) { + if (enabled) { + // register contact filter to help MyAvatar pass through backfacing triangles + gContactAddedCallback = flipNormalsMyAvatarVsBackfacingTriangles; + } else { + // deregister contact filter + gContactAddedCallback = nullptr; + } +} + struct AllContactsCallback : public btCollisionWorld::ContactResultCallback { AllContactsCallback(int32_t mask, int32_t group, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject, float threshold) : btCollisionWorld::ContactResultCallback(), diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index d10be018b8..43cc0d2176 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -148,6 +148,8 @@ public: // See PhysicsCollisionGroups.h for mask flags. std::vector contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const; + void enableGlobalContactAddedCallback(bool enabled); + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 211f6ca0a2..6eb6d531e1 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -26,6 +26,7 @@ ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() { const int8_t STENCIL_BACKGROUND = 0; _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + _procedural._opaqueState->setDepthTest(gpu::State::DepthTest(false)); } bool ProceduralSkybox::empty() { diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 8ecfb84633..3963ad5593 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "ScriptEngine.h" #include "ScriptEngineLogging.h" @@ -476,6 +477,8 @@ ScriptEnginePointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool i scriptUrl = normalizeScriptURL(scriptFilename); } + scriptUrl = QUrl(FileUtils::selectFile(scriptUrl.toString())); + auto scriptEngine = getScriptEngine(scriptUrl); if (scriptEngine && !scriptEngine->isStopping()) { return scriptEngine; diff --git a/libraries/shared/src/CrashHelpers.cpp b/libraries/shared/src/CrashHelpers.cpp index f8ca90bc4c..1676318f3e 100644 --- a/libraries/shared/src/CrashHelpers.cpp +++ b/libraries/shared/src/CrashHelpers.cpp @@ -11,6 +11,16 @@ #include "CrashHelpers.h" +#ifdef NDEBUG +// undefine NDEBUG so doAssert() works for all builds +#undef NDEBUG +#include +#define NDEBUG +#else +#include +#endif + + namespace crash { class B; @@ -34,6 +44,11 @@ A::~A() { _b->virtualFunction(); } +// only use doAssert() for debug purposes +void doAssert(bool value) { + assert(value); +} + void pureVirtualCall() { qCDebug(shared) << "About to make a pure virtual call"; B b; diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h index ad988c8906..247aea5cde 100644 --- a/libraries/shared/src/CrashHelpers.h +++ b/libraries/shared/src/CrashHelpers.h @@ -18,6 +18,7 @@ namespace crash { +void doAssert(bool value); // works for Release void pureVirtualCall(); void doubleFree(); void nullDeref(); diff --git a/libraries/shared/src/Grab.h b/libraries/shared/src/Grab.h index 5765d6fd0e..f16a80befa 100644 --- a/libraries/shared/src/Grab.h +++ b/libraries/shared/src/Grab.h @@ -26,16 +26,16 @@ public: void accumulate(glm::vec3 position, glm::quat orientation) { _position += position; _orientation = orientation; // XXX - count++; + _count++; } - glm::vec3 finalizePosition() { return count > 0 ? _position * (1.0f / count) : glm::vec3(0.0f); } + glm::vec3 finalizePosition() { return _count > 0 ? _position * (1.0f / _count) : glm::vec3(0.0f); } glm::quat finalizeOrientation() { return _orientation; } // XXX protected: glm::vec3 _position; glm::quat _orientation; - int count { 0 }; + int _count { 0 }; }; class Grab { @@ -48,7 +48,8 @@ public: _parentJointIndex(newParentJointIndex), _hand(newHand), _positionalOffset(newPositionalOffset), - _rotationalOffset(newRotationalOffset) {} + _rotationalOffset(newRotationalOffset), + _released(false) {} QByteArray toByteArray(); bool fromByteArray(const QByteArray& grabData); @@ -61,6 +62,7 @@ public: _positionalOffset = other->_positionalOffset; _rotationalOffset = other->_rotationalOffset; _actionID = other->_actionID; + _released = other->_released; return *this; } @@ -85,6 +87,9 @@ public: glm::quat getRotationalOffset() const { return _rotationalOffset; } void setRotationalOffset(glm::quat rotationalOffset) { _rotationalOffset = rotationalOffset; } + bool getReleased() const { return _released; } + void setReleased(bool value) { _released = value; } + protected: QUuid _actionID; // if an action is created in bullet for this grab, this is the ID QUuid _ownerID; // avatar ID of grabber @@ -93,6 +98,7 @@ protected: QString _hand; // "left" or "right" glm::vec3 _positionalOffset; // relative to joint glm::quat _rotationalOffset; // relative to joint + bool _released { false }; // released and scheduled for deletion }; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 65651373be..c51d9bf611 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -38,7 +38,6 @@ LogHandler::LogHandler() { } LogHandler::~LogHandler() { - flushRepeatedMessages(); } const char* stringForLogType(LogMsgType msgType) { diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index c524e3183b..d3ed79faf4 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1390,7 +1390,12 @@ void SpatiallyNestable::removeGrab(GrabPointer grab) { bool SpatiallyNestable::hasGrabs() { bool result { false }; _grabsLock.withReadLock([&] { - result = !_grabs.isEmpty(); + foreach (const GrabPointer &grab, _grabs) { + if (grab && !grab->getReleased()) { + result = true; + break; + } + } }); return result; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 319f07236b..e7a449f73f 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -218,6 +218,7 @@ public: virtual void addGrab(GrabPointer grab); virtual void removeGrab(GrabPointer grab); + virtual void disableGrab(GrabPointer grab) {}; bool hasGrabs(); virtual QUuid getEditSenderID(); @@ -241,7 +242,7 @@ protected: quint64 _rotationChanged { 0 }; mutable ReadWriteLockable _grabsLock; - QSet _grabs; + QSet _grabs; // upon this thing private: SpatiallyNestable() = delete; diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 0709a53602..f2a4925351 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -30,9 +30,18 @@ const QStringList& FileUtils::getFileSelectors() { static std::once_flag once; static QStringList extraSelectors; std::call_once(once, [] { + +#if defined(Q_OS_ANDROID) + extraSelectors << "android_" HIFI_ANDROID_APP; +#endif + #if defined(USE_GLES) extraSelectors << "gles"; #endif + +#ifndef Q_OS_ANDROID + extraSelectors << "webengine"; +#endif }); return extraSelectors; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index f67a356078..71bb65509f 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -49,6 +49,7 @@ #include #include "SecurityImageProvider.h" +#include "shared/FileUtils.h" #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" @@ -237,7 +238,9 @@ void OffscreenQmlSurface::clearFocusItem() { void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); - new QQmlFileSelector(engine); + QQmlFileSelector* fileSelector = new QQmlFileSelector(engine); + fileSelector->setExtraSelectors(FileUtils::getFileSelectors()); + static std::once_flag once; std::call_once(once, [] { qRegisterMetaType(); diff --git a/scripts/+android/defaultScripts.js b/scripts/+android_interface/defaultScripts.js similarity index 93% rename from scripts/+android/defaultScripts.js rename to scripts/+android_interface/defaultScripts.js index fbd03352de..8b3082d81a 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android_interface/defaultScripts.js @@ -13,10 +13,10 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", - "system/+android/touchscreenvirtualpad.js", - "system/+android/actionbar.js", - "system/+android/audio.js" , - "system/+android/modes.js", + "system/+android_interface/touchscreenvirtualpad.js", + "system/+android_interface/actionbar.js", + "system/+android_interface/audio.js" , + "system/+android_interface/modes.js", "system/makeUserConnection.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", @@ -34,7 +34,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEBUG_SCRIPTS = [ - "system/+android/stats.js" + "system/+android_interface/stats.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ ]; diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js new file mode 100644 index 0000000000..d22716302c --- /dev/null +++ b/scripts/+android_questInterface/defaultScripts.js @@ -0,0 +1,119 @@ +"use strict"; +/* jslint vars: true, plusplus: true */ + +// +// defaultScripts.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var DEFAULT_SCRIPTS_COMBINED = [ + "system/request-service.js", + "system/progress.js", + //"system/away.js", + "system/hmd.js", + "system/menu.js", + "system/bubble.js", + "system/pal.js", // "system/mod.js", // older UX, if you prefer + "system/avatarapp.js", + "system/makeUserConnection.js", + "system/tablet-goto.js", + "system/notifications.js", + "system/commerce/wallet.js", + "system/dialTone.js", + "system/quickGoto.js", + "system/firstPersonHMD.js", + "system/tablet-ui/tabletUI.js", + "system/miniTablet.js" +]; +var DEFAULT_SCRIPTS_SEPARATE = [ + "system/controllers/controllerScripts.js", + //"system/chat.js" +]; + +if (Window.interstitialModeEnabled) { + // Insert interstitial scripts at front so that they're started first. + DEFAULT_SCRIPTS_COMBINED.splice(0, 0, "system/interstitialPage.js", "system/redirectOverlays.js"); +} + +// add a menu item for debugging +var MENU_CATEGORY = "Developer > Scripting"; +var MENU_ITEM = "Debug defaultScripts.js"; + +var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); + +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; +} + +if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { + Menu.addMenuItem({ + menuName: MENU_CATEGORY, + menuItemName: MENU_ITEM, + isCheckable: true, + isChecked: previousSetting, + }); +} + +function loadSeparateDefaults() { + for (var i in DEFAULT_SCRIPTS_SEPARATE) { + Script.load(DEFAULT_SCRIPTS_SEPARATE[i]); + } +} + +function runDefaultsTogether() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.include(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.load(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +// start all scripts +if (Menu.isOptionChecked(MENU_ITEM)) { + // we're debugging individual default scripts + // so we load each into its own ScriptEngine instance + runDefaultsSeparately(); +} else { + // include all default scripts into this ScriptEngine + runDefaultsTogether(); +} + +function menuItemEvent(menuItem) { + if (menuItem === MENU_ITEM) { + var isChecked = Menu.isOptionChecked(MENU_ITEM); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); + } + Menu.triggerOption("Reload All Scripts"); + } +} + +function removeMenuItem() { + if (!Menu.isOptionChecked(MENU_ITEM)) { + Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + } +} + +Script.scriptEnding.connect(function() { + removeMenuItem(); +}); + +Menu.menuItemEvent.connect(menuItemEvent); diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android_interface/actionbar.js similarity index 91% rename from scripts/system/+android/actionbar.js rename to scripts/system/+android_interface/actionbar.js index e7e2459e69..74b3896a62 100644 --- a/scripts/system/+android/actionbar.js +++ b/scripts/system/+android_interface/actionbar.js @@ -26,8 +26,8 @@ function init() { qml: "hifi/ActionBar.qml" }); backButton = actionbar.addButton({ - icon: "icons/+android/backward.svg", - activeIcon: "icons/+android/backward.svg", + icon: "icons/+android_interface/backward.svg", + activeIcon: "icons/+android_interface/backward.svg", text: "", bgOpacity: 0.0, hoverBgOpacity: 0.0, diff --git a/scripts/system/+android/audio.js b/scripts/system/+android_interface/audio.js similarity index 100% rename from scripts/system/+android/audio.js rename to scripts/system/+android_interface/audio.js diff --git a/scripts/system/+android/clickWeb.js b/scripts/system/+android_interface/clickWeb.js similarity index 100% rename from scripts/system/+android/clickWeb.js rename to scripts/system/+android_interface/clickWeb.js diff --git a/scripts/system/+android/displayNames.js b/scripts/system/+android_interface/displayNames.js similarity index 100% rename from scripts/system/+android/displayNames.js rename to scripts/system/+android_interface/displayNames.js diff --git a/scripts/system/+android/modes.js b/scripts/system/+android_interface/modes.js similarity index 100% rename from scripts/system/+android/modes.js rename to scripts/system/+android_interface/modes.js diff --git a/scripts/system/+android/radar.js b/scripts/system/+android_interface/radar.js similarity index 100% rename from scripts/system/+android/radar.js rename to scripts/system/+android_interface/radar.js diff --git a/scripts/system/+android/stats.js b/scripts/system/+android_interface/stats.js similarity index 100% rename from scripts/system/+android/stats.js rename to scripts/system/+android_interface/stats.js diff --git a/scripts/system/+android/touchscreenvirtualpad.js b/scripts/system/+android_interface/touchscreenvirtualpad.js similarity index 100% rename from scripts/system/+android/touchscreenvirtualpad.js rename to scripts/system/+android_interface/touchscreenvirtualpad.js diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android_interface/uniqueColor.js similarity index 100% rename from scripts/system/+android/uniqueColor.js rename to scripts/system/+android_interface/uniqueColor.js diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 19efdc042c..17ff918243 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -379,21 +379,16 @@ function deleteSendMoneyParticleEffect() { function onUsernameChanged() { } -var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); -var METAVERSE_SERVER_URL = Account.metaverseServerURL; -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } function setCertificateInfo(itemCertificateId) { @@ -425,10 +420,10 @@ function fromQml(message) { case 'purchases': case 'marketplace cta': case 'mainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; - default: // User needs to return to an individual marketplace item URL - ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + default: + openMarketplace(); break; } break; @@ -440,13 +435,13 @@ function fromQml(message) { case 'maybeEnableHmdPreview': break; // do nothing here, handled in marketplaces.js case 'transactionHistory_linkClicked': - ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'goToMarketplaceMainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'goToMarketplaceItemPage': - ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'refreshConnections': print('Refreshing Connections...'); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index f1931192e4..8d408169ba 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -19,6 +19,7 @@ var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; + var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -72,7 +73,16 @@ // Footer actions. $("#back-button").on("click", function () { - (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); + if (document.referrer !== "") { + window.history.back(); + } else { + var params = { type: GOTO_MARKETPLACE }; + var itemIdMatch = location.search.match(/itemId=([^&]*)/); + if (itemIdMatch && itemIdMatch.length === 2) { + params.itemId = itemIdMatch[1]; + } + EventBridge.emitWebEvent(JSON.stringify(params)); + } }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(JSON.stringify({ @@ -93,7 +103,9 @@ window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = marketplaceBaseURL + "/marketplace?"; + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); }); } diff --git a/scripts/system/marketplaces/marketplace.js b/scripts/system/marketplaces/marketplace.js index d3e5c96739..70680acd1d 100644 --- a/scripts/system/marketplaces/marketplace.js +++ b/scripts/system/marketplaces/marketplace.js @@ -1,8 +1,8 @@ // // marketplace.js // -// Created by Eric Levin on 8 Jan 2016 -// Copyright 2016 High Fidelity, Inc. +// Created by Roxanne Skelly on 1/18/2019 +// Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -10,108 +10,27 @@ (function() { // BEGIN LOCAL_SCOPE -/* global WebTablet */ -Script.include("../libraries/WebTablet.js"); +var AppUi = Script.require('appUi'); -var toolIconUrl = Script.resolvePath("../assets/images/tools/"); +var BUTTON_NAME = "MARKET"; +var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml"; +var ui; +function startup() { -var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; -var marketplaceWindow = new OverlayWebWindow({ - title: "Marketplace", - source: "about:blank", - width: 900, - height: 700, - visible: false -}); - -var toolHeight = 50; -var toolWidth = 50; -var TOOLBAR_MARGIN_Y = 0; -var marketplaceVisible = false; -var marketplaceWebTablet; - -// We persist avatarEntity data in the .ini file, and reconsistitute it on restart. -// To keep things consistent, we pickle the tablet data in Settings, and kill any existing such on restart and domain change. -var persistenceKey = "io.highfidelity.lastDomainTablet"; - -function shouldShowWebTablet() { - var rightPose = Controller.getPoseValue(Controller.Standard.RightHand); - var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand); - var hasHydra = !!Controller.Hardware.Hydra; - return HMD.active && (leftPose.valid || rightPose.valid || hasHydra); + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: MARKETPLACE_QML_SOURCE + }); } -function showMarketplace(marketplaceID) { - var url = MARKETPLACE_URL; - if (marketplaceID) { - url = url + "/items/" + marketplaceID; - } - tablet.gotoWebScreen(url); - marketplaceVisible = true; - UserActivityLogger.openedMarketplace(); +function shutdown() { } -function hideTablet(tablet) { - if (!tablet) { - return; - } - updateButtonState(false); - tablet.unregister(); - tablet.destroy(); - marketplaceWebTablet = null; - Settings.setValue(persistenceKey, ""); -} -function clearOldTablet() { // If there was a tablet from previous domain or session, kill it and let it be recreated - -} -function hideMarketplace() { - if (marketplaceWindow.visible) { - marketplaceWindow.setVisible(false); - marketplaceWindow.setURL("about:blank"); - } else if (marketplaceWebTablet) { - - } - marketplaceVisible = false; -} - -function toggleMarketplace() { - if (marketplaceVisible) { - hideMarketplace(); - } else { - showMarketplace(); - } -} - -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - -var browseExamplesButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - text: "MARKET" -}); - -function updateButtonState(visible) { - -} -function onMarketplaceWindowVisibilityChanged() { - updateButtonState(marketplaceWindow.visible); - marketplaceVisible = marketplaceWindow.visible; -} - -function onClick() { - toggleMarketplace(); -} - -browseExamplesButton.clicked.connect(onClick); -marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged); - -clearOldTablet(); // Run once at startup, in case there's anything laying around from a crash. -// We could also optionally do something like Window.domainChanged.connect(function () {Script.setTimeout(clearOldTablet, 2000)}), -// but the HUD version stays around, so lets do the same. - -Script.scriptEnding.connect(function () { - browseExamplesButton.clicked.disconnect(onClick); - tablet.removeButton(browseExamplesButton); - marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged); -}); +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e4891a9bae..e059081741 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,12 +19,14 @@ var selectionDisplay = null; // for gridTool.js to ignore var AppUi = Script.require('appUi'); Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); +Script.include("/~/system/libraries/accountUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; // HRS FIXME "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; @@ -36,6 +38,7 @@ var CLARA_IO_STATUS = "CLARA.IO STATUS"; var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; +var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -154,18 +157,14 @@ function onMarketplaceOpen(referrer) { } } -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +function openMarketplace(optionalItem, edition) { + ui.open(MARKETPLACE_QML_PATH); + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem, edition: edition } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } // Function Name: wireQmlEventBridge() @@ -439,7 +438,9 @@ var referrerURL; // Used for updating Purchases QML var filterText; // Used for updating Purchases QML function onWebEventReceived(message) { message = JSON.parse(message); - if (message.type === GOTO_DIRECTORY) { + if (message.type === GOTO_MARKETPLACE) { + openMarketplace(message.itemId); + } else if (message.type === GOTO_DIRECTORY) { // This is the chooser between marketplaces. Only OUR markteplace // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -485,7 +486,6 @@ function onWebEventReceived(message) { } else if (message.type === "WALLET_SETUP") { setupWallet('marketplace cta'); } else if (message.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -546,11 +546,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { openWallet(); break; case 'checkout_cancelClicked': - openMarketplace(message.params); + openMarketplace(message.itemId); break; case 'header_goToPurchases': case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; ui.open(MARKETPLACE_PURCHASES_QML_PATH); break; @@ -560,6 +559,25 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_continue': openMarketplace(); break; + case 'marketplace_checkout': + ui.open(MARKETPLACE_CHECKOUT_QML_PATH); + ui.tablet.sendToQml({ + method: 'updateCheckoutQMLItemID', + params: message + }); + break; + case 'marketplace_marketplaces': + // This is the chooser between marketplaces. Only OUR markteplace + // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. + var url = MARKETPLACES_URL; + if(message.itemId) { + url = url + "?itemId=" + message.itemId + } + ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'marketplace_open_link': + ui.open(message.link); + break; case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': @@ -588,18 +606,18 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { } break; case 'header_marketplaceImageClicked': - openMarketplace(message.referrerURL); + openMarketplace(); break; case 'purchases_goToMarketplaceClicked': openMarketplace(); break; case 'updateItemClicked': - openMarketplace(message.upgradeUrl + "?edition=" + message.itemEdition); + openMarketplace(message.itemId, message.itemEdition); break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': // Should/must NOT check for wallet setup. - ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'needsLogIn_loginClicked': openLoginWindow(); @@ -624,10 +642,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': - openMarketplace(message.marketplaceUrl); + console.log("INSPECTION CERTIFICATE SHOW IN MARKETPLACE CLICKED: " + message.itemId); + openMarketplace(message.itemId); break; case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -689,7 +707,7 @@ var onMarketplaceScreen = false; var onWalletScreen = false; var onTabletScreenChanged = function onTabletScreenChanged(type, url) { ui.setCurrentVisibleScreenMetadata(type, url); - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onMarketplaceScreen = type === "QML" && url.indexOf(MARKETPLACE_QML_PATH) !== -1; onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; var onCommerceScreenNow = type === "QML" && ( @@ -699,7 +717,9 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var onMarketplaceItemTesterScreenNow = ( url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 || url === MARKETPLACE_ITEM_TESTER_QML_PATH); - + var onMarketplaceScreenNow = ( + url.indexOf(MARKETPLACE_QML_PATH) !== -1 || + url === MARKETPLACE_QML_PATH); if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen) || (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) @@ -711,7 +731,7 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; - wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen); + wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen || onMarketplaceScreenNow); if (url === MARKETPLACE_PURCHASES_QML_PATH) { // FIXME? Is there a race condition here in which the event @@ -734,11 +754,8 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { Keyboard.raised = false; } - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } + ContextOverlay.isInMarketplaceInspectionMode = false; + if (onInspectionCertificateScreen) { setCertificateInfo(contextOverlayEntity); @@ -769,16 +786,13 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var BUTTON_NAME = "MARKET"; -var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; -// Append "?" if necessary to signal injected script that it's the initial page. -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + (MARKETPLACE_URL.indexOf("?") > -1 ? "" : "?"); + var ui; function startup() { ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 9, - inject: MARKETPLACES_INJECT_SCRIPT_URL, - home: MARKETPLACE_URL_INITIAL, + home: MARKETPLACE_QML_PATH, onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived }); diff --git a/scripts/system/quickGoto.js b/scripts/system/quickGoto.js new file mode 100644 index 0000000000..c5560cce83 --- /dev/null +++ b/scripts/system/quickGoto.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// quickGoto.js +// scripts/system/ +// +// Created by Dante Ruiz +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + function addGotoButton(destination) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: destination + }); + var buttonDestination = destination; + button.clicked.connect(function() { + Window.location = "hifi://" + buttonDestination; + }); + Script.scriptEnding.connect(function () { + tablet.removeButton(button); + }); + } + + addGotoButton("dev-mobile"); + addGotoButton("quest-dev"); + +}()); // END LOCAL_SCOPE diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 17999c4d8e..2af4d5f2cd 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -1,7 +1,7 @@ # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared animation gpu fbx graphics networking test-utils) + link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils) package_libraries_for_deployment() endmacro () diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index 910770bb0d..708b1b2d2a 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -143,7 +143,7 @@ void AnimInverseKinematicsTests::testSingleChain() { ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"), QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"), QString("poleReferenceVectorD"), QString("poleVectorD")); - AnimNode::Triggers triggers; + AnimVariantMap triggers; // the IK solution should be: // @@ -236,7 +236,7 @@ void AnimInverseKinematicsTests::testSingleChain() { ikDoll.setTargetVars(QString("D"), QString("positionD"), QString("rotationD"), QString("targetTypeD"), QString("weightD"), 1.0f, flexCoefficients, QString("poleVectorEnabledD"), QString("poleReferenceVectorD"), QString("poleVectorD")); - AnimNode::Triggers triggers; + AnimVariantMap triggers; // the IK solution should be: // diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 01c8d1c1b6..0cd9571e22 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ void AnimTests::initTestCase() { DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); } @@ -84,26 +86,26 @@ void AnimTests::testClipEvaulate() { AnimClip clip(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag); - AnimNode::Triggers triggers; + AnimVariantMap triggers; clip.evaluate(vars, context, framesToSec(10.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 12.0f, TEST_EPSILON); // does it loop? - triggers.clear(); + triggers.clearMap(); clip.evaluate(vars, context, framesToSec(12.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, TEST_EPSILON); // Note: frame 3 and not 4, because extra frame between start and end. // did we receive a loop trigger? - QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end()); + QVERIFY(triggers.hasKey("myClipNodeOnLoop")); // does it pause at end? - triggers.clear(); + triggers.clearMap(); clip.setLoopFlagVar("FalseVar"); clip.evaluate(vars, context, framesToSec(20.0f), triggers); QCOMPARE_WITH_ABS_ERROR(clip._frame, 22.0f, TEST_EPSILON); // did we receive a done trigger? - QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnDone") != triggers.end()); + QVERIFY(triggers.hasKey("myClipNodeOnDone")); } void AnimTests::testClipEvaulateWithVars() { @@ -133,7 +135,7 @@ void AnimTests::testClipEvaulateWithVars() { clip.setTimeScaleVar("timeScale2"); clip.setLoopFlagVar("loopFlag2"); - AnimNode::Triggers triggers; + AnimVariantMap triggers; clip.evaluate(vars, context, framesToSec(0.1f), triggers); // verify that the values from the AnimVariantMap made it into the clipNode's @@ -284,11 +286,11 @@ void AnimTests::testAccumulateTime() { timeScale = 1.0f; float dt = 1.0f; QString id = "testNode"; - AnimNode::Triggers triggers; + AnimVariantMap triggers; float loopFlag = true; float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); // a one frame looping animation should NOT trigger onLoop events - QVERIFY(triggers.empty()); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); const uint32_t MAX_TRIGGER_COUNT = 3; @@ -296,45 +298,45 @@ void AnimTests::testAccumulateTime() { endFrame = 1.1f; timeScale = 10.0f; dt = 10.0f; - triggers.clear(); + triggers.clearMap(); loopFlag = true; resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); - // a short animation with a large dt & a large timescale, should only create a MAXIMUM of 3 loop events. - QVERIFY(triggers.size() <= MAX_TRIGGER_COUNT); + // a short animation with a large dt & a large timescale, should generate a onLoop event. + QVERIFY(triggers.hasKey("testNodeOnLoop")); } void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const { float dt = (1.0f / 30.0f) / timeScale; // sec QString id = "testNode"; - AnimNode::Triggers triggers; + AnimVariantMap triggers; bool loopFlag = false; float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 1.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 2.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 3.0f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // test onDone trigger and frame clamping. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnDone")); + triggers.clearMap(); resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnDone")); + triggers.clearMap(); // test onLoop trigger and looping frame logic loopFlag = true; @@ -342,26 +344,26 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram // should NOT trigger loop even though we stop at last frame, because there is an extra frame between end and start frames. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // now we should hit loop trigger resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // should NOT trigger loop, even though we move past the end frame, because of extra frame between end and start. resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); QVERIFY(resultFrame == endFrame + 0.5f); - QVERIFY(triggers.empty()); - triggers.clear(); + QVERIFY(!triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); // now we should hit loop trigger resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); QVERIFY(resultFrame == startFrame + 0.5f); - QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); - triggers.clear(); + QVERIFY(triggers.hasKey("testNodeOnLoop")); + triggers.clearMap(); } void AnimTests::testAnimPose() { diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp index 7fbf43139f..e74caddd5e 100644 --- a/tools/gpu-frame-player/src/PlayerWindow.cpp +++ b/tools/gpu-frame-player/src/PlayerWindow.cpp @@ -64,11 +64,37 @@ void PlayerWindow::loadFrame() { } void PlayerWindow::keyPressEvent(QKeyEvent* event) { + bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); + float moveScale = isShifted ? 10.0f : 1.0f; switch (event->key()) { case Qt::Key_F1: loadFrame(); return; + case Qt::Key_W: + _renderThread.move(vec3{ 0, 0, -0.1f } * moveScale); + return; + + case Qt::Key_S: + _renderThread.move(vec3{ 0, 0, 0.1f } * moveScale); + return; + + case Qt::Key_A: + _renderThread.move(vec3{ -0.1f, 0, 0 } * moveScale); + return; + + case Qt::Key_D: + _renderThread.move(vec3{ 0.1f, 0, 0 } * moveScale); + return; + + case Qt::Key_E: + _renderThread.move(vec3{ 0, 0.1f, 0 } * moveScale); + return; + + case Qt::Key_F: + _renderThread.move(vec3{ 0, -0.1f, 0 } * moveScale); + return; + default: break; } @@ -106,5 +132,4 @@ void PlayerWindow::loadFrame(const QString& path) { } resize(size.x, size.y); } - _renderThread.submitFrame(frame); } diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index 608e8f250f..ff0d7630e5 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -20,6 +20,11 @@ void RenderThread::resize(const QSize& newSize) { _pendingSize.push(newSize); } +void RenderThread::move(const glm::vec3& v) { + std::unique_lock lock(_frameLock); + _correction = glm::inverse(glm::translate(mat4(), v)) * _correction; +} + void RenderThread::initialize(QWindow* window) { std::unique_lock lock(_frameLock); setObjectName("RenderThread"); @@ -27,9 +32,12 @@ void RenderThread::initialize(QWindow* window) { _window = window; #ifdef USE_GL + _window->setFormat(getDefaultOpenGLSurfaceFormat()); _context.setWindow(window); _context.create(); - _context.makeCurrent(); + if (!_context.makeCurrent()) { + qFatal("Unable to make context current"); + } QOpenGLContextWrapper(_context.qglContext()).makeCurrent(_window); glGenTextures(1, &_externalTexture); glBindTexture(GL_TEXTURE_2D, _externalTexture); @@ -105,6 +113,13 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { #ifdef USE_GL _context.makeCurrent(); #endif + if (_correction != glm::mat4()) { + std::unique_lock lock(_frameLock); + if (_correction != glm::mat4()) { + _backend->setCameraCorrection(_correction, _activeFrame->view); + //_prevRenderView = _correction * _activeFrame->view; + } + } _backend->recycle(); _backend->syncCache(); @@ -139,18 +154,29 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { using namespace vks::debug::marker; beginRegion(commandBuffer, "executeFrame", glm::vec4{ 1, 1, 1, 1 }); #endif + auto& glbackend = (gpu::gl::GLBackend&)(*_backend); + glm::uvec2 fboSize{ frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; + auto fbo = glbackend.getFramebufferID(frame->framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glClearColor(0, 0, 0, 1); + glClearDepth(0); + glClear(GL_DEPTH_BUFFER_BIT); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + //_gpuContext->enableStereo(true); if (frame && !frame->batches.empty()) { _gpuContext->executeFrame(frame); } #ifdef USE_GL - auto& glbackend = (gpu::gl::GLBackend&)(*_backend); - glm::uvec2 fboSize{ frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; - auto fbo = glbackend.getFramebufferID(frame->framebuffer); - glDisable(GL_FRAMEBUFFER_SRGB); - glBlitNamedFramebuffer(fbo, 0, 0, 0, fboSize.x, fboSize.y, 0, 0, windowSize.width(), windowSize.height(), - GL_COLOR_BUFFER_BIT, GL_NEAREST); + //glDisable(GL_FRAMEBUFFER_SRGB); + //glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBlitFramebuffer( + 0, 0, fboSize.x, fboSize.y, + 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); (void)CHECK_GL_ERROR(); _context.swapBuffers(); @@ -183,11 +209,11 @@ bool RenderThread::process() { pendingFrames.swap(_pendingFrames); pendingSize.swap(_pendingSize); } - + while (!pendingFrames.empty()) { _activeFrame = pendingFrames.front(); - _gpuContext->consumeFrameUpdates(_activeFrame); pendingFrames.pop(); + _gpuContext->consumeFrameUpdates(_activeFrame); } while (!pendingSize.empty()) { diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h index 7312fece6c..09eef56623 100644 --- a/tools/gpu-frame-player/src/RenderThread.h +++ b/tools/gpu-frame-player/src/RenderThread.h @@ -55,6 +55,8 @@ public: std::queue _pendingSize; gpu::FramePointer _activeFrame; uint32_t _externalTexture{ 0 }; + void move(const glm::vec3& v); + glm::mat4 _correction; void resize(const QSize& newSize); diff --git a/tools/noramlizeFrame.py b/tools/noramlizeFrame.py new file mode 100644 index 0000000000..f1012f6428 --- /dev/null +++ b/tools/noramlizeFrame.py @@ -0,0 +1,62 @@ +import os +import json +import shutil +import sys + +def scriptRelative(*paths): + scriptdir = os.path.dirname(os.path.realpath(sys.argv[0])) + result = os.path.join(scriptdir, *paths) + result = os.path.realpath(result) + result = os.path.normcase(result) + return result + + + +class FrameProcessor: + def __init__(self, filename): + self.filename = filename + dir, name = os.path.split(self.filename) + self.dir = dir + self.ktxDir = os.path.join(self.dir, 'ktx') + os.makedirs(self.ktxDir, exist_ok=True) + self.resDir = scriptRelative("../interface/resources") + + if (name.endswith(".json")): + self.name = name[0:-5] + else: + self.name = name + self.filename = self.filename + '.json' + + with open(self.filename, 'r') as f: + self.json = json.load(f) + + + def processKtx(self, texture): + if texture is None: return + if not 'ktxFile' in texture: return + sourceKtx = texture['ktxFile'] + if sourceKtx.startswith(':'): + sourceKtx = sourceKtx[1:] + while sourceKtx.startswith('/'): + sourceKtx = sourceKtx[1:] + sourceKtx = os.path.join(self.resDir, sourceKtx) + sourceKtxDir, sourceKtxName = os.path.split(sourceKtx) + destKtx = os.path.join(self.ktxDir, sourceKtxName) + if not os.path.isfile(destKtx): + shutil.copy(sourceKtx, destKtx) + newValue = 'ktx/' + sourceKtxName + texture['ktxFile'] = newValue + + + def process(self): + for texture in self.json['textures']: + self.processKtx(texture) + + with open(self.filename, 'w') as f: + json.dump(self.json, f, indent=2) + +fp = FrameProcessor("D:/Frames/20190114_1629.json") +fp.process() + + +#C:\Users\bdavi\git\hifi\interface\resources\meshes \ No newline at end of file