mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-10 08:12:08 +02:00
Merge branch 'master' into M21222
This commit is contained in:
commit
4779a6b156
362 changed files with 14173 additions and 3145 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -24,6 +24,7 @@ android/**/bin
|
|||
android/**/src/main/res/values/libs.xml
|
||||
android/**/src/main/assets
|
||||
android/**/gradle*
|
||||
*.class
|
||||
|
||||
# VSCode
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
|
@ -98,12 +99,9 @@ tools/jsdoc/package-lock.json
|
|||
# Python compile artifacts
|
||||
**/__pycache__
|
||||
|
||||
# ignore unneeded unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter/Library
|
||||
tools/unity-avatar-exporter/Logs
|
||||
tools/unity-avatar-exporter/Packages
|
||||
tools/unity-avatar-exporter/ProjectSettings
|
||||
tools/unity-avatar-exporter/Temp
|
||||
# ignore local unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter
|
||||
|
||||
server-console/package-lock.json
|
||||
vcpkg/
|
||||
/tools/nitpick/compiledResources
|
||||
|
|
65
BUILD_QUEST.md
Normal file
65
BUILD_QUEST.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
Please read the [general build guide](BUILD.md) for information on building other platform. Only Quest specific instructions are found in this file.
|
||||
|
||||
# Dependencies
|
||||
|
||||
Building is currently supported on OSX, Windows and Linux platforms, but developers intending to do work on the library dependencies are strongly urged to use 64 bit Linux as a build platform
|
||||
|
||||
You will need the following tools to build Android targets.
|
||||
|
||||
* [Android Studio](https://developer.android.com/studio/index.html)
|
||||
|
||||
### Android Studio
|
||||
|
||||
Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager
|
||||
|
||||
From the SDK Platforms tab, select API levels 24 and 26.
|
||||
|
||||
From the SDK Tools tab select the following
|
||||
|
||||
* Android SDK Build-Tools
|
||||
* GPU Debugging Tools
|
||||
* CMake (even if you have a separate CMake installation)
|
||||
* LLDB
|
||||
* Android SDK Platform-Tools
|
||||
* Android SDK Tools
|
||||
* NDK (even if you have the NDK installed separately)
|
||||
|
||||
Make sure the NDK installed version is 18 (or higher)
|
||||
|
||||
# Environment
|
||||
|
||||
Setting up the environment for android builds requires some additional steps
|
||||
|
||||
#### Set up machine specific Gradle properties
|
||||
|
||||
Create a `gradle.properties` file in $HOME/.gradle. Edit the file to contain the following
|
||||
|
||||
HIFI_ANDROID_PRECOMPILED=<your_home_directory>/Android/hifi_externals
|
||||
HIFI_ANDROID_KEYSTORE=<key_store_directory>/<keystore_name>.jks
|
||||
HIFI_ANDROID_KEYSTORE_PASSWORD=<password>
|
||||
HIFI_ANDROID_KEY_ALIAS=<key_alias>
|
||||
HIFI_ANDROID_KEY_PASSWORD=<key_password>
|
||||
|
||||
Note, do not use `$HOME` for the path. It must be a fully qualified path name.
|
||||
|
||||
### Setup the repository
|
||||
|
||||
Clone the repository
|
||||
|
||||
`git clone https://github.com/highfidelity/hifi.git`
|
||||
|
||||
Enter the repository `android` directory
|
||||
|
||||
`cd hifi/android`
|
||||
|
||||
# Building & Running
|
||||
|
||||
* Open Android Studio
|
||||
* Choose _Open Existing Android Studio Project_
|
||||
* Navigate to the `hifi` repository and choose the `android` folder and select _OK_
|
||||
* Open Gradle.settings and comment out any projects not necessary
|
||||
* From _File_ menu select _Sync with File System_ to resync Gradle settings
|
||||
* From the _Build_ menu select _Make Project_
|
||||
* From
|
||||
* Once the build completes, from the _Run_ menu select _Run App_
|
||||
|
|
@ -52,6 +52,7 @@ else()
|
|||
set(MOBILE 0)
|
||||
endif()
|
||||
|
||||
set(HIFI_USE_OPTIMIZED_IK OFF)
|
||||
set(BUILD_CLIENT_OPTION ON)
|
||||
set(BUILD_SERVER_OPTION ON)
|
||||
set(BUILD_TESTS_OPTION OFF)
|
||||
|
@ -100,6 +101,13 @@ if (ANDROID)
|
|||
add_definitions(-DCUSTOM_DISPLAY_PLUGINS)
|
||||
set(PLATFORM_PLUGIN_LIBRARIES oculusMobile oculusMobilePlugin)
|
||||
endif()
|
||||
|
||||
# Allow client code to use preprocessor macros to distinguish between quest and non-quest builds
|
||||
if (${HIFI_ANDROID_APP} STREQUAL "questInterface")
|
||||
add_definitions(-DANDROID_APP_QUEST_INTERFACE)
|
||||
elseif(${HIFI_ANDROID_APP} STREQUAL "interface")
|
||||
add_definitions(-DANDROID_APP_INTERFACE)
|
||||
endif()
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
|
||||
endif ()
|
||||
|
@ -108,7 +116,7 @@ if (USE_GLES AND (NOT ANDROID))
|
|||
set(DISABLE_QML_OPTION ON)
|
||||
endif()
|
||||
|
||||
|
||||
option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
|
||||
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
|
||||
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
|
||||
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
|
||||
|
@ -139,6 +147,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
|||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK})
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
|
@ -184,6 +193,10 @@ find_package( Threads )
|
|||
add_definitions(-DGLM_FORCE_RADIANS)
|
||||
add_definitions(-DGLM_ENABLE_EXPERIMENTAL)
|
||||
add_definitions(-DGLM_FORCE_CTOR_INIT)
|
||||
if (HIFI_USE_OPTIMIZED_IK)
|
||||
MESSAGE(STATUS "SET THE USE IK DEFINITION ")
|
||||
add_definitions(-DHIFI_USE_OPTIMIZED_IK)
|
||||
endif()
|
||||
set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
|
||||
|
||||
set(EXTERNAL_PROJECT_PREFIX "project")
|
||||
|
|
|
@ -36,11 +36,6 @@ apply plugin: 'com.android.application'
|
|||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
//buildToolsVersion '27.0.3'
|
||||
|
||||
def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
|
||||
def appVersionName = RELEASE_NUMBER ?: "1.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.highfidelity.hifiinterface"
|
||||
minSdkVersion 24
|
||||
|
@ -66,10 +61,10 @@ 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 : ''
|
||||
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks')
|
||||
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password'
|
||||
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0'
|
||||
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,10 +85,7 @@ android {
|
|||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEY_ALIAS") &&
|
||||
project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
|
||||
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""
|
||||
|
|
|
@ -81,6 +81,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
private boolean nativeEnterBackgroundCallEnqueued = false;
|
||||
private SlidingDrawer mWebSlidingDrawer;
|
||||
private boolean mStartInDomain;
|
||||
private boolean isLoading;
|
||||
// private GvrApi gvrApi;
|
||||
// Opaque native pointer to the Application C++ object.
|
||||
// This object is owned by the InterfaceActivity instance and passed to the native methods.
|
||||
|
@ -94,7 +95,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.isLoading = true;
|
||||
isLoading = true;
|
||||
Intent intent = getIntent();
|
||||
if (intent.hasExtra(DOMAIN_URL) && !TextUtils.isEmpty(intent.getStringExtra(DOMAIN_URL))) {
|
||||
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
|
||||
|
@ -145,7 +146,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (super.isLoading) {
|
||||
if (isLoading) {
|
||||
nativeEnterBackgroundCallEnqueued = true;
|
||||
} else {
|
||||
nativeEnterBackground();
|
||||
|
@ -172,7 +173,6 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
super.onResume();
|
||||
nativeEnterForeground();
|
||||
surfacesWorkaround();
|
||||
keepInterfaceRunning = false;
|
||||
registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
||||
//gvrApi.resumeTracking();
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
}
|
||||
|
||||
public void onAppLoadedComplete() {
|
||||
super.isLoading = false;
|
||||
isLoading = false;
|
||||
if (nativeEnterBackgroundCallEnqueued) {
|
||||
nativeEnterBackground();
|
||||
}
|
||||
|
@ -413,7 +413,6 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
|||
|
||||
@Override
|
||||
public void onExpand() {
|
||||
keepInterfaceRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
BIN
android/apps/keystore.jks
Normal file
BIN
android/apps/keystore.jks
Normal file
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
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()
|
||||
|
|
|
@ -19,24 +19,6 @@
|
|||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
|
||||
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
|
||||
<activity
|
||||
android:name=".QuestQtActivity"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:excludeFromRecents="false"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||
>
|
||||
<!-- JNI nonsense -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="questFramePlayer"/>
|
||||
<!-- Qt nonsense -->
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||
|
@ -50,6 +32,13 @@
|
|||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- JNI nonsense -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="questFramePlayer"/>
|
||||
<!-- Qt nonsense -->
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
30
android/apps/questFramePlayer/src/main/cpp/AndroidHelper.cpp
Normal file
30
android/apps/questFramePlayer/src/main/cpp/AndroidHelper.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2019/02/15
|
||||
// Copyright 2013-2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "AndroidHelper.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
AndroidHelper::AndroidHelper() {
|
||||
}
|
||||
|
||||
AndroidHelper::~AndroidHelper() {
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyLoadComplete() {
|
||||
emit qtAppLoadComplete();
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyEnterForeground() {
|
||||
emit enterForeground();
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyEnterBackground() {
|
||||
emit enterBackground();
|
||||
}
|
||||
|
43
android/apps/questFramePlayer/src/main/cpp/AndroidHelper.h
Normal file
43
android/apps/questFramePlayer/src/main/cpp/AndroidHelper.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2019/02/15
|
||||
// Copyright 2013-2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Android_Helper_h
|
||||
#define hifi_Android_Helper_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QEventLoop>
|
||||
|
||||
class AndroidHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AndroidHelper(AndroidHelper const&) = delete;
|
||||
void operator=(AndroidHelper const&) = delete;
|
||||
|
||||
static AndroidHelper& instance() {
|
||||
static AndroidHelper instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void notifyLoadComplete();
|
||||
void notifyEnterForeground();
|
||||
void notifyEnterBackground();
|
||||
|
||||
|
||||
signals:
|
||||
void qtAppLoadComplete();
|
||||
void enterForeground();
|
||||
void enterBackground();
|
||||
|
||||
private:
|
||||
AndroidHelper();
|
||||
~AndroidHelper();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -11,15 +11,11 @@
|
|||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
PlayerWindow::PlayerWindow() {
|
||||
installEventFilter(this);
|
||||
setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
|
||||
setFlags(Qt::Window);
|
||||
setSurfaceType(QSurface::OpenGLSurface);
|
||||
create();
|
||||
showFullScreen();
|
||||
// Ensure the window is visible and the GL context is valid
|
||||
QCoreApplication::processEvents();
|
||||
_renderThread.initialize(this);
|
||||
}
|
||||
|
||||
PlayerWindow::~PlayerWindow() {
|
||||
_renderThread.initialize();
|
||||
}
|
||||
|
|
|
@ -8,22 +8,13 @@
|
|||
|
||||
#pragma once
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QSettings>
|
||||
|
||||
#include <gpu/Forward.h>
|
||||
#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;
|
||||
virtual ~PlayerWindow() {}
|
||||
|
||||
private:
|
||||
QSettings _settings;
|
||||
RenderThread _renderThread;
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QFileInfo>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QImageReader>
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <gpu/FrameIO.h>
|
||||
|
@ -29,9 +30,7 @@
|
|||
#include <VrApi.h>
|
||||
#include <VrApi_Input.h>
|
||||
|
||||
static JNIEnv* _env { nullptr };
|
||||
static JavaVM* _vm { nullptr };
|
||||
static jobject _activity { nullptr };
|
||||
#include "AndroidHelper.h"
|
||||
|
||||
struct HandController{
|
||||
ovrInputTrackedRemoteCapabilities caps {};
|
||||
|
@ -48,21 +47,43 @@ struct HandController{
|
|||
};
|
||||
|
||||
std::vector<HandController> devices;
|
||||
QAndroidJniObject __interfaceActivity;
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void *) {
|
||||
__android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__);
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "QQQ", __FUNCTION__);
|
||||
__interfaceActivity = QAndroidJniObject(obj);
|
||||
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
|
||||
__interfaceActivity.callMethod<void>("onAppLoadedComplete", "()V");
|
||||
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
|
||||
env->GetJavaVM(&_vm);
|
||||
_activity = env->NewGlobalRef(obj);
|
||||
JNIEXPORT void
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().moveToThread(qApp->thread());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnPause(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().notifyEnterBackground();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnResume(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().notifyEnterForeground();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static const char* FRAME_FILE = "assets:/frames/20190121_1220.json";
|
||||
|
||||
static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
|
||||
|
@ -84,11 +105,10 @@ void RenderThread::move(const glm::vec3& v) {
|
|||
_correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
|
||||
}
|
||||
|
||||
void RenderThread::initialize(QWindow* window) {
|
||||
void RenderThread::initialize() {
|
||||
std::unique_lock<std::mutex> lock(_frameLock);
|
||||
setObjectName("RenderThread");
|
||||
Parent::initialize();
|
||||
_window = window;
|
||||
_thread->setObjectName("RenderThread");
|
||||
}
|
||||
|
||||
|
@ -96,14 +116,7 @@ void RenderThread::setup() {
|
|||
// Wait until the context has been moved to this thread
|
||||
{ std::unique_lock<std::mutex> 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();
|
||||
|
@ -169,7 +182,6 @@ void RenderThread::handleInput() {
|
|||
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 {
|
||||
|
|
|
@ -20,11 +20,9 @@
|
|||
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<gpu::Backend> _backend;
|
||||
std::atomic<size_t> _presentCount{ 0 };
|
||||
std::mutex _frameLock;
|
||||
std::queue<gpu::FramePointer> _pendingFrames;
|
||||
gpu::FramePointer _activeFrame;
|
||||
|
@ -39,6 +37,6 @@ public:
|
|||
void handleInput();
|
||||
|
||||
void submitFrame(const gpu::FramePointer& frame);
|
||||
void initialize(QWindow* window);
|
||||
void initialize();
|
||||
void renderFrame();
|
||||
};
|
||||
|
|
|
@ -11,30 +11,33 @@
|
|||
#include <QtGui/QGuiApplication>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
|
||||
#include <Trace.h>
|
||||
|
||||
#include "PlayerWindow.h"
|
||||
#include "AndroidHelper.h"
|
||||
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
if (!message.isEmpty()) {
|
||||
const char * local=message.toStdString().c_str();
|
||||
const char* local = message.toStdString().c_str();
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
__android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
|
||||
__android_log_write(ANDROID_LOG_DEBUG, "Interface", local);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
__android_log_write(ANDROID_LOG_INFO,"Interface",local);
|
||||
__android_log_write(ANDROID_LOG_INFO, "Interface", local);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
__android_log_write(ANDROID_LOG_WARN,"Interface",local);
|
||||
__android_log_write(ANDROID_LOG_WARN, "Interface", local);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
__android_log_write(ANDROID_LOG_ERROR,"Interface",local);
|
||||
__android_log_write(ANDROID_LOG_ERROR, "Interface", local);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
default:
|
||||
__android_log_write(ANDROID_LOG_FATAL,"Interface",local);
|
||||
__android_log_write(ANDROID_LOG_FATAL, "Interface", local);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
@ -46,11 +49,13 @@ int main(int argc, char** argv) {
|
|||
auto oldMessageHandler = qInstallMessageHandler(messageHandler);
|
||||
DependencyManager::set<tracing::Tracer>();
|
||||
PlayerWindow window;
|
||||
__android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec");
|
||||
QTimer::singleShot(10, []{
|
||||
__android_log_write(ANDROID_LOG_WARN, "QQQ", "notifyLoadComplete");
|
||||
AndroidHelper::instance().notifyLoadComplete();
|
||||
});
|
||||
__android_log_write(ANDROID_LOG_WARN, "QQQ", "Exec");
|
||||
app.exec();
|
||||
__android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done");
|
||||
__android_log_write(ANDROID_LOG_WARN, "QQQ", "Exec done");
|
||||
qInstallMessageHandler(oldMessageHandler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,6 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
|
16
android/apps/questInterface/CMakeLists.txt
Normal file
16
android/apps/questInterface/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
set(TARGET_NAME questInterface)
|
||||
setup_hifi_library()
|
||||
link_hifi_libraries(
|
||||
shared task networking qml
|
||||
image fbx hfm render-utils physics entities octree
|
||||
oculusMobile oculusMobilePlugin
|
||||
gl gpu ${PLATFORM_GL_BACKEND}
|
||||
)
|
||||
target_opengl()
|
||||
target_bullet()
|
||||
target_oculus_mobile()
|
||||
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/interface" "libraries/interface")
|
||||
include_directories("${CMAKE_SOURCE_DIR}/interface/src")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/plugins/hifiCodec" "libraries/hifiCodecPlugin")
|
||||
target_link_libraries(questInterface android log m interface)
|
142
android/apps/questInterface/build.gradle
Normal file
142
android/apps/questInterface/build.gradle
Normal file
|
@ -0,0 +1,142 @@
|
|||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
task renameHifiACTaskDebug() {
|
||||
doLast {
|
||||
def sourceFile = new File("${appDir}/build/intermediates/cmake/debug/obj/arm64-v8a/","libhifiCodec.so")
|
||||
def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so")
|
||||
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||
}
|
||||
}
|
||||
task renameHifiACTaskRelease(type: Copy) {
|
||||
doLast {
|
||||
def sourceFile = new File("${appDir}/build/intermediates/cmake/release/obj/arm64-v8a/","libhifiCodec.so")
|
||||
def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so")
|
||||
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "io.highfidelity.questInterface"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 28
|
||||
versionCode appVersionCode
|
||||
versionName appVersionName
|
||||
ndk { abiFilters 'arm64-v8a' }
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DHIFI_ANDROID=1',
|
||||
'-DHIFI_ANDROID_APP=questInterface',
|
||||
'-DANDROID_TOOLCHAIN=clang',
|
||||
'-DANDROID_STL=c++_shared',
|
||||
'-DCMAKE_VERBOSE_MAKEFILE=ON',
|
||||
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
|
||||
'-DRELEASE_TYPE=' + RELEASE_TYPE,
|
||||
'-DSTABLE_BUILD=' + STABLE_BUILD,
|
||||
'-DDISABLE_QML=OFF',
|
||||
'-DDISABLE_KTX_CACHE=OFF',
|
||||
'-DUSE_BREAKPAD=OFF'
|
||||
targets = ['questInterface']
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : file('../keystore.jks')
|
||||
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : 'password'
|
||||
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : 'key0'
|
||||
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : 'password'
|
||||
v2SigningEnabled false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
|
||||
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\""
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\""
|
||||
buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\""
|
||||
buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\""
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path '../../../CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
// Our asset contents depend on items produced in the CMake build
|
||||
// so our merge has to depend on the external native build
|
||||
variant.externalNativeBuildTasks.each { task ->
|
||||
variant.mergeResources.dependsOn(task)
|
||||
if (Os.isFamily(Os.FAMILY_UNIX)) {
|
||||
// FIXME
|
||||
def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
||||
def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first()
|
||||
def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first()
|
||||
runDumpSymsTask.dependsOn(task)
|
||||
variant.assemble.dependsOn(uploadDumpSymsTask)
|
||||
variant.mergeResources.dependsOn(renameHifiACTask)
|
||||
}
|
||||
}
|
||||
|
||||
variant.mergeAssets.doLast {
|
||||
def assetList = new LinkedList<String>()
|
||||
def youngestLastModified = 0
|
||||
|
||||
// Copy the compiled resources generated by the external native build
|
||||
copy {
|
||||
from new File(projectDir, "../../../interface/compiledResources")
|
||||
into outputDir
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details ->
|
||||
youngestLastModified = Math.max(youngestLastModified, details.lastModified)
|
||||
assetList.add(details.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the scripts directory
|
||||
copy {
|
||||
from new File(projectDir, "../../../scripts")
|
||||
into new File(outputDir, "scripts")
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details->
|
||||
youngestLastModified = Math.max(youngestLastModified, details.lastModified)
|
||||
assetList.add("scripts/" + details.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Write a list of files to be unpacked to the cache folder
|
||||
new File(outputDir, 'cache_assets.txt').withWriter { out ->
|
||||
out.println(Long.toString(youngestLastModified))
|
||||
assetList.each { file -> out.println(file) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
|
||||
implementation project(':oculus')
|
||||
implementation project(':qt')
|
||||
}
|
25
android/apps/questInterface/proguard-rules.pro
vendored
Normal file
25
android/apps/questInterface/proguard-rules.pro
vendored
Normal file
|
@ -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
|
49
android/apps/questInterface/src/main/AndroidManifest.xml
Normal file
49
android/apps/questInterface/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.highfidelity.questInterface"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
|
||||
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.vr.high_performance" android:required="true"/>
|
||||
|
||||
<application
|
||||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="true">
|
||||
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
|
||||
<activity
|
||||
android:name=".PermissionsChecker"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="landscape"
|
||||
android:excludeFromRecents="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.INFO" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name=".InterfaceActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
<meta-data android:name="android.app.lib_name" android:value="questInterface"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
110
android/apps/questInterface/src/main/cpp/native.cpp
Normal file
110
android/apps/questInterface/src/main/cpp/native.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include <functional>
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTextStream>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#include <QtAndroidExtras/QtAndroid>
|
||||
#include <android/log.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
#include <shared/Storage.h>
|
||||
|
||||
#include <AddressManager.h>
|
||||
#include <AndroidHelper.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include <OVR_Platform.h>
|
||||
#include <OVR_Functions_Voip.h>
|
||||
|
||||
void initOculusPlatform(JNIEnv* env, jobject obj) {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]{
|
||||
// static const char* appID = "2343652845669354";
|
||||
// if (ovr_PlatformInitializeAndroid(appID, obj, env) != ovrPlatformInitialize_Success) {
|
||||
// __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to init platform SDK");
|
||||
// return;
|
||||
// }
|
||||
// ovr_Voip_SetSystemVoipSuppressed(true);
|
||||
});
|
||||
}
|
||||
|
||||
void getClassName(JNIEnv *env, jobject obj){
|
||||
jclass cls = env->GetObjectClass(obj);
|
||||
jmethodID mid = env->GetMethodID(cls,"getClass", "()Ljava/lang/Class;");
|
||||
jobject clsObj = env->CallObjectMethod(obj, mid);
|
||||
|
||||
cls= env->GetObjectClass(clsObj);
|
||||
|
||||
mid= env->GetMethodID(cls, "getName", "()Ljava/lang/String;");
|
||||
|
||||
jstring strObj = (jstring) env->CallObjectMethod(clsObj, mid);
|
||||
|
||||
const char* str = env->GetStringUTFChars(strObj, NULL);
|
||||
|
||||
__android_log_print(ANDROID_LOG_ERROR,__FUNCTION__, "Native Class call: %s",str);
|
||||
|
||||
env->ReleaseStringUTFChars(strObj, str);
|
||||
}
|
||||
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_nativeInitOculusPlatform(JNIEnv *env, jobject obj){
|
||||
initOculusPlatform(env, obj);
|
||||
}
|
||||
QAndroidJniObject __interfaceActivity;
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "QQQ", __FUNCTION__);
|
||||
initOculusPlatform(env, obj);
|
||||
getClassName(env, obj);
|
||||
|
||||
__interfaceActivity = QAndroidJniObject(obj);
|
||||
|
||||
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
|
||||
__interfaceActivity.callMethod<void>("onAppLoadedComplete", "()V");
|
||||
|
||||
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete,
|
||||
nullptr,
|
||||
nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeAwayMode(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().toggleAwayMode();
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv* env, jobject obj) {
|
||||
AndroidHelper::instance().moveToThread(qApp->thread());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnPause(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().notifyEnterBackground();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnResume(JNIEnv *env, jobject obj) {
|
||||
AndroidHelper::instance().notifyEnterForeground();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_io_highfidelity_questInterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env,
|
||||
jobject instance,
|
||||
jboolean pluggedIn) {
|
||||
AndroidHelper::instance().notifyHeadsetOn(pluggedIn);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.highfidelity.questInterface;
|
||||
|
||||
import android.os.Bundle;
|
||||
import io.highfidelity.oculus.OculusMobileActivity;
|
||||
import io.highfidelity.utils.HifiUtils;
|
||||
|
||||
public class InterfaceActivity extends OculusMobileActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
HifiUtils.upackAssets(getAssets(), getCacheDir().getAbsolutePath());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package io.highfidelity.questInterface;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import io.highfidelity.oculus.OculusMobileActivity;
|
||||
import io.highfidelity.utils.HifiUtils;
|
||||
|
||||
public class PermissionsChecker extends Activity {
|
||||
private static final int REQUEST_PERMISSIONS = 20;
|
||||
private static final String TAG = PermissionsChecker.class.getName();
|
||||
private static final String[] REQUIRED_PERMISSIONS = new String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.CAMERA
|
||||
};
|
||||
|
||||
private static final String EXTRA_ARGS = "args";
|
||||
private String mArgs;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mArgs =(getIntent().getStringExtra(EXTRA_ARGS));
|
||||
|
||||
if(!TextUtils.isEmpty(mArgs)) {
|
||||
System.out.println("Application launched with following args: " + mArgs);
|
||||
}
|
||||
|
||||
requestAppPermissions(REQUIRED_PERMISSIONS,REQUEST_PERMISSIONS);
|
||||
}
|
||||
|
||||
public void requestAppPermissions(final String[] requestedPermissions,
|
||||
final int requestCode) {
|
||||
int permissionCheck = PackageManager.PERMISSION_GRANTED;
|
||||
boolean shouldShowRequestPermissionRationale = false;
|
||||
for (String permission : requestedPermissions) {
|
||||
permissionCheck = permissionCheck + checkSelfPermission(permission);
|
||||
shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || shouldShowRequestPermissionRationale(permission);
|
||||
}
|
||||
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
||||
System.out.println("Permission was not granted. Ask for permissions");
|
||||
if (shouldShowRequestPermissionRationale) {
|
||||
requestPermissions(requestedPermissions, requestCode);
|
||||
} else {
|
||||
requestPermissions(requestedPermissions, requestCode);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Launching the other activity..");
|
||||
launchActivityWithPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
private void launchActivityWithPermissions() {
|
||||
Intent intent= new Intent(this, InterfaceActivity.class);
|
||||
|
||||
if(!TextUtils.isEmpty(mArgs)) {
|
||||
intent.putExtra("applicationArguments", mArgs);
|
||||
}
|
||||
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
int permissionCheck = PackageManager.PERMISSION_GRANTED;
|
||||
for (int permission : grantResults) {
|
||||
permissionCheck = permissionCheck + permission;
|
||||
}
|
||||
if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) {
|
||||
launchActivityWithPermissions();
|
||||
} else if (grantResults.length > 0) {
|
||||
System.out.println("User has deliberately denied Permissions. Launching anyways");
|
||||
launchActivityWithPermissions();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--suppress AndroidUnknownAttribute -->
|
||||
<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192"
|
||||
android:width="192dp"
|
||||
android:height="192dp">
|
||||
<path
|
||||
android:pathData="M189.5 96.5A93.5 93.5 0 0 1 96 190 93.5 93.5 0 0 1 2.5 96.5 93.5 93.5 0 0 1 96 3 93.5 93.5 0 0 1 189.5 96.5Z"
|
||||
android:fillColor="#333333" />
|
||||
<path
|
||||
android:pathData="M96.2 173.1c-10.3 0 -20.4 -2.1 -29.8 -6 -9.2 -3.8 -17.3 -9.4 -24.3 -16.4 -7 -7 -12.6 -15.2 -16.4 -24.3 -4.1 -9.6 -6.2 -19.6 -6.2 -30 0 -10.3 2.1 -20.4 6 -29.8 3.8 -9.2 9.4 -17.3 16.4 -24.3 7 -7 15.2 -12.6 24.3 -16.4 9.5 -4 19.5 -6 29.8 -6 10.3 0 20.4 2.1 29.8 6 9.2 3.8 17.3 9.4 24.3 16.4 7 7 12.6 15.2 16.4 24.3 4 9.5 6 19.5 6 29.8 0 10.3 -2.1 20.4 -6 29.8 -3.8 9.2 -9.4 17.3 -16.4 24.3 -7 7 -15.2 12.6 -24.3 16.4 -9.2 4.1 -19.3 6.2 -29.6 6.2zm0 -145.3c-37.8 0 -68.6 30.8 -68.6 68.6 0 37.8 30.8 68.6 68.6 68.6 37.8 0 68.6 -30.8 68.6 -68.6 0 -37.8 -30.8 -68.6 -68.6 -68.6z"
|
||||
android:fillColor="#00b4f0" />
|
||||
<path
|
||||
android:pathData="M119.6 129l0 -53.8c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 25L79 83.8 79 64c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 54.1c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.7 -2.4 -6.9 -5.8 -8l0 -27.3 35 16.3 0 22.2c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.8 -2.4 -6.9 -5.8 -8z"
|
||||
android:fillColor="#00b4f0" />
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name" translatable="false">Interface</string>
|
||||
</resources>
|
|
@ -42,6 +42,8 @@ ext {
|
|||
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
|
||||
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
|
||||
appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
|
||||
appVersionName = RELEASE_NUMBER ?: "1.0"
|
||||
}
|
||||
|
||||
def appDir = new File(projectDir, 'apps/interface')
|
||||
|
|
|
@ -1,11 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
|
||||
ANDROID_BUILD_TYPE=release
|
||||
ANDROID_BUILD_TARGET=assembleRelease
|
||||
|
||||
if [[ "$RELEASE_TYPE" == "PR" ]]; then
|
||||
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;
|
||||
elif [[ "${STABLE_BUILD}" == "1" ]]; then
|
||||
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ;
|
||||
else
|
||||
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;
|
||||
fi
|
||||
|
||||
|
||||
# Interface build
|
||||
ANDROID_APP=interface
|
||||
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
||||
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
||||
ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX}
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET}
|
||||
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
|
||||
|
||||
# Quest Interface build
|
||||
ANDROID_APP=questInterface
|
||||
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
||||
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
||||
ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX}
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true
|
||||
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} || true
|
||||
|
||||
|
||||
|
||||
|
||||
# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK
|
||||
OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_BUILT_APK_NAME}
|
||||
# This is the APK name requested by Jenkins
|
||||
TARGET_APK=./${ANDROID_APK_NAME}
|
||||
# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk"
|
||||
cp ${OUTPUT_APK} ${TARGET_APK}
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D
|
|||
# So make sure we use VERSION_CODE consistently
|
||||
test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION
|
||||
|
||||
# PR builds don't populate STABLE_BUILD, but the release builds do, and the build
|
||||
# bash script requires it, so we need to populate it if it's not present
|
||||
test -z "$STABLE_BUILD" && export STABLE_BUILD=0
|
||||
|
||||
# FIXME figure out which of these actually need to be forwarded and which can be eliminated
|
||||
docker run \
|
||||
--rm \
|
||||
--security-opt seccomp:unconfined \
|
||||
|
@ -27,6 +32,8 @@ docker run \
|
|||
-e OAUTH_CLIENT_SECRET \
|
||||
-e OAUTH_CLIENT_ID \
|
||||
-e OAUTH_REDIRECT_URI \
|
||||
-e SHA7 \
|
||||
-e STABLE_BUILD \
|
||||
-e VERSION_CODE \
|
||||
"${DOCKER_IMAGE_NAME}" \
|
||||
sh -c "./build_android.sh"
|
||||
|
|
|
@ -73,7 +73,7 @@ RUN mkdir "$HIFI_BASE" && \
|
|||
|
||||
RUN git clone https://github.com/jherico/hifi.git && \
|
||||
cd ~/hifi && \
|
||||
git checkout feature/quest_frame_player
|
||||
git checkout quest/build
|
||||
|
||||
WORKDIR /home/jenkins/hifi
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
org.gradle.jvmargs=-Xms2g -Xmx4g
|
||||
android.debug.obsoleteApi=true
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Sat Dec 01 08:32:47 PST 2018
|
||||
#Wed Dec 19 13:46:46 PST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
|
|
|
@ -15,3 +15,7 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':qt')
|
||||
}
|
||||
|
|
|
@ -7,62 +7,72 @@
|
|||
//
|
||||
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;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
import io.highfidelity.utils.HifiUtils;
|
||||
|
||||
/**
|
||||
* Contains a native surface and forwards the activity lifecycle and surface lifecycle
|
||||
* events to the OculusMobileDisplayPlugin
|
||||
*/
|
||||
public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback {
|
||||
public class OculusMobileActivity extends QtActivity 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 native void questNativeOnCreate();
|
||||
private native void questNativeOnPause();
|
||||
private native void questNativeOnResume();
|
||||
private native void questOnAppAfterLoad();
|
||||
|
||||
private native void questNativeAwayMode();
|
||||
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");
|
||||
|
||||
if(getIntent().hasExtra("applicationArguments")){
|
||||
super.APPLICATION_PARAMETERS=getIntent().getStringExtra("applicationArguments");
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
Log.w(TAG, "QQQ onCreate");
|
||||
// Create a native surface for VR rendering (Qt GL surfaces are not suitable
|
||||
// because of the lack of fine control over the surface callbacks)
|
||||
// Forward the create message to the JNI code
|
||||
mView = new SurfaceView(this);
|
||||
setContentView(mView);
|
||||
mView.getHolder().addCallback(this);
|
||||
|
||||
// Forward the create message to the JNI code
|
||||
nativeOnCreate();
|
||||
questNativeOnCreate();
|
||||
}
|
||||
|
||||
public void onAppLoadedComplete() {
|
||||
Log.w(TAG, "QQQ Load Completed");
|
||||
runOnUiThread(() -> {
|
||||
setContentView(mView);
|
||||
questOnAppAfterLoad();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.w(TAG, "QQQ onDestroy");
|
||||
if (mSurfaceHolder != null) {
|
||||
nativeOnSurfaceChanged(null);
|
||||
}
|
||||
nativeOnDestroy();
|
||||
isPausing=false;
|
||||
super.onStop();
|
||||
nativeOnSurfaceChanged(null);
|
||||
|
||||
Log.w(TAG, "QQQ onDestroy -- SUPER onDestroy");
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
@ -70,33 +80,56 @@ public class OculusMobileActivity extends Activity implements SurfaceHolder.Call
|
|||
protected void onResume() {
|
||||
Log.w(TAG, "QQQ onResume");
|
||||
super.onResume();
|
||||
//Reconnect the global reference back to handler
|
||||
nativeOnCreate();
|
||||
|
||||
questNativeOnResume();
|
||||
nativeOnResume();
|
||||
isPausing=false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
Log.w(TAG, "QQQ onPause");
|
||||
nativeOnPause();
|
||||
super.onPause();
|
||||
|
||||
questNativeOnPause();
|
||||
nativeOnPause();
|
||||
isPausing=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop(){
|
||||
super.onStop();
|
||||
Log.w(TAG, "QQQ_ Onstop called");
|
||||
questNativeAwayMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestart() {
|
||||
super.onRestart();
|
||||
Log.w(TAG, "QQQ_ onRestart called");
|
||||
questOnAppAfterLoad();
|
||||
questNativeAwayMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.w(TAG, "QQQ surfaceCreated");
|
||||
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");
|
||||
Log.w(TAG, "QQQ_ surfaceChanged");
|
||||
nativeOnSurfaceChanged(holder.getSurface());
|
||||
mSurfaceHolder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.w(TAG, "QQQ surfaceDestroyed");
|
||||
Log.w(TAG, "QQQ_ surfaceDestroyed");
|
||||
nativeOnSurfaceChanged(null);
|
||||
mSurfaceHolder = null;
|
||||
}
|
||||
|
|
|
@ -70,9 +70,7 @@ public class QtActivity extends Activity {
|
|||
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
|
||||
private QtActivityLoader m_loader = new QtActivityLoader(this);
|
||||
|
||||
public boolean isLoading;
|
||||
public boolean keepInterfaceRunning;
|
||||
|
||||
public boolean isPausing=false;
|
||||
public QtActivity() {
|
||||
}
|
||||
|
||||
|
@ -229,10 +227,13 @@ public class QtActivity extends Activity {
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
protected void onCreateHook(Bundle savedInstanceState) {
|
||||
|
||||
m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
|
||||
m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
|
||||
m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
|
||||
m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
|
||||
|
||||
|
||||
m_loader.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
|
@ -364,7 +365,10 @@ public class QtActivity extends Activity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
QtApplication.invokeDelegate();
|
||||
|
||||
QtNative.terminateQt();
|
||||
QtNative.setActivity(null,null);
|
||||
System.exit(0);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -506,9 +510,9 @@ public class QtActivity extends Activity {
|
|||
super.onPause();
|
||||
// GC: this trick allow us to show a splash activity until Qt app finishes
|
||||
// loading
|
||||
if (!isLoading && !keepInterfaceRunning) {
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//QtApplication.invokeDelegate();
|
||||
|
||||
//TODO(Amer): looking into why this messes up pause.
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -647,13 +651,13 @@ public class QtActivity extends Activity {
|
|||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (!keepInterfaceRunning) {
|
||||
|
||||
if(!isPausing){
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
QtNative.terminateQt();
|
||||
QtNative.setActivity(null,null);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,15 +12,26 @@ project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
|
|||
// Applications
|
||||
//
|
||||
|
||||
include ':interface'
|
||||
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
|
||||
if (!getSettings().hasProperty("SUPPRESS_INTERFACE")) {
|
||||
include ':interface'
|
||||
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
|
||||
}
|
||||
|
||||
if (!getSettings().hasProperty("SUPPRESS_QUEST_INTERFACE")) {
|
||||
include ':questInterface'
|
||||
project(':questInterface').projectDir = new File(settingsDir, 'apps/questInterface')
|
||||
}
|
||||
|
||||
//
|
||||
// Test projects
|
||||
//
|
||||
|
||||
include ':framePlayer'
|
||||
project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
|
||||
if (!getSettings().hasProperty("SUPPRESS_FRAME_PLAYER")) {
|
||||
include ':framePlayer'
|
||||
project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
|
||||
}
|
||||
|
||||
include ':questFramePlayer'
|
||||
project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer')
|
||||
if (!getSettings().hasProperty("SUPPRESS_QUEST_FRAME_PLAYER")) {
|
||||
include ':questFramePlayer'
|
||||
project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer')
|
||||
}
|
||||
|
|
|
@ -64,10 +64,6 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
|||
return _pool._queue.try_pop(node);
|
||||
}
|
||||
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
static AudioMixerSlave slave;
|
||||
#endif
|
||||
|
||||
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
|
||||
_function = &AudioMixerSlave::processPackets;
|
||||
_configure = [](AudioMixerSlave& slave) {};
|
||||
|
@ -87,19 +83,9 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
|||
_begin = begin;
|
||||
_end = end;
|
||||
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
_configure(slave);
|
||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
||||
_function(slave, node);
|
||||
});
|
||||
#else
|
||||
// fill the queue
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||
#if defined(__clang__) && defined(Q_OS_LINUX)
|
||||
_queue.push(node);
|
||||
#else
|
||||
_queue.emplace(node);
|
||||
#endif
|
||||
});
|
||||
|
||||
{
|
||||
|
@ -119,17 +105,12 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
|||
}
|
||||
|
||||
assert(_queue.empty());
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) {
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
functor(slave);
|
||||
#else
|
||||
for (auto& slave : _slaves) {
|
||||
functor(*slave.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AudioMixerSlavePool::setNumThreads(int numThreads) {
|
||||
|
@ -155,9 +136,6 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) {
|
|||
void AudioMixerSlavePool::resize(int numThreads) {
|
||||
assert(_numThreads == (int)_slaves.size());
|
||||
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
|
||||
#else
|
||||
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
||||
|
||||
Lock lock(_mutex);
|
||||
|
@ -205,5 +183,4 @@ void AudioMixerSlavePool::resize(int numThreads) {
|
|||
|
||||
_numThreads = _numStarted = _numFinished = numThreads;
|
||||
assert(_numThreads == (int)_slaves.size());
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <AABox.h>
|
||||
#include <AvatarLogging.h>
|
||||
|
@ -32,6 +33,8 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
#include <TryLocker.h>
|
||||
#include "../AssignmentDynamicFactory.h"
|
||||
#include "../entities/AssignmentParentFinder.h"
|
||||
|
||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||
|
||||
|
@ -55,6 +58,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message),
|
||||
_slavePool(&_slaveSharedData)
|
||||
{
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
DependencyManager::set<AssignmentDynamicFactory>();
|
||||
|
||||
// make sure we hear about node kills so we can tell the other nodes
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||
|
||||
|
@ -69,6 +75,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
|
||||
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
|
||||
packetReceiver.registerListenerForTypes({
|
||||
PacketType::ReplicatedAvatarIdentity,
|
||||
|
@ -240,6 +248,10 @@ void AvatarMixer::start() {
|
|||
|
||||
int lockWait, nodeTransform, functor;
|
||||
|
||||
{
|
||||
_entityViewer.queryOctree();
|
||||
}
|
||||
|
||||
// Allow nodes to process any pending/queued packets across our worker threads
|
||||
{
|
||||
auto start = usecTimestampNow();
|
||||
|
@ -252,6 +264,10 @@ void AvatarMixer::start() {
|
|||
}, &lockWait, &nodeTransform, &functor);
|
||||
auto end = usecTimestampNow();
|
||||
_processQueuedAvatarDataPacketsElapsedTime += (end - start);
|
||||
|
||||
_broadcastAvatarDataLockWait += lockWait;
|
||||
_broadcastAvatarDataNodeTransform += nodeTransform;
|
||||
_broadcastAvatarDataNodeFunctor += functor;
|
||||
}
|
||||
|
||||
// process pending display names... this doesn't currently run on multiple threads, because it
|
||||
|
@ -269,6 +285,10 @@ void AvatarMixer::start() {
|
|||
}, &lockWait, &nodeTransform, &functor);
|
||||
auto end = usecTimestampNow();
|
||||
_displayNameManagementElapsedTime += (end - start);
|
||||
|
||||
_broadcastAvatarDataLockWait += lockWait;
|
||||
_broadcastAvatarDataNodeTransform += nodeTransform;
|
||||
_broadcastAvatarDataNodeFunctor += functor;
|
||||
}
|
||||
|
||||
// this is where we need to put the real work...
|
||||
|
@ -691,8 +711,11 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage
|
|||
}
|
||||
|
||||
void AvatarMixer::sendStatsPacket() {
|
||||
auto start = usecTimestampNow();
|
||||
if (!_numTightLoopFrames) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto start = usecTimestampNow();
|
||||
|
||||
QJsonObject statsObject;
|
||||
|
||||
|
@ -775,6 +798,7 @@ void AvatarMixer::sendStatsPacket() {
|
|||
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
|
||||
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
|
||||
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
|
||||
slavesAggregatObject["sent_7_averageHeroAvatars"] = TIGHT_LOOP_STAT(aggregateStats.numHeroesIncluded);
|
||||
|
||||
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
|
||||
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
|
||||
|
@ -882,13 +906,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
|
|||
void AvatarMixer::domainSettingsRequestComplete() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({
|
||||
NodeType::Agent, NodeType::EntityScriptServer,
|
||||
NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer,
|
||||
NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer
|
||||
});
|
||||
|
||||
// parse the settings to pull out the values we need
|
||||
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
|
||||
|
||||
setupEntityQuery();
|
||||
|
||||
// start our tight loop...
|
||||
start();
|
||||
}
|
||||
|
@ -939,6 +965,14 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
|
||||
}
|
||||
|
||||
{
|
||||
const QString CONNECTION_RATE = "connection_rate";
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto defaultConnectionRate = nodeList->getMaxConnectionRate();
|
||||
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate);
|
||||
nodeList->setMaxConnectionRate(connectionRate);
|
||||
}
|
||||
|
||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||
|
||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||
|
@ -976,3 +1010,58 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::setupEntityQuery() {
|
||||
_entityViewer.init();
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
_slaveSharedData.entityTree = _entityViewer.getTree();
|
||||
|
||||
// ES query: {"avatarPriority": true, "type": "Zone"}
|
||||
QJsonObject priorityZoneQuery;
|
||||
priorityZoneQuery["avatarPriority"] = true;
|
||||
priorityZoneQuery["type"] = "Zone";
|
||||
|
||||
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
|
||||
}
|
||||
|
||||
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
PacketType packetType = message->getType();
|
||||
|
||||
switch (packetType) {
|
||||
case PacketType::OctreeStats:
|
||||
{ // Ignore stats, but may have a different Entity packet appended.
|
||||
OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
|
||||
const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead();
|
||||
if (piggyBackedSizeWithHeader > 0) {
|
||||
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
|
||||
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
|
||||
memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader);
|
||||
|
||||
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
|
||||
auto newMessage = QSharedPointer<ReceivedMessage>::create(*newPacket);
|
||||
handleOctreePacket(newMessage, senderNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PacketType::EntityData:
|
||||
_entityViewer.processDatagram(*message, senderNode);
|
||||
break;
|
||||
|
||||
case PacketType::EntityErase:
|
||||
_entityViewer.processEraseMessage(*message, senderNode);
|
||||
break;
|
||||
|
||||
default:
|
||||
qCDebug(avatars) << "Unexpected packet type:" << packetType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||
DependencyManager::destroy<AssignmentParentFinder>();
|
||||
|
||||
ThreadedAssignment::aboutToFinish();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <PortableHighResolutionClock.h>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
#include "../entities/EntityTreeHeadlessViewer.h"
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
#include "AvatarMixerSlavePool.h"
|
||||
|
@ -29,6 +30,7 @@ class AvatarMixer : public ThreadedAssignment {
|
|||
Q_OBJECT
|
||||
public:
|
||||
AvatarMixer(ReceivedMessage& message);
|
||||
virtual void aboutToFinish() override;
|
||||
|
||||
static bool shouldReplicateTo(const Node& from, const Node& to) {
|
||||
return to.getType() == NodeType::DownstreamAvatarMixer &&
|
||||
|
@ -57,6 +59,7 @@ private slots:
|
|||
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void start();
|
||||
|
||||
private:
|
||||
|
@ -71,8 +74,13 @@ private:
|
|||
|
||||
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
||||
|
||||
void setupEntityQuery();
|
||||
|
||||
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
||||
|
||||
// Attach to entity tree for avatar-priority zone info.
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
|
||||
// FIXME - new throttling - use these values somehow
|
||||
float _trailingMixRatio { 0.0f };
|
||||
float _throttlingRatio { 0.0f };
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
#include <DependencyManager.h>
|
||||
#include <NodeList.h>
|
||||
#include <EntityTree.h>
|
||||
#include <ZoneEntityItem.h>
|
||||
|
||||
#include "AvatarLogging.h"
|
||||
|
||||
#include "AvatarMixerSlave.h"
|
||||
|
||||
|
@ -62,7 +66,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
|||
|
||||
switch (packet->getType()) {
|
||||
case PacketType::AvatarData:
|
||||
parseData(*packet);
|
||||
parseData(*packet, slaveSharedData);
|
||||
break;
|
||||
case PacketType::SetAvatarTraits:
|
||||
processSetTraitsMessage(*packet, slaveSharedData, *node);
|
||||
|
@ -80,7 +84,42 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
|||
return packetsProcessed;
|
||||
}
|
||||
|
||||
int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
||||
namespace {
|
||||
using std::static_pointer_cast;
|
||||
|
||||
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
|
||||
struct FindPriorityZone {
|
||||
glm::vec3 position;
|
||||
bool isInPriorityZone { false };
|
||||
float zoneVolume { std::numeric_limits<float>::max() };
|
||||
|
||||
static bool operation(const OctreeElementPointer& element, void* extraData) {
|
||||
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
|
||||
if (element->getAACube().contains(findPriorityZone->position)) {
|
||||
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
|
||||
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
|
||||
if (item->getType() == EntityTypes::Zone
|
||||
&& item->contains(findPriorityZone->position)) {
|
||||
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
|
||||
if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) {
|
||||
float volume = zoneItem->getVolumeEstimate();
|
||||
if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins
|
||||
findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED;
|
||||
findPriorityZone->zoneVolume = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return true; // Keep recursing
|
||||
} else { // Position isn't within this subspace, so end recursion.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // Close anonymous namespace.
|
||||
|
||||
int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) {
|
||||
// pull the sequence number from the data first
|
||||
uint16_t sequenceNumber;
|
||||
|
||||
|
@ -90,9 +129,33 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
|||
incrementNumOutOfOrderSends();
|
||||
}
|
||||
_lastReceivedSequenceNumber = sequenceNumber;
|
||||
glm::vec3 oldPosition = getPosition();
|
||||
|
||||
// compute the offset to the data payload
|
||||
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
||||
if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto newPosition = getPosition();
|
||||
if (newPosition != oldPosition) {
|
||||
//#define AVATAR_HERO_TEST_HACK
|
||||
#ifdef AVATAR_HERO_TEST_HACK
|
||||
{
|
||||
const static QString heroKey { "HERO" };
|
||||
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
|
||||
}
|
||||
#else
|
||||
EntityTree& entityTree = *slaveSharedData.entityTree;
|
||||
FindPriorityZone findPriorityZone { newPosition, false } ;
|
||||
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
|
||||
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
|
||||
//if (findPriorityZone.isInPriorityZone) {
|
||||
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
|
||||
//}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <AvatarData.h>
|
||||
#include "MixerAvatar.h"
|
||||
#include <AssociatedTraitValues.h>
|
||||
#include <NodeData.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
@ -45,11 +45,12 @@ public:
|
|||
using HRCTime = p_high_resolution_clock::time_point;
|
||||
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
|
||||
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
AvatarData& getAvatar() { return *_avatar; }
|
||||
const AvatarData& getAvatar() const { return *_avatar; }
|
||||
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
|
||||
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
||||
using NodeData::parseData; // Avoid clang warning about hiding.
|
||||
int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData);
|
||||
MixerAvatar& getAvatar() { return *_avatar; }
|
||||
const MixerAvatar& getAvatar() const { return *_avatar; }
|
||||
const MixerAvatar* getConstAvatarData() const { return _avatar.get(); }
|
||||
MixerAvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
||||
|
||||
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
|
||||
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
|
||||
|
@ -163,7 +164,7 @@ private:
|
|||
};
|
||||
PacketQueue _packetQueue;
|
||||
|
||||
AvatarSharedPointer _avatar { new AvatarData() };
|
||||
MixerAvatarSharedPointer _avatar { new MixerAvatar() };
|
||||
|
||||
uint16_t _lastReceivedSequenceNumber { 0 };
|
||||
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;
|
||||
|
|
|
@ -281,7 +281,34 @@ AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) {
|
|||
return box;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class SortableAvatar : public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const MixerAvatar* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
|
||||
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {
|
||||
}
|
||||
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
|
||||
float getRadius() const override {
|
||||
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
|
||||
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
|
||||
}
|
||||
uint64_t getTimestamp() const override {
|
||||
return _lastEncodeTime;
|
||||
}
|
||||
const Node* getNode() const { return _node; }
|
||||
const MixerAvatar* getAvatar() const { return _avatar; }
|
||||
|
||||
private:
|
||||
const MixerAvatar* _avatar;
|
||||
const Node* _node;
|
||||
uint64_t _lastEncodeTime;
|
||||
};
|
||||
|
||||
} // Close anonymous namespace.
|
||||
|
||||
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
|
||||
const float AVATAR_HERO_FRACTION { 0.4f };
|
||||
const Node* destinationNode = node.data();
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -293,29 +320,30 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
_stats.nodesBroadcastedTo++;
|
||||
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
|
||||
AvatarMixerClientData* destinationNodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
|
||||
|
||||
nodeData->resetInViewStats();
|
||||
destinationNodeData->resetInViewStats();
|
||||
|
||||
const AvatarData& avatar = nodeData->getAvatar();
|
||||
glm::vec3 myPosition = avatar.getClientGlobalPosition();
|
||||
const AvatarData& avatar = destinationNodeData->getAvatar();
|
||||
glm::vec3 destinationPosition = avatar.getClientGlobalPosition();
|
||||
|
||||
// reset the internal state for correct random number distribution
|
||||
distribution.reset();
|
||||
|
||||
// Estimate number to sort on number sent last frame (with min. of 20).
|
||||
const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
|
||||
const int numToSendEst = std::max(int(destinationNodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
|
||||
|
||||
// reset the number of sent avatars
|
||||
nodeData->resetNumAvatarsSentLastFrame();
|
||||
destinationNodeData->resetNumAvatarsSentLastFrame();
|
||||
|
||||
// keep track of outbound data rate specifically for avatar data
|
||||
int numAvatarDataBytes = 0;
|
||||
int identityBytesSent = 0;
|
||||
int traitBytesSent = 0;
|
||||
|
||||
// max number of avatarBytes per frame
|
||||
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||
// max number of avatarBytes per frame (13 900, typical)
|
||||
const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
@ -325,8 +353,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client
|
||||
// about avatars they've ignored or that are out of view
|
||||
bool PALIsOpen = nodeData->getRequestsDomainListData();
|
||||
bool PALWasOpen = nodeData->getPrevRequestsDomainListData();
|
||||
bool PALIsOpen = destinationNodeData->getRequestsDomainListData();
|
||||
bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData();
|
||||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
|
||||
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
|
||||
|
@ -337,36 +365,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
// compute node bounding box
|
||||
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
|
||||
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
|
||||
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
|
||||
float getRadius() const override {
|
||||
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
|
||||
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
|
||||
}
|
||||
uint64_t getTimestamp() const override {
|
||||
return _lastEncodeTime;
|
||||
}
|
||||
const Node* getNode() const { return _node; }
|
||||
|
||||
private:
|
||||
const AvatarData* _avatar;
|
||||
const Node* _node;
|
||||
uint64_t _lastEncodeTime;
|
||||
};
|
||||
AABox destinationNodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
|
||||
// prepare to sort
|
||||
const auto& cameraViews = nodeData->getViewFrustums();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
sortedAvatars.reserve(_end - _begin);
|
||||
const auto& cameraViews = destinationNodeData->getViewFrustums();
|
||||
|
||||
using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>;
|
||||
// Keep two independent queues, one for heroes and one for the riff-raff.
|
||||
enum PriorityVariants { kHero, kNonhero };
|
||||
AvatarPriorityQueue avatarPriorityQueues[2] =
|
||||
{
|
||||
{cameraViews, AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge},
|
||||
{cameraViews, AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}
|
||||
};
|
||||
|
||||
avatarPriorityQueues[kNonhero].reserve(_end - _begin);
|
||||
|
||||
for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
|
||||
Node* otherNodeRaw = (*listedNode).data();
|
||||
|
@ -376,47 +391,47 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
continue;
|
||||
}
|
||||
|
||||
auto avatarNode = otherNodeRaw;
|
||||
auto sourceAvatarNode = otherNodeRaw;
|
||||
|
||||
bool shouldIgnore = false;
|
||||
bool sendAvatar = true; // We will consider this source avatar for sending.
|
||||
// We ignore other nodes for a couple of reasons:
|
||||
// 1) ignore bubbles and ignore specific node
|
||||
// 2) the node hasn't really updated it's frame data recently, this can
|
||||
// happen if for example the avatar is connected on a desktop and sending
|
||||
// updates at ~30hz. So every 3 frames we skip a frame.
|
||||
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
assert(sourceAvatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data
|
||||
const AvatarMixerClientData* sourceAvatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceAvatarNode->getLinkedData());
|
||||
assert(sourceAvatarNodeData); // we can't have gotten here without sourceAvatarNode having valid data
|
||||
quint64 startIgnoreCalculation = usecTimestampNow();
|
||||
|
||||
// make sure we have data for this avatar, that it isn't the same node,
|
||||
// and isn't an avatar that the viewing node has ignored
|
||||
// or that has ignored the viewing node
|
||||
if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
|
||||
|| (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
|
||||
shouldIgnore = true;
|
||||
if ((destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) && !PALIsOpen)
|
||||
|| (sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
|
||||
sendAvatar = false;
|
||||
} else {
|
||||
// Check to see if the space bubble is enabled
|
||||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||
if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
// Perform the collision check between the two bounding boxes
|
||||
AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox();
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
nodeData->ignoreOther(destinationNode, avatarNode);
|
||||
shouldIgnore = !getsAnyIgnored;
|
||||
AABox sourceNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox();
|
||||
if (destinationNodeBox.touches(sourceNodeBox)) {
|
||||
destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode);
|
||||
sendAvatar = getsAnyIgnored;
|
||||
}
|
||||
}
|
||||
// Not close enough to ignore
|
||||
if (!shouldIgnore) {
|
||||
nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID());
|
||||
if (sendAvatar) {
|
||||
destinationNodeData->removeFromRadiusIgnoringSet(sourceAvatarNode->getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldIgnore) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID());
|
||||
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber();
|
||||
if (sendAvatar) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID());
|
||||
AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber();
|
||||
|
||||
// FIXME - This code does appear to be working. But it seems brittle.
|
||||
// It supports determining if the frame of data for this "other"
|
||||
|
@ -430,26 +445,28 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// or that somehow we haven't sent
|
||||
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
|
||||
++numAvatarsHeldBack;
|
||||
shouldIgnore = true;
|
||||
sendAvatar = false;
|
||||
} else if (lastSeqFromSender == 0) {
|
||||
// We have have not yet recieved any data about this avatar. Ignore it for now
|
||||
// We have have not yet received any data about this avatar. Ignore it for now
|
||||
// This is important for Agent scripts that are not avatar
|
||||
// so that they don't appear to be an avatar at the origin
|
||||
shouldIgnore = true;
|
||||
sendAvatar = false;
|
||||
} else if (lastSeqFromSender - lastSeqToReceiver > 1) {
|
||||
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening
|
||||
++numAvatarsWithSkippedFrames;
|
||||
}
|
||||
}
|
||||
|
||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||
|
||||
if (!shouldIgnore) {
|
||||
if (sendAvatar) {
|
||||
// sort this one for later
|
||||
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData();
|
||||
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID());
|
||||
const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData();
|
||||
auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID());
|
||||
|
||||
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime));
|
||||
avatarPriorityQueues[avatarNodeData->getHasPriority() ? kHero : kNonhero].push(
|
||||
SortableAvatar(avatarNodeData, sourceAvatarNode, lastEncodeTime));
|
||||
}
|
||||
|
||||
// If Avatar A's PAL WAS open but is no longer open, AND
|
||||
|
@ -459,135 +476,153 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// will be sent when it doesn't need to be (but where it _should_ be OK to send).
|
||||
// However, it's less heavy-handed than using `shouldIgnore`.
|
||||
if (PALWasOpen && !PALIsOpen &&
|
||||
(destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) ||
|
||||
avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
|
||||
(destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) ||
|
||||
sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
|
||||
// ...send a Kill Packet to Node A, instructing Node A to kill Avatar B,
|
||||
// then have Node A cleanup the killed Node B.
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
packet->write(avatarNode->getUUID().toRfc4122());
|
||||
packet->write(sourceAvatarNode->getUUID().toRfc4122());
|
||||
packet->writePrimitive(KillAvatarReason::AvatarIgnored);
|
||||
nodeList->sendPacket(std::move(packet), *destinationNode);
|
||||
nodeData->cleanupKilledNode(avatarNode->getUUID(), avatarNode->getLocalID());
|
||||
destinationNodeData->cleanupKilledNode(sourceAvatarNode->getUUID(), sourceAvatarNode->getLocalID());
|
||||
}
|
||||
|
||||
nodeData->setPrevRequestsDomainListData(PALIsOpen);
|
||||
destinationNodeData->setPrevRequestsDomainListData(PALIsOpen);
|
||||
}
|
||||
|
||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
int remainingAvatars = (int)avatarPriorityQueues[kHero].size() + (int)avatarPriorityQueues[kNonhero].size();
|
||||
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||
|
||||
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
|
||||
int avatarSpaceAvailable = avatarPacketCapacity;
|
||||
int numPacketsSent = 0;
|
||||
int numAvatarsSent = 0;
|
||||
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||
|
||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst);
|
||||
for (const auto& sortedAvatar : sortedAvatarVector) {
|
||||
const Node* otherNode = sortedAvatar.getNode();
|
||||
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
||||
// Loop over two priorities - hero avatars then everyone else:
|
||||
for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) {
|
||||
const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst);
|
||||
for (const auto& sortedAvatar : sortedAvatarVector) {
|
||||
const Node* sourceNode = sortedAvatar.getNode();
|
||||
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
||||
|
||||
assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
assert(sourceNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
|
||||
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
|
||||
|
||||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||
if (overBudget) {
|
||||
if (PALIsOpen) {
|
||||
_stats.overBudgetAvatars++;
|
||||
detail = AvatarData::PALMinimum;
|
||||
} else {
|
||||
_stats.overBudgetAvatars += remainingAvatars;
|
||||
break;
|
||||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||
if (overBudget) {
|
||||
if (PALIsOpen) {
|
||||
_stats.overBudgetAvatars++;
|
||||
detail = AvatarData::PALMinimum;
|
||||
} else {
|
||||
_stats.overBudgetAvatars += remainingAvatars;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame;
|
||||
if (overHeroBudget) {
|
||||
break; // No more heroes (this frame).
|
||||
}
|
||||
|
||||
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
|
||||
const AvatarMixerClientData* sourceNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceNode->getLinkedData());
|
||||
const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
|
||||
|
||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||
bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling?
|
||||
|
||||
if (isLowerPriority) {
|
||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
||||
destinationNodeData->incrementAvatarOutOfView();
|
||||
} else if (!overBudget) {
|
||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
destinationNodeData->incrementAvatarInView();
|
||||
|
||||
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
|
||||
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
|
||||
if (sourceAvatar->hasProcessedFirstIdentity()
|
||||
&& destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
|
||||
identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);
|
||||
|
||||
// remember the last time we sent identity details about this other node to the receiver
|
||||
destinationNodeData->setLastBroadcastTime(sourceNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
||||
QVector<JointData>& lastSentJointsForOther = destinationNodeData->getLastOtherAvatarSentJoints(sourceNode->getLocalID());
|
||||
|
||||
const bool distanceAdjust = true;
|
||||
const bool dropFaceTracking = false;
|
||||
AvatarDataPacket::SendStatus sendStatus;
|
||||
sendStatus.sendUUID = true;
|
||||
|
||||
do {
|
||||
auto startSerialize = chrono::high_resolution_clock::now();
|
||||
QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
sendStatus, dropFaceTracking, distanceAdjust, destinationPosition,
|
||||
&lastSentJointsForOther, avatarSpaceAvailable);
|
||||
auto endSerialize = chrono::high_resolution_clock::now();
|
||||
_stats.toByteArrayElapsedTime +=
|
||||
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||
|
||||
avatarPacket->write(bytes);
|
||||
avatarSpaceAvailable -= bytes.size();
|
||||
numAvatarDataBytes += bytes.size();
|
||||
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
|
||||
// Weren't able to fit everything.
|
||||
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
||||
++numPacketsSent;
|
||||
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||
avatarSpaceAvailable = avatarPacketCapacity;
|
||||
}
|
||||
} while (!sendStatus);
|
||||
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
if (sourceAvatar->getHasPriority()) {
|
||||
_stats.numHeroesIncluded++;
|
||||
}
|
||||
|
||||
// increment the number of avatars sent to this receiver
|
||||
destinationNodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
destinationNodeData->setLastBroadcastSequenceNumber(sourceNode->getLocalID(),
|
||||
sourceNodeData->getLastReceivedSequenceNumber());
|
||||
destinationNodeData->setLastOtherAvatarEncodeTime(sourceNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
_stats.avatarDataPackingElapsedTime +=
|
||||
(quint64)chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||
|
||||
if (!overBudget) {
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(destinationNodeData, sourceNodeData, *traitsPacketList);
|
||||
}
|
||||
numAvatarsSent++;
|
||||
remainingAvatars--;
|
||||
}
|
||||
|
||||
if (currentVariant == kHero) { // Dump any remaining heroes into the commoners.
|
||||
for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) {
|
||||
avatarPriorityQueues[kNonhero].push(*avIter);
|
||||
}
|
||||
}
|
||||
|
||||
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData();
|
||||
|
||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
|
||||
|
||||
if (isLowerPriority) {
|
||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
||||
nodeData->incrementAvatarOutOfView();
|
||||
} else if (!overBudget) {
|
||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
nodeData->incrementAvatarInView();
|
||||
|
||||
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO
|
||||
// the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
|
||||
if (otherAvatar->hasProcessedFirstIdentity()
|
||||
&& nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) {
|
||||
identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode);
|
||||
|
||||
// remember the last time we sent identity details about this other node to the receiver
|
||||
nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID());
|
||||
|
||||
const bool distanceAdjust = true;
|
||||
const bool dropFaceTracking = false;
|
||||
AvatarDataPacket::SendStatus sendStatus;
|
||||
sendStatus.sendUUID = true;
|
||||
|
||||
do {
|
||||
auto startSerialize = chrono::high_resolution_clock::now();
|
||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
sendStatus, dropFaceTracking, distanceAdjust, myPosition,
|
||||
&lastSentJointsForOther, avatarSpaceAvailable);
|
||||
auto endSerialize = chrono::high_resolution_clock::now();
|
||||
_stats.toByteArrayElapsedTime +=
|
||||
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||
|
||||
avatarPacket->write(bytes);
|
||||
avatarSpaceAvailable -= bytes.size();
|
||||
numAvatarDataBytes += bytes.size();
|
||||
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
|
||||
// Weren't able to fit everything.
|
||||
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
||||
++numPacketsSent;
|
||||
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||
avatarSpaceAvailable = avatarPacketCapacity;
|
||||
}
|
||||
} while (!sendStatus);
|
||||
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
|
||||
// increment the number of avatars sent to this receiver
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
_stats.avatarDataPackingElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||
|
||||
if (!overBudget) {
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
}
|
||||
|
||||
remainingAvatars--;
|
||||
}
|
||||
|
||||
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
|
||||
qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame()
|
||||
if (destinationNodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
|
||||
qCWarning(avatars) << "More avatars sent than upper estimate" << destinationNodeData->getNumAvatarsSentLastFrame()
|
||||
<< " / " << numToSendEst;
|
||||
}
|
||||
|
||||
|
@ -618,12 +653,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
}
|
||||
|
||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||
nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
|
||||
destinationNodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
|
||||
|
||||
|
||||
// record the number of avatars held back this frame
|
||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||
destinationNodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||
destinationNodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||
|
||||
quint64 endPacketSending = usecTimestampNow();
|
||||
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
int numIdentityPacketsSent { 0 };
|
||||
int numOthersIncluded { 0 };
|
||||
int overBudgetAvatars { 0 };
|
||||
int numHeroesIncluded { 0 };
|
||||
|
||||
quint64 ignoreCalculationElapsedTime { 0 };
|
||||
quint64 avatarDataPackingElapsedTime { 0 };
|
||||
|
@ -57,6 +58,7 @@ public:
|
|||
numIdentityPacketsSent = 0;
|
||||
numOthersIncluded = 0;
|
||||
overBudgetAvatars = 0;
|
||||
numHeroesIncluded = 0;
|
||||
|
||||
ignoreCalculationElapsedTime = 0;
|
||||
avatarDataPackingElapsedTime = 0;
|
||||
|
@ -80,6 +82,7 @@ public:
|
|||
numIdentityPacketsSent += rhs.numIdentityPacketsSent;
|
||||
numOthersIncluded += rhs.numOthersIncluded;
|
||||
overBudgetAvatars += rhs.overBudgetAvatars;
|
||||
numHeroesIncluded += rhs.numHeroesIncluded;
|
||||
|
||||
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
|
||||
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
|
||||
|
@ -90,9 +93,13 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class EntityTree;
|
||||
using EntityTreePointer = std::shared_ptr<EntityTree>;
|
||||
|
||||
struct SlaveSharedData {
|
||||
QStringList skeletonURLWhitelist;
|
||||
QUrl skeletonReplacementURL;
|
||||
EntityTreePointer entityTree;
|
||||
};
|
||||
|
||||
class AvatarMixerSlave {
|
||||
|
|
|
@ -63,10 +63,6 @@ bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) {
|
|||
return _pool._queue.try_pop(node);
|
||||
}
|
||||
|
||||
#ifdef AVATAR_SINGLE_THREADED
|
||||
static AvatarMixerSlave slave;
|
||||
#endif
|
||||
|
||||
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
|
||||
_function = &AvatarMixerSlave::processIncomingPackets;
|
||||
_configure = [=](AvatarMixerSlave& slave) {
|
||||
|
@ -89,19 +85,9 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
|||
_begin = begin;
|
||||
_end = end;
|
||||
|
||||
#ifdef AUDIO_SINGLE_THREADED
|
||||
_configure(slave);
|
||||
std::for_each(begin, end, [&](const SharedNodePointer& node) {
|
||||
_function(slave, node);
|
||||
});
|
||||
#else
|
||||
// fill the queue
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||
#if defined(__clang__) && defined(Q_OS_LINUX)
|
||||
_queue.push(node);
|
||||
#else
|
||||
_queue.emplace(node);
|
||||
#endif
|
||||
});
|
||||
|
||||
{
|
||||
|
@ -121,18 +107,13 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
|
|||
}
|
||||
|
||||
assert(_queue.empty());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
|
||||
#ifdef AVATAR_SINGLE_THREADED
|
||||
functor(slave);
|
||||
#else
|
||||
for (auto& slave : _slaves) {
|
||||
functor(*slave.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
|
||||
|
@ -158,9 +139,6 @@ void AvatarMixerSlavePool::setNumThreads(int numThreads) {
|
|||
void AvatarMixerSlavePool::resize(int numThreads) {
|
||||
assert(_numThreads == (int)_slaves.size());
|
||||
|
||||
#ifdef AVATAR_SINGLE_THREADED
|
||||
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
|
||||
#else
|
||||
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
|
||||
|
||||
Lock lock(_mutex);
|
||||
|
@ -208,5 +186,4 @@ void AvatarMixerSlavePool::resize(int numThreads) {
|
|||
|
||||
_numThreads = _numStarted = _numFinished = numThreads;
|
||||
assert(_numThreads == (int)_slaves.size());
|
||||
#endif
|
||||
}
|
||||
|
|
31
assignment-client/src/avatars/MixerAvatar.h
Normal file
31
assignment-client/src/avatars/MixerAvatar.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// MixerAvatar.h
|
||||
// assignment-client/src/avatars
|
||||
//
|
||||
// Created by Simon Walton Feb 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
|
||||
//
|
||||
|
||||
// Avatar class for use within the avatar mixer - encapsulates data required only for
|
||||
// sorting priorities within the mixer.
|
||||
|
||||
#ifndef hifi_MixerAvatar_h
|
||||
#define hifi_MixerAvatar_h
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
class MixerAvatar : public AvatarData {
|
||||
public:
|
||||
bool getHasPriority() const { return _hasPriority; }
|
||||
void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; }
|
||||
|
||||
private:
|
||||
bool _hasPriority { false };
|
||||
};
|
||||
|
||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||
|
||||
#endif // hifi_MixerAvatar_h
|
|
@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// we need to ask the DS about agents so we can ping/reply with them
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer,
|
||||
NodeType::AvatarMixer });
|
||||
|
||||
beforeRun(); // after payload has been processed
|
||||
|
||||
|
|
|
@ -270,6 +270,16 @@ macro(AUTOSCRIBE_SHADER_LIBS)
|
|||
set(AUTOSCRIBE_SHADERGEN_COMMANDS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shadergen.txt)
|
||||
file(WRITE ${AUTOSCRIBE_SHADERGEN_COMMANDS_FILE} "${AUTOSCRIBE_SHADERGEN_COMMANDS}")
|
||||
|
||||
if (HIFI_ANDROID)
|
||||
if (
|
||||
(${HIFI_ANDROID_APP} STREQUAL "questInterface") OR
|
||||
(${HIFI_ANDROID_APP} STREQUAL "questFramePlayer") OR
|
||||
(${HIFI_ANDROID_APP} STREQUAL "framePlayer")
|
||||
)
|
||||
set(EXTRA_SHADERGEN_ARGS --extensions EXT_clip_cull_distance)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# A custom python script which will generate all our shader artifacts
|
||||
add_custom_command(
|
||||
OUTPUT ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS}
|
||||
|
@ -279,6 +289,7 @@ macro(AUTOSCRIBE_SHADER_LIBS)
|
|||
--tools-dir ${VCPKG_TOOLS_DIR}
|
||||
--build-dir ${CMAKE_CURRENT_BINARY_DIR}
|
||||
--source-dir ${CMAKE_SOURCE_DIR}
|
||||
${EXTRA_SHADERGEN_ARGS}
|
||||
DEPENDS ${AUTOSCRIBE_SHADER_HEADERS} ${CMAKE_SOURCE_DIR}/tools/shadergen.py ${ALL_SCRIBE_SHADERS})
|
||||
|
||||
add_custom_target(shadergen DEPENDS ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS})
|
||||
|
|
|
@ -1302,6 +1302,14 @@
|
|||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "connection_rate",
|
||||
"label": "Connection Rate",
|
||||
"help": "Number of new agents that can connect to the mixer every second",
|
||||
"placeholder": "50",
|
||||
"default": "50",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1243,12 +1243,11 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
|
|||
|
||||
limitedNodeList->eachMatchingNode(
|
||||
[this, addedNode](const SharedNodePointer& node)->bool {
|
||||
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
|
||||
// is the added Node in this node's interest list?
|
||||
return isInInterestSet(node, addedNode);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// is the added Node in this node's interest list?
|
||||
return node->getLinkedData()
|
||||
&& node->getActiveSocket()
|
||||
&& node != addedNode
|
||||
&& isInInterestSet(node, addedNode);
|
||||
},
|
||||
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
|
||||
// send off this packet to the node
|
||||
|
@ -2548,7 +2547,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
|
|||
_pendingFileContent.seek(_pendingFileContent.size());
|
||||
_pendingFileContent.write(dataChunk);
|
||||
_pendingFileContent.close();
|
||||
|
||||
|
||||
// Respond immediately - will timeout if we wait for restore.
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <AvatarData.h>
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <NLPacketList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingHelpers.h>
|
||||
#include <AvatarData.h> //for KillAvatarReason
|
||||
#include <FingerprintUtils.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -870,14 +870,6 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
|
|||
}
|
||||
}
|
||||
|
||||
// if we are here, then we kicked them, so send the KillAvatar message
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
packet->write(nodeUUID.toRfc4122());
|
||||
packet->writePrimitive(KillAvatarReason::NoReason);
|
||||
|
||||
// send to avatar mixer, it sends the kill to everyone else
|
||||
limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
|
||||
|
||||
if (newPermissions) {
|
||||
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
|
||||
<< "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID());
|
||||
|
|
|
@ -53,9 +53,9 @@ ANDROID_PACKAGES = {
|
|||
'includeLibs': ['libvrapi.so']
|
||||
},
|
||||
'oculusPlatform': {
|
||||
'file': 'OVRPlatformSDK_v1.32.0.zip',
|
||||
'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL',
|
||||
'checksum': 'ab5b203b3a39a56ab148d68fff769e05',
|
||||
'file': 'OVRPlatformSDK_v1.34.0.zip',
|
||||
'versionId': 'vbRUkkyzUAXfTGSEtuiUr_7.Fm5h5BZk',
|
||||
'checksum': '16e4c5f39520f122bc49cb6d5bb88289',
|
||||
'sharedLibFolder': 'Android/libs/arm64-v8a',
|
||||
'includeLibs': ['libovrplatformloader.so']
|
||||
},
|
||||
|
|
Binary file not shown.
|
@ -1016,8 +1016,8 @@
|
|||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/run_fwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 21.0,
|
||||
"startFrame": 1.0,
|
||||
"endFrame": 22.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
|
|
2229
interface/resources/avatar/avatar-animation_withSplineIKNode.json
Normal file
2229
interface/resources/avatar/avatar-animation_withSplineIKNode.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,9 @@
|
|||
"invert"
|
||||
],
|
||||
"to": "Actions.Pitch"
|
||||
}
|
||||
},
|
||||
|
||||
{ "from": "TouchscreenVirtualPad.RB", "to": "Standard.RB"}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
BIN
interface/resources/fonts/rawline-500.ttf
Normal file
BIN
interface/resources/fonts/rawline-500.ttf
Normal file
Binary file not shown.
BIN
interface/resources/images/handshake.png
Normal file
BIN
interface/resources/images/handshake.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -425,11 +425,12 @@ FocusScope {
|
|||
console.warn("Could not find top level window for " + item);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (typeof Controller === "undefined") {
|
||||
console.warn("Controller not yet available... can't center");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect();
|
||||
var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
|
||||
|
@ -455,15 +456,17 @@ FocusScope {
|
|||
console.warn("Could not find top level window for " + item);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (typeof Controller === "undefined") {
|
||||
console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
var oldRecommendedRect = recommendedRect;
|
||||
var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
|
||||
var newRecommendedRect = Controller.getRecommendedHUDRect();
|
||||
var newRecommendedRect = { width: 1280, height: 720, x: 0, y: 0 };
|
||||
if (typeof Controller !== "undefined") newRecommendedRect = Controller.getRecommendedHUDRect();
|
||||
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
|
||||
repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
|
||||
}
|
||||
|
@ -480,7 +483,8 @@ FocusScope {
|
|||
return;
|
||||
}
|
||||
|
||||
var recommended = Controller.getRecommendedHUDRect();
|
||||
var recommended = { width: 1280, height: 720, x: 0, y: 0 };
|
||||
if (typeof Controller !== "undefined") recommended = Controller.getRecommendedHUDRect();
|
||||
var maxX = recommended.x + recommended.width;
|
||||
var maxY = recommended.y + recommended.height;
|
||||
var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y);
|
||||
|
|
|
@ -273,11 +273,7 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = null;
|
||||
root.canceled();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,11 +295,7 @@ ModalWindow {
|
|||
}
|
||||
root.result = JSON.stringify(result);
|
||||
root.selected(root.result);
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -815,7 +815,7 @@ ModalWindow {
|
|||
Action {
|
||||
id: cancelAction
|
||||
text: "Cancel"
|
||||
onTriggered: { canceled(); root.shown = false; }
|
||||
onTriggered: { canceled(); root.destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -168,11 +168,7 @@ ModalWindow {
|
|||
shortcut: "Esc"
|
||||
onTriggered: {
|
||||
root.canceled();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,11 +179,7 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = items ? comboBox.currentText : textResult.text
|
||||
root.selected(root.result);
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ OriginalDesktop.Desktop {
|
|||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
scrollGestureEnabled: false // we don't need/want these
|
||||
onEntered: ApplicationCompositor.reticleOverDesktop = true
|
||||
onExited: ApplicationCompositor.reticleOverDesktop = false
|
||||
onEntered: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = true
|
||||
onExited: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = false
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
|
|
|
@ -213,6 +213,63 @@ Item {
|
|||
popup.open();
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: errorsGlyph
|
||||
visible: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||
text: hifi.glyphs.alert
|
||||
size: 315
|
||||
color: "#EA4C5F"
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -30
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: successGlyph
|
||||
visible: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 52
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
width: 149.6
|
||||
height: 149
|
||||
source: "../../../icons/checkmark-stroke.svg"
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: doctorStatusMessage
|
||||
|
||||
states: [
|
||||
State {
|
||||
when: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||
name: "noErrors"
|
||||
PropertyChanges {
|
||||
target: doctorStatusMessage
|
||||
text: "Your avatar looks fine."
|
||||
}
|
||||
},
|
||||
State {
|
||||
when: !AvatarPackagerCore.currentAvatarProject || AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||
name: "errors"
|
||||
PropertyChanges {
|
||||
target: doctorStatusMessage
|
||||
text: "Your avatar has a few issues."
|
||||
}
|
||||
}
|
||||
]
|
||||
color: 'white'
|
||||
size: 20
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: errorsGlyph.bottom
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: infoMessage
|
||||
|
||||
|
@ -240,7 +297,7 @@ Item {
|
|||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.top: doctorStatusMessage.bottom
|
||||
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
|
@ -249,6 +306,53 @@ Item {
|
|||
text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users."
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: notForSaleMessage
|
||||
|
||||
visible: root.hasSuccessfullyUploaded
|
||||
|
||||
color: 'white'
|
||||
linkColor: '#00B4EF'
|
||||
size: 20
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: infoMessage.bottom
|
||||
anchors.topMargin: 10
|
||||
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
text: "This item is not for sale yet, <a href='#'>learn more</a>."
|
||||
|
||||
onLinkActivated: {
|
||||
Qt.openUrlExternally("https://docs.highfidelity.com/sell/add-item/upload-avatar.html");
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: showErrorsLink
|
||||
|
||||
color: 'white'
|
||||
linkColor: '#00B4EF'
|
||||
|
||||
visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.hasErrors
|
||||
|
||||
anchors {
|
||||
top: notForSaleMessage.bottom
|
||||
topMargin: 16
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
size: 28
|
||||
|
||||
text: "<a href='toggle'>View all errors</a>"
|
||||
|
||||
onLinkActivated: {
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport;
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: openFolderButton
|
||||
|
||||
|
|
|
@ -33,15 +33,19 @@ Rectangle {
|
|||
property bool balanceReceived: false;
|
||||
property bool availableUpdatesReceived: false;
|
||||
property bool itemInfoReceived: false;
|
||||
property bool dataReady: itemInfoReceived && ownershipStatusReceived && balanceReceived && availableUpdatesReceived;
|
||||
property string baseItemName: "";
|
||||
property string itemName;
|
||||
property string itemId;
|
||||
property string itemHref;
|
||||
property string itemAuthor;
|
||||
property int itemEdition: -1;
|
||||
property bool hasSomethingToTradeIn: itemEdition > 0; // i.e., don't trade in your artist's proof
|
||||
property bool isTradingIn: canUpdate && hasSomethingToTradeIn;
|
||||
property bool isStocking: (availability === 'not for sale') && (creator === Account.username) && !updated_item_id;
|
||||
property string certificateId;
|
||||
property double balanceAfterPurchase;
|
||||
property bool alreadyOwned: false;
|
||||
property bool alreadyOwned: false; // Including proofs
|
||||
property int itemPrice: -1;
|
||||
property bool isCertified;
|
||||
property string itemType: "unknown";
|
||||
|
@ -55,7 +59,10 @@ Rectangle {
|
|||
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
|
||||
property string referrer;
|
||||
property bool isInstalled;
|
||||
property bool isUpdating;
|
||||
property bool canUpdate;
|
||||
property string availability: "available";
|
||||
property string updated_item_id: "";
|
||||
property string creator: "";
|
||||
property string baseAppURL;
|
||||
property int currentUpdatesPage: 1;
|
||||
// Style
|
||||
|
@ -138,6 +145,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
// Answers the updatable original item cert data still owned by this user that are EITHER instances of this marketplace id, or update to this marketplace id.
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Available Updates", result.data.message);
|
||||
} else {
|
||||
|
@ -149,7 +157,7 @@ Rectangle {
|
|||
if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) {
|
||||
continue;
|
||||
}
|
||||
root.isUpdating = true;
|
||||
root.canUpdate = true;
|
||||
root.baseItemName = result.data.updates[i].base_item_title;
|
||||
// This CertID is the one corresponding to the base item CertID that the user already owns
|
||||
root.certificateId = result.data.updates[i].certificate_id;
|
||||
|
@ -160,7 +168,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
if (result.data.updates.length === 0 || root.isUpdating) {
|
||||
if (result.data.updates.length === 0 || root.canUpdate) {
|
||||
root.availableUpdatesReceived = true;
|
||||
refreshBuyUI();
|
||||
} else {
|
||||
|
@ -172,7 +180,7 @@ Rectangle {
|
|||
|
||||
onUpdateItemResult: {
|
||||
if (result.status !== 'success') {
|
||||
failureErrorText.text = result.message;
|
||||
failureErrorText.text = result.data ? (result.data.message || "Unknown Error") : JSON.stringify(result);
|
||||
root.activeView = "checkoutFailure";
|
||||
} else {
|
||||
root.itemHref = result.data.download_url;
|
||||
|
@ -260,13 +268,6 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
enabled: titleBarContainer.usernameDropdownVisible;
|
||||
anchors.fill: parent;
|
||||
onClicked: {
|
||||
titleBarContainer.usernameDropdownVisible = false;
|
||||
}
|
||||
}
|
||||
//
|
||||
// TITLE BAR END
|
||||
//
|
||||
|
@ -434,7 +435,7 @@ Rectangle {
|
|||
anchors.top: parent.top;
|
||||
anchors.left: itemPreviewImage.right;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.right: itemPriceContainer.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 8;
|
||||
height: 30;
|
||||
// Style
|
||||
|
@ -449,21 +450,22 @@ Rectangle {
|
|||
Item {
|
||||
id: itemPriceContainer;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
anchors.top: itemNameText.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: itemNameText.left;
|
||||
height: 30;
|
||||
width: itemPriceTextLabel.width + itemPriceText.width + 20;
|
||||
width: itemPriceText.width + 20;
|
||||
|
||||
// "HFC" balance label
|
||||
// "HFC" label
|
||||
HiFiGlyphs {
|
||||
id: itemPriceTextLabel;
|
||||
visible: !(root.isUpdating && root.itemEdition > 0) && (root.itemPrice > 0);
|
||||
visible: !isTradingIn && (root.itemPrice > 0);
|
||||
text: hifi.glyphs.hfc;
|
||||
// Size
|
||||
size: 30;
|
||||
// Anchors
|
||||
anchors.right: itemPriceText.left;
|
||||
anchors.rightMargin: 4;
|
||||
anchors.right: parent.right;
|
||||
//anchors.rightMargin: 4;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 0;
|
||||
width: paintedWidth;
|
||||
|
@ -473,13 +475,15 @@ Rectangle {
|
|||
}
|
||||
FiraSansSemiBold {
|
||||
id: itemPriceText;
|
||||
text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : ((root.itemPrice > 0) ? root.itemPrice : "FREE"));
|
||||
text: isTradingIn ? "FREE\nUPDATE" :
|
||||
(isStocking ? "Free for creator" :
|
||||
((root.itemPrice === -1) ? "--" : ((root.itemPrice > 0) ? root.itemPrice : "FREE")));
|
||||
// Text size
|
||||
size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26;
|
||||
size: isTradingIn ? 20 : 26;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
anchors.left: itemPriceTextLabel.visible ? itemPriceTextLabel.right : parent.left;
|
||||
anchors.leftMargin: 4;
|
||||
width: paintedWidth;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
|
@ -571,7 +575,7 @@ Rectangle {
|
|||
// "View in Inventory" button
|
||||
HifiControlsUit.Button {
|
||||
id: viewInMyPurchasesButton;
|
||||
visible: false;
|
||||
visible: isCertified && dataReady && (isTradingIn ? hasSomethingToTradeIn : alreadyOwned);
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top;
|
||||
|
@ -579,9 +583,9 @@ Rectangle {
|
|||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY";
|
||||
text: (canUpdate && !isTradingIn) ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY";
|
||||
onClicked: {
|
||||
if (root.isUpdating) {
|
||||
if (root.canUpdate) {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName});
|
||||
} else {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
|
||||
|
@ -592,8 +596,12 @@ Rectangle {
|
|||
// "Buy" button
|
||||
HifiControlsUit.Button {
|
||||
id: buyButton;
|
||||
visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible)
|
||||
enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified) || root.isUpdating;
|
||||
visible: isTradingIn || !alreadyOwned || isStocking || !(root.itemType === "avatar" || root.itemType === "app");
|
||||
property bool checkBalance: dataReady && (root.availability === "available")
|
||||
enabled: (checkBalance && (balanceAfterPurchase >= 0)) || !isCertified || isTradingIn || isStocking;
|
||||
text: isTradingIn ? "Confirm Update" :
|
||||
(enabled ? (viewInMyPurchasesButton.visible ? "Get It Again" : (dataReady ? "Get Item" : "--")) :
|
||||
(checkBalance ? "Insufficient Funds" : availability))
|
||||
color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
|
||||
|
@ -602,10 +610,8 @@ Rectangle {
|
|||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ?
|
||||
((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Get It Again" : "Confirm") : "--") : "Get Item"));
|
||||
onClicked: {
|
||||
if (root.isUpdating && root.itemEdition > 0) {
|
||||
if (isTradingIn) {
|
||||
// If we're updating an app, the existing app needs to be uninstalled.
|
||||
// This call will fail/return `false` if the app isn't installed, but that's OK.
|
||||
if (root.itemType === "app") {
|
||||
|
@ -1063,7 +1069,11 @@ Rectangle {
|
|||
buyButton.color = hifi.buttons.red;
|
||||
root.shouldBuyWithControlledFailure = true;
|
||||
} else {
|
||||
buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
|
||||
buyButton.text = (root.isCertified ?
|
||||
(dataReady ?
|
||||
(root.alreadyOwned ? "Buy Another" : "Buy") :
|
||||
"--") :
|
||||
"Get Item");
|
||||
buyButton.color = hifi.buttons.blue;
|
||||
root.shouldBuyWithControlledFailure = false;
|
||||
}
|
||||
|
@ -1091,6 +1101,9 @@ Rectangle {
|
|||
root.itemPrice = result.data.cost;
|
||||
root.itemAuthor = result.data.creator;
|
||||
root.itemType = result.data.item_type || "unknown";
|
||||
root.availability = result.data.availability;
|
||||
root.updated_item_id = result.data.updated_item_id || ""
|
||||
root.creator = result.data.creator;
|
||||
if (root.itemType === "unknown") {
|
||||
root.itemHref = result.data.review_url;
|
||||
} else {
|
||||
|
@ -1139,7 +1152,7 @@ Rectangle {
|
|||
signal sendToScript(var message);
|
||||
|
||||
function canBuyAgain() {
|
||||
return (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown");
|
||||
return root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown" || isStocking;
|
||||
}
|
||||
|
||||
function handleContentSets() {
|
||||
|
@ -1185,29 +1198,23 @@ Rectangle {
|
|||
|
||||
function refreshBuyUI() {
|
||||
if (root.isCertified) {
|
||||
if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) {
|
||||
if (dataReady) {
|
||||
buyText.text = "";
|
||||
|
||||
// If the user IS on the checkout page for the updated version of an owned item...
|
||||
if (root.isUpdating) {
|
||||
if (root.canUpdate) {
|
||||
// If the user HAS already selected a specific edition to update...
|
||||
if (root.itemEdition > 0) {
|
||||
if (hasSomethingToTradeIn) {
|
||||
buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it.";
|
||||
buyTextContainer.color = "#FFFFFF";
|
||||
buyTextContainer.border.color = "#FFFFFF";
|
||||
// Else if the user HAS NOT selected a specific edition to update...
|
||||
} else {
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
|
||||
handleBuyAgainLogic();
|
||||
}
|
||||
// If the user IS NOT on the checkout page for the updated verison of an owned item...
|
||||
// (i.e. they are checking out an item "normally")
|
||||
} else {
|
||||
if (root.alreadyOwned) {
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
}
|
||||
|
||||
handleBuyAgainLogic();
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -41,6 +41,7 @@ Rectangle {
|
|||
property string searchScopeString: "Featured"
|
||||
property bool isLoggedIn: false
|
||||
property bool supports3DHTML: true
|
||||
property bool pendingGetMarketplaceItemCall: false
|
||||
|
||||
anchors.fill: (typeof parent === undefined) ? undefined : parent
|
||||
|
||||
|
@ -100,7 +101,9 @@ Rectangle {
|
|||
getMarketplaceItems();
|
||||
}
|
||||
onGetMarketplaceItemsResult: {
|
||||
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
|
||||
if (!pendingGetMarketplaceItemCall) {
|
||||
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
|
||||
}
|
||||
}
|
||||
|
||||
onGetMarketplaceItemResult: {
|
||||
|
@ -112,7 +115,7 @@ Rectangle {
|
|||
marketplaceItem.image_url = result.data.thumbnail_url;
|
||||
marketplaceItem.name = result.data.title;
|
||||
marketplaceItem.likes = result.data.likes;
|
||||
if(result.data.has_liked !== undefined) {
|
||||
if (result.data.has_liked !== undefined) {
|
||||
marketplaceItem.liked = result.data.has_liked;
|
||||
}
|
||||
marketplaceItem.creator = result.data.creator;
|
||||
|
@ -121,11 +124,13 @@ Rectangle {
|
|||
marketplaceItem.description = result.data.description;
|
||||
marketplaceItem.attributions = result.data.attributions;
|
||||
marketplaceItem.license = result.data.license;
|
||||
marketplaceItem.available = result.data.availability === "available";
|
||||
marketplaceItem.availability = result.data.availability;
|
||||
marketplaceItem.updated_item_id = result.data.updated_item_id || "";
|
||||
marketplaceItem.created_at = result.data.created_at;
|
||||
marketplaceItemScrollView.contentHeight = marketplaceItemContent.height;
|
||||
itemsList.visible = false;
|
||||
marketplaceItemView.visible = true;
|
||||
pendingGetMarketplaceItemCall = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -539,7 +544,7 @@ Rectangle {
|
|||
creator: model.creator
|
||||
category: model.primary_category
|
||||
price: model.cost
|
||||
available: model.availability === "available"
|
||||
availability: model.availability
|
||||
isLoggedIn: root.isLoggedIn;
|
||||
|
||||
onShowItem: {
|
||||
|
@ -711,7 +716,7 @@ Rectangle {
|
|||
topMargin: 10;
|
||||
leftMargin: 15;
|
||||
}
|
||||
height: visible ? childrenRect.height : 0
|
||||
height: visible ? 36 : 0
|
||||
|
||||
RalewayRegular {
|
||||
id: sortText
|
||||
|
@ -733,8 +738,9 @@ Rectangle {
|
|||
top: parent.top
|
||||
leftMargin: 20
|
||||
}
|
||||
|
||||
width: root.isLoggedIn ? 342 : 262
|
||||
height: 36
|
||||
height: parent.height
|
||||
|
||||
radius: 4
|
||||
border.width: 1
|
||||
|
@ -978,7 +984,6 @@ Rectangle {
|
|||
xhr.open("GET", url);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||
console.log(xhr.responseText);
|
||||
licenseText.text = xhr.responseText;
|
||||
licenseInfo.visible = true;
|
||||
}
|
||||
|
@ -1223,6 +1228,7 @@ Rectangle {
|
|||
console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!");
|
||||
return;
|
||||
}
|
||||
pendingGetMarketplaceItemCall = true;
|
||||
marketplaceItem.edition = message.params.edition ? message.params.edition : -1;
|
||||
MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId);
|
||||
break;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// MarketplaceListItem.qml
|
||||
// qml/hifi/commerce/marketplace
|
||||
//
|
||||
// MarketplaceListItem
|
||||
// MarketplaceItem
|
||||
//
|
||||
// Created by Roxanne Skelly on 2019-01-22
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
|
@ -33,16 +33,16 @@ Rectangle {
|
|||
property string creator: ""
|
||||
property var categories: []
|
||||
property int price: 0
|
||||
property string availability: "unknown"
|
||||
property string updated_item_id: ""
|
||||
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();
|
||||
|
@ -52,13 +52,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onDescriptionChanged: {
|
||||
|
||||
if(root.supports3DHTML) {
|
||||
descriptionTextModel.clear();
|
||||
descriptionTextModel.append({text: description});
|
||||
} else {
|
||||
descriptionText.text = description;
|
||||
}
|
||||
descriptionText.text = description;
|
||||
}
|
||||
|
||||
onAttributionsChanged: {
|
||||
|
@ -250,7 +244,6 @@ Rectangle {
|
|||
|
||||
function evalHeight() {
|
||||
height = categoriesList.y - buyButton.y + categoriesList.height;
|
||||
console.log("HEIGHT: " + height);
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
|
@ -264,11 +257,17 @@ Rectangle {
|
|||
}
|
||||
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 : "") : ""
|
||||
property bool isUpdate: root.edition >= 0 // Special case of updating from a specific older item
|
||||
property bool isStocking: (creator === Account.username) && (availability === "not for sale") && !updated_item_id // Note: server will say "sold out" or "invalidated" before it says NFS
|
||||
property bool isFreeSpecial: isStocking || isUpdate
|
||||
enabled: isFreeSpecial || (availability === 'available')
|
||||
buttonGlyph: (enabled && !isUpdate && (price > 0)) ? hifi.glyphs.hfc : ""
|
||||
text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
|
||||
color: hifi.buttons.blue
|
||||
|
||||
|
||||
buttonGlyphSize: 24
|
||||
fontSize: 24
|
||||
|
||||
onClicked: root.buy();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ Rectangle {
|
|||
property string creator: ""
|
||||
property string category: ""
|
||||
property int price: 0
|
||||
property bool available: false
|
||||
property string availability: "unknown"
|
||||
property bool isLoggedIn: false;
|
||||
|
||||
signal buy()
|
||||
|
@ -226,6 +226,7 @@ Rectangle {
|
|||
top: parent.top
|
||||
left: parent.left
|
||||
leftMargin: 15
|
||||
topMargin: 10
|
||||
}
|
||||
width: paintedWidth
|
||||
|
||||
|
@ -241,7 +242,7 @@ Rectangle {
|
|||
anchors {
|
||||
top: creatorLabel.top;
|
||||
left: creatorLabel.right;
|
||||
leftMargin: 15;
|
||||
leftMargin: 10;
|
||||
}
|
||||
width: paintedWidth;
|
||||
|
||||
|
@ -273,7 +274,7 @@ Rectangle {
|
|||
anchors {
|
||||
top: categoryLabel.top
|
||||
left: categoryLabel.right
|
||||
leftMargin: 15
|
||||
leftMargin: 10
|
||||
}
|
||||
width: paintedWidth
|
||||
|
||||
|
@ -298,11 +299,21 @@ Rectangle {
|
|||
topMargin:10
|
||||
bottomMargin: 10
|
||||
}
|
||||
width: 180
|
||||
|
||||
property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS
|
||||
property bool isMine: creator === Account.username
|
||||
property bool isUpgrade: root.edition >= 0
|
||||
property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price
|
||||
property bool isAvailable: availability === "available"
|
||||
|
||||
text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability)
|
||||
enabled: isAvailable
|
||||
buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : ""
|
||||
|
||||
text: root.price ? root.price : "FREE"
|
||||
buttonGlyph: root.price ? hifi.glyphs.hfc : ""
|
||||
color: hifi.buttons.blue;
|
||||
|
||||
buttonGlyphSize: 24
|
||||
fontSize: 24
|
||||
onClicked: root.buy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -380,7 +380,7 @@ Item {
|
|||
if (updateButton.visible && uninstallButton.visible) {
|
||||
item.itemButtonText = "";
|
||||
item.glyphSize = 20;
|
||||
} else {
|
||||
} else if (item) {
|
||||
item.itemButtonText = "Send to Trash";
|
||||
item.glyphSize = 30;
|
||||
}
|
||||
|
|
|
@ -40,10 +40,6 @@ Rectangle {
|
|||
source: "images/wallet-bg.jpg";
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
KeyboardScriptingInterface.raised = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3
|
|||
import TabletScriptingInterface 1.0
|
||||
|
||||
import "."
|
||||
import stylesUit 1.0
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import "../audio" as HifiAudio
|
||||
|
||||
Item {
|
||||
|
@ -49,44 +49,116 @@ Item {
|
|||
}
|
||||
|
||||
Item {
|
||||
width: 150
|
||||
height: 50
|
||||
id: rightContainer
|
||||
width: clockItem.width > loginItem.width ? clockItem.width + clockAmPmTextMetrics.width :
|
||||
loginItem.width + clockAmPmTextMetrics.width
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 30
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 20
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
function timeChanged() {
|
||||
var date = new Date();
|
||||
clockTime.text = date.toLocaleTimeString(Qt.locale("en_US"), "h:mm ap");
|
||||
var regex = /[\sa-zA-z]+/;
|
||||
clockTime.text = clockTime.text.replace(regex, "");
|
||||
clockAmPm.text = date.toLocaleTimeString(Qt.locale("en_US"), "ap");
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignRight
|
||||
font.pixelSize: 20
|
||||
color: "#afafaf"
|
||||
Timer {
|
||||
interval: 1000; running: true; repeat: true;
|
||||
onTriggered: rightContainer.timeChanged();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: clockAmPmItem
|
||||
width: clockAmPmTextMetrics.width
|
||||
height: clockAmPmTextMetrics.height
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
TextMetrics {
|
||||
id: clockAmPmTextMetrics
|
||||
text: clockAmPm.text
|
||||
font: clockAmPm.font
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
visible: Account.loggedIn
|
||||
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
|
||||
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignRight
|
||||
font.pixelSize: 20
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
id: clockAmPm
|
||||
anchors.right: parent.right
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.pixelSize: 12
|
||||
font.family: "Rawline"
|
||||
color: "#afafaf"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (!Account.loggedIn) {
|
||||
DialogsManager.showLoginDialog()
|
||||
} else {
|
||||
Account.logOut()
|
||||
Item {
|
||||
id: clockItem
|
||||
width: clockTimeTextMetrics.width
|
||||
height: clockTimeTextMetrics.height
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -10
|
||||
right: clockAmPmItem.left
|
||||
rightMargin: 5
|
||||
}
|
||||
TextMetrics {
|
||||
id: clockTimeTextMetrics
|
||||
text: clockTime.text
|
||||
font: clockTime.font
|
||||
}
|
||||
Text {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
id: clockTime
|
||||
font.bold: false
|
||||
font.pixelSize: 36
|
||||
font.family: "Rawline"
|
||||
color: "#afafaf"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: loginItem
|
||||
width: loginTextMetrics.width
|
||||
height: loginTextMetrics.height
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 10
|
||||
right: clockAmPmItem.left
|
||||
rightMargin: 5
|
||||
}
|
||||
Text {
|
||||
id: loginText
|
||||
anchors.right: parent.right
|
||||
text: Account.loggedIn ? tabletRoot.usernameShort : qsTr("Log in")
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignRight
|
||||
font.pixelSize: 18
|
||||
font.family: "Rawline"
|
||||
color: "#afafaf"
|
||||
}
|
||||
TextMetrics {
|
||||
id: loginTextMetrics
|
||||
text: loginText.text
|
||||
font: loginText.font
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (!Account.loggedIn) {
|
||||
DialogsManager.showLoginDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
rightContainer.timeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -178,10 +178,10 @@ Rectangle {
|
|||
|
||||
function setUsername(newUsername) {
|
||||
username = newUsername;
|
||||
usernameShort = newUsername.substring(0, 8);
|
||||
usernameShort = newUsername.substring(0, 14);
|
||||
|
||||
if (newUsername.length > 8) {
|
||||
usernameShort = usernameShort + "..."
|
||||
if (newUsername.length > 14) {
|
||||
usernameShort = usernameShort + "..."
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
interface/resources/qml/styles-uit/Rawline.qml
Normal file
20
interface/resources/qml/styles-uit/Rawline.qml
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Rawline.qml
|
||||
//
|
||||
// Created by Wayne Chen on 25 Feb 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
|
||||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
|
||||
Text {
|
||||
id: root
|
||||
property real size: 32
|
||||
font.pixelSize: size
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.family: "Rawline"
|
||||
}
|
20
interface/resources/qml/stylesUit/Rawline.qml
Normal file
20
interface/resources/qml/stylesUit/Rawline.qml
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Rawline.qml
|
||||
//
|
||||
// Created by Wayne Chen on 25 Feb 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
|
||||
//
|
||||
|
||||
import QtQuick 2.7
|
||||
|
||||
Text {
|
||||
id: root
|
||||
property real size: 32
|
||||
font.pixelSize: size
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.family: "Rawline"
|
||||
}
|
|
@ -16,8 +16,8 @@
|
|||
#include <QObject>
|
||||
|
||||
/**jsdoc
|
||||
* The <code>HifiAbout</code> API provides information about the version of Interface that is currently running. It also
|
||||
* provides the ability to open a Web page in an Interface browser window.
|
||||
* The <code>HifiAbout</code> API provides information about the version of Interface that is currently running. It also
|
||||
* has the functionality to open a web page in an Interface browser window.
|
||||
*
|
||||
* @namespace HifiAbout
|
||||
*
|
||||
|
@ -30,9 +30,9 @@
|
|||
* @property {string} qtVersion - The Qt version used in Interface that is currently running. <em>Read-only.</em>
|
||||
*
|
||||
* @example <caption>Report build information for the version of Interface currently running.</caption>
|
||||
* print("HiFi build date: " + HifiAbout.buildDate); // 11 Feb 2019
|
||||
* print("HiFi version: " + HifiAbout.buildVersion); // 0.78.0
|
||||
* print("Qt version: " + HifiAbout.qtVersion); // 5.10.1
|
||||
* print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine.
|
||||
* print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine.
|
||||
* print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine.
|
||||
*/
|
||||
|
||||
class AboutUtil : public QObject {
|
||||
|
@ -52,9 +52,9 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Display a Web page in an Interface browser window.
|
||||
* Display a web page in an Interface browser window.
|
||||
* @function HifiAbout.openUrl
|
||||
* @param {string} url - The URL of the Web page to display.
|
||||
* @param {string} url - The URL of the web page you want to view in Interface.
|
||||
*/
|
||||
void openUrl(const QString &url) const;
|
||||
private:
|
||||
|
|
|
@ -45,6 +45,10 @@ void AndroidHelper::notifyBeforeEnterBackground() {
|
|||
emit beforeEnterBackground();
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyToggleAwayMode() {
|
||||
emit toggleAwayMode();
|
||||
}
|
||||
|
||||
void AndroidHelper::notifyEnterBackground() {
|
||||
emit enterBackground();
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
void notifyEnterForeground();
|
||||
void notifyBeforeEnterBackground();
|
||||
void notifyEnterBackground();
|
||||
void notifyToggleAwayMode();
|
||||
|
||||
void performHapticFeedback(int duration);
|
||||
void processURL(const QString &url);
|
||||
|
@ -55,7 +56,7 @@ signals:
|
|||
void enterForeground();
|
||||
void beforeEnterBackground();
|
||||
void enterBackground();
|
||||
|
||||
void toggleAwayMode();
|
||||
void hapticFeedbackRequested(int duration);
|
||||
|
||||
void handleSignupCompleted();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include <chrono>
|
||||
|
@ -37,7 +38,6 @@
|
|||
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
@ -51,6 +51,7 @@
|
|||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <gl/GLWindow.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
|
@ -191,6 +192,9 @@
|
|||
#include "scripting/WalletScriptingInterface.h"
|
||||
#include "scripting/TTSScriptingInterface.h"
|
||||
#include "scripting/KeyboardScriptingInterface.h"
|
||||
|
||||
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
#endif
|
||||
|
@ -239,6 +243,7 @@
|
|||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||
#include <DesktopPreviewProvider.h>
|
||||
|
||||
|
||||
#include "AboutUtil.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -622,8 +627,6 @@ public:
|
|||
switch (type) {
|
||||
case NestableType::Entity:
|
||||
return getEntityModelProvider(static_cast<EntityItemID>(uuid));
|
||||
case NestableType::Overlay:
|
||||
return nullptr;
|
||||
case NestableType::Avatar:
|
||||
return getAvatarModelProvider(uuid);
|
||||
}
|
||||
|
@ -1058,6 +1061,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged,
|
||||
controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices);
|
||||
|
||||
EntityTree::setEntityClicksCapturedOperator([this] {
|
||||
return _controllerScriptingInterface->areEntityClicksCaptured();
|
||||
});
|
||||
|
||||
_entityClipboard->createRootElement();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -1072,6 +1079,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/rawline-500.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
|
||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
|
||||
|
@ -1747,12 +1755,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return 1;
|
||||
return 1 ;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
});
|
||||
|
||||
|
||||
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
|
||||
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
|
||||
// if the _touchscreenDevice is not supported it will not be registered
|
||||
|
@ -1850,6 +1859,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
this->installEventFilter(this);
|
||||
|
||||
|
||||
|
||||
#ifdef HAVE_DDE
|
||||
auto ddeTracker = DependencyManager::get<DdeFaceTracker>();
|
||||
ddeTracker->init();
|
||||
|
@ -1911,46 +1922,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
EntityTreeRenderer::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
if (_aboutToQuit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to find the renderable
|
||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||
if (renderable) {
|
||||
renderable->addMaterial(material, parentMaterialName);
|
||||
}
|
||||
|
||||
// even if we don't find it, try to find the entity
|
||||
auto entity = getEntities()->getEntity(entityID);
|
||||
if (entity) {
|
||||
entity->addMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
EntityTree::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
EntityTreeRenderer::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
if (_aboutToQuit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to find the renderable
|
||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||
if (renderable) {
|
||||
renderable->removeMaterial(material, parentMaterialName);
|
||||
}
|
||||
|
||||
// even if we don't find it, try to find the entity
|
||||
auto entity = getEntities()->getEntity(entityID);
|
||||
if (entity) {
|
||||
entity->removeMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
EntityTreeRenderer::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
|
||||
if (avatar) {
|
||||
|
@ -1959,7 +1958,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
}
|
||||
return false;
|
||||
});
|
||||
EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
EntityTreeRenderer::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
|
||||
if (avatar) {
|
||||
|
@ -2282,7 +2281,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// Setup the mouse ray pick and related operators
|
||||
{
|
||||
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::PICK_ENTITIES()), 0.0f, true);
|
||||
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_LOCAL_ENTITIES()), 0.0f, true);
|
||||
mouseRayPick->parentTransform = std::make_shared<MouseTransformNode>();
|
||||
mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE);
|
||||
auto mouseRayPickID = DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, mouseRayPick);
|
||||
|
@ -2308,31 +2307,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
|
||||
});
|
||||
|
||||
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode) {
|
||||
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
|
||||
if (billboardMode == BillboardMode::YAW) {
|
||||
//rotate about vertical to face the camera
|
||||
ViewFrustum frustum;
|
||||
copyViewFrustum(frustum);
|
||||
glm::vec3 dPosition = frustum.getPosition() - position;
|
||||
glm::vec3 dPosition = frustumPos - position;
|
||||
// If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
|
||||
float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
|
||||
return glm::quat(glm::vec3(0.0f, yawRotation, 0.0f));
|
||||
} else if (billboardMode == BillboardMode::FULL) {
|
||||
ViewFrustum frustum;
|
||||
copyViewFrustum(frustum);
|
||||
glm::vec3 cameraPos = frustum.getPosition();
|
||||
// use the referencial from the avatar, y isn't always up
|
||||
glm::vec3 avatarUP = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
|
||||
// check to see if glm::lookAt will work / using glm::lookAt variable name
|
||||
glm::highp_vec3 s(glm::cross(position - cameraPos, avatarUP));
|
||||
glm::highp_vec3 s(glm::cross(position - frustumPos, avatarUP));
|
||||
|
||||
// make sure s is not NaN for any component
|
||||
if (glm::length2(s) > 0.0f) {
|
||||
return glm::conjugate(glm::toQuat(glm::lookAt(cameraPos, position, avatarUP)));
|
||||
return glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP)));
|
||||
}
|
||||
}
|
||||
return rotation;
|
||||
});
|
||||
EntityItem::setPrimaryViewFrustumPositionOperator([this]() {
|
||||
ViewFrustum viewFrustum;
|
||||
copyViewFrustum(viewFrustum);
|
||||
return viewFrustum.getPosition();
|
||||
});
|
||||
|
||||
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
||||
bool isTablet = url == TabletScriptingInterface::QML;
|
||||
|
@ -2347,6 +2346,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
} else {
|
||||
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
|
||||
}
|
||||
auto surfaceContext = webSurface->getSurfaceContext();
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
} else {
|
||||
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
|
||||
webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) {
|
||||
|
@ -2418,6 +2419,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground);
|
||||
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
|
||||
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
|
||||
connect(&AndroidHelper::instance(), &AndroidHelper::toggleAwayMode, this, &Application::toggleAwayMode);
|
||||
AndroidHelper::instance().notifyLoadComplete();
|
||||
#endif
|
||||
pauseUntilLoginDetermined();
|
||||
|
@ -3099,7 +3101,7 @@ void Application::initializeUi() {
|
|||
}
|
||||
if (TouchscreenVirtualPadDevice::NAME == inputPlugin->getName()) {
|
||||
_touchscreenVirtualPadDevice = std::dynamic_pointer_cast<TouchscreenVirtualPadDevice>(inputPlugin);
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#if defined(ANDROID_APP_INTERFACE)
|
||||
auto& virtualPadManager = VirtualPad::Manager::instance();
|
||||
connect(&virtualPadManager, &VirtualPad::Manager::hapticFeedbackRequested,
|
||||
this, [](int duration) {
|
||||
|
@ -3282,6 +3284,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
|
||||
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
|
||||
if (setAdditionalContextProperties) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
|
@ -3292,7 +3295,6 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
|
||||
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
|
@ -3631,10 +3633,14 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
}
|
||||
|
||||
// Get controller availability
|
||||
#ifdef ANDROID_APP_QUEST_INTERFACE
|
||||
bool hasHandControllers = true;
|
||||
#else
|
||||
bool hasHandControllers = false;
|
||||
if (PluginUtils::isViveControllerAvailable() || PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check HMD use (may be technically available without being in use)
|
||||
bool hasHMD = PluginUtils::isHMDAvailable();
|
||||
|
@ -3664,8 +3670,8 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
// If this is a first run we short-circuit the address passed in
|
||||
if (_firstRun.get()) {
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
#endif
|
||||
_firstRun.set(false);
|
||||
|
||||
|
@ -3776,9 +3782,12 @@ std::map<QString, QString> Application::prepareServerlessDomainContents(QUrl dom
|
|||
tmpTree->reaverageOctreeElements();
|
||||
tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0);
|
||||
}
|
||||
std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
|
||||
|
||||
return tmpTree->getNamedPaths();
|
||||
// we must manually eraseAllOctreeElements(false) else the tmpTree will mem-leak
|
||||
tmpTree->eraseAllOctreeElements(false);
|
||||
|
||||
return namedPaths;
|
||||
}
|
||||
|
||||
void Application::loadServerlessDomain(QUrl domainURL) {
|
||||
|
@ -4389,7 +4398,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
|||
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
|
||||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) {
|
||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||
getOverlays().mouseMoveEvent(&mappedEvent);
|
||||
}
|
||||
|
||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
@ -4423,14 +4431,8 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
|||
#endif
|
||||
|
||||
QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers());
|
||||
std::pair<float, QUuid> entityResult;
|
||||
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
entityResult = getEntities()->mousePressEvent(&mappedEvent);
|
||||
}
|
||||
std::pair<float, QUuid> overlayResult = getOverlays().mousePressEvent(&mappedEvent);
|
||||
|
||||
QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second;
|
||||
setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID);
|
||||
QUuid result = getEntities()->mousePressEvent(&mappedEvent);
|
||||
setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
|
||||
|
||||
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
|
@ -4469,11 +4471,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
|
|||
transformedPos,
|
||||
event->screenPos(), event->button(),
|
||||
event->buttons(), event->modifiers());
|
||||
|
||||
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
getEntities()->mouseDoublePressEvent(&mappedEvent);
|
||||
}
|
||||
getOverlays().mouseDoublePressEvent(&mappedEvent);
|
||||
getEntities()->mouseDoublePressEvent(&mappedEvent);
|
||||
|
||||
// if one of our scripts have asked to capture this event, then stop processing it
|
||||
if (_controllerScriptingInterface->isMouseCaptured()) {
|
||||
|
@ -4498,7 +4496,6 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
|
|||
event->buttons(), event->modifiers());
|
||||
|
||||
getEntities()->mouseReleaseEvent(&mappedEvent);
|
||||
getOverlays().mouseReleaseEvent(&mappedEvent);
|
||||
|
||||
_controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
||||
|
@ -4982,6 +4979,15 @@ void Application::idle() {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (_keyboardFocusWaitingOnRenderable && getEntities()->renderableForEntityId(_keyboardFocusedEntity.get())) {
|
||||
QUuid entityId = _keyboardFocusedEntity.get();
|
||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||
_keyboardFocusWaitingOnRenderable = false;
|
||||
setKeyboardFocusEntity(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("pluginIdle");
|
||||
PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()");
|
||||
|
@ -5764,6 +5770,11 @@ void Application::reloadResourceCaches() {
|
|||
|
||||
DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models
|
||||
|
||||
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
|
||||
getOffscreenUI()->clearCache();
|
||||
|
||||
DependencyManager::get<Keyboard>()->createKeyboard();
|
||||
|
||||
getMyAvatar()->resetFullAvatarURL();
|
||||
}
|
||||
|
||||
|
@ -5810,7 +5821,7 @@ void Application::setKeyboardFocusEntity(const QUuid& id) {
|
|||
if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) {
|
||||
if (id == _loginDialogID) {
|
||||
emit loginDialogFocusEnabled();
|
||||
} else {
|
||||
} else if (!_keyboardFocusWaitingOnRenderable) {
|
||||
// that's the only entity we want in focus;
|
||||
return;
|
||||
}
|
||||
|
@ -5827,7 +5838,10 @@ void Application::setKeyboardFocusEntity(const QUuid& id) {
|
|||
if (properties.getVisible()) {
|
||||
auto entities = getEntities();
|
||||
auto entityId = _keyboardFocusedEntity.get();
|
||||
if (entities->wantsKeyboardFocus(entityId)) {
|
||||
auto entityItemRenderable = entities->renderableForEntityId(entityId);
|
||||
if (!entityItemRenderable) {
|
||||
_keyboardFocusWaitingOnRenderable = true;
|
||||
} else if (entityItemRenderable->wantsKeyboardFocus()) {
|
||||
entities->setProxyWindow(entityId, _window->windowHandle());
|
||||
if (_keyboardMouseDevice->isActive()) {
|
||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||
|
@ -6931,7 +6945,7 @@ void Application::clearDomainOctreeDetails(bool clearAll) {
|
|||
});
|
||||
|
||||
// reset the model renderer
|
||||
clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities();
|
||||
clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities();
|
||||
|
||||
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
|
||||
|
@ -8251,6 +8265,7 @@ void Application::loadDomainConnectionDialog() {
|
|||
}
|
||||
|
||||
void Application::toggleLogDialog() {
|
||||
#ifndef ANDROID_APP_QUEST_INTERFACE
|
||||
if (getLoginDialogPoppedUp()) {
|
||||
return;
|
||||
}
|
||||
|
@ -8274,6 +8289,7 @@ void Application::toggleLogDialog() {
|
|||
} else {
|
||||
_logDialog->show();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::recreateLogWindow(int keepOnTop) {
|
||||
|
@ -9136,23 +9152,40 @@ void Application::beforeEnterBackground() {
|
|||
clearDomainOctreeDetails();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Application::enterBackground() {
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"stop", Qt::BlockingQueuedConnection);
|
||||
// Quest only supports one plugin which can't be deactivated currently
|
||||
#if !defined(ANDROID_APP_QUEST_INTERFACE)
|
||||
if (getActiveDisplayPlugin()->isActive()) {
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::enterForeground() {
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||
"start", Qt::BlockingQueuedConnection);
|
||||
// Quest only supports one plugin which can't be deactivated currently
|
||||
#if !defined(ANDROID_APP_QUEST_INTERFACE)
|
||||
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
|
||||
qWarning() << "Could not re-activate display plugin";
|
||||
}
|
||||
#endif
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->setSendDomainServerCheckInEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
void Application::toggleAwayMode(){
|
||||
QKeyEvent event = QKeyEvent (QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier);
|
||||
QCoreApplication::sendEvent (this, &event);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#include "Application.moc"
|
||||
|
|
|
@ -338,7 +338,8 @@ public:
|
|||
void beforeEnterBackground();
|
||||
void enterBackground();
|
||||
void enterForeground();
|
||||
#endif
|
||||
void toggleAwayMode();
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
@ -732,6 +733,7 @@ private:
|
|||
bool _failedToConnectToEntityServer { false };
|
||||
|
||||
bool _reticleClickPressed { false };
|
||||
bool _keyboardFocusWaitingOnRenderable { false };
|
||||
|
||||
int _avatarAttachmentRequest = 0;
|
||||
|
||||
|
|
|
@ -19,13 +19,19 @@
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <render/Args.h>
|
||||
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.75f; // default quality level setting is High (lower framerate)
|
||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate)
|
||||
#else
|
||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid
|
||||
#endif
|
||||
const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps
|
||||
#ifdef Q_OS_ANDROID
|
||||
const float LOD_MAX_LIKELY_HMD_FPS = 36.0f; // this is essentially, V-synch fps
|
||||
#else
|
||||
const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps
|
||||
#endif
|
||||
|
||||
const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate
|
||||
|
||||
class AABox;
|
||||
|
|
|
@ -297,6 +297,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
avatar->setIsNewAvatar(false);
|
||||
}
|
||||
avatar->simulate(deltaTime, inView);
|
||||
if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) {
|
||||
_myAvatar->addAvatarHandsToFlow(avatar);
|
||||
}
|
||||
avatar->updateRenderItem(renderTransaction);
|
||||
avatar->updateSpaceProxy(workloadTransaction);
|
||||
avatar->setLastRenderUpdateTime(startTime);
|
||||
|
@ -716,7 +719,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
}
|
||||
}
|
||||
|
||||
if (rayAvatarResult._intersect && pickAgainstMesh) {
|
||||
if (avatar && rayAvatarResult._intersect && pickAgainstMesh) {
|
||||
glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint);
|
||||
glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayAvatarResult._distance * rayDirection, rayAvatarResult._intersectWithJoint);
|
||||
|
||||
|
|
|
@ -254,11 +254,15 @@ void AvatarProject::openInInventory() const {
|
|||
DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource("hifi/commerce/wallet/Wallet.qml");
|
||||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||
tablet->getTabletRoot()->forceActiveFocus();
|
||||
auto name = getProjectName();
|
||||
|
||||
// I'm not a fan of this, but it's the only current option.
|
||||
auto name = getProjectName();
|
||||
QTimer::singleShot(TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS, [name, tablet]() {
|
||||
tablet->sendToQml(QVariantMap({ { "method", "updatePurchases" }, { "filterText", name } }));
|
||||
});
|
||||
|
||||
QQuickItem* root = tablet->getTabletRoot();
|
||||
if (root) {
|
||||
root->forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,10 @@ public:
|
|||
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
|
||||
}
|
||||
Q_INVOKABLE bool getHasErrors() const { return _hasErrors; }
|
||||
Q_INVOKABLE void setHasErrors(bool hasErrors) { _hasErrors = hasErrors; }
|
||||
Q_INVOKABLE void setHasErrors(bool hasErrors) {
|
||||
_hasErrors = hasErrors;
|
||||
emit hasErrorsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the AvatarProject or a nullptr on failure.
|
||||
|
|
|
@ -32,6 +32,17 @@ void GrabManager::simulateGrabs() {
|
|||
bool success;
|
||||
SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success);
|
||||
if (success && grabbedThing) {
|
||||
auto entity = std::dynamic_pointer_cast<EntityItem>(grabbedThing);
|
||||
if (entity) {
|
||||
if (entity->getLocked()) {
|
||||
continue; // even if someone else claims to be grabbing it, don't move a locked thing
|
||||
}
|
||||
const GrabPropertyGroup& grabProps = entity->getGrabProperties();
|
||||
if (!grabProps.getGrabbable()) {
|
||||
continue; // even if someone else claims to be grabbing it, don't move non-grabbable
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 finalPosition = acc.finalizePosition();
|
||||
glm::quat finalOrientation = acc.finalizeOrientation();
|
||||
grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition));
|
||||
|
|
117
interface/src/avatar/MyAvatar.cpp
Executable file → Normal file
117
interface/src/avatar/MyAvatar.cpp
Executable file → Normal file
|
@ -910,7 +910,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
recorder->recordFrame(FRAME_TYPE, toFrame(*this));
|
||||
}
|
||||
|
||||
locationChanged();
|
||||
locationChanged(true, false);
|
||||
// if a entity-child of this avatar has moved outside of its queryAACube, update the cube and tell the entity server.
|
||||
auto entityTreeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
|
@ -920,6 +920,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties();
|
||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
locationChanged(true, false);
|
||||
// we need to update attached queryAACubes in our own local tree so point-select always works
|
||||
// however we don't want to flood the update pipeline with AvatarEntity updates, so we assume
|
||||
// others have all info required to properly update queryAACube of AvatarEntities on their end
|
||||
|
@ -932,7 +933,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
|
||||
bool zoneAllowsFlying = zoneInteractionProperties.first;
|
||||
bool collisionlessAllowed = zoneInteractionProperties.second;
|
||||
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
|
||||
_characterController.setZoneFlyingAllowed(zoneAllowsFlying || !isPhysicsEnabled);
|
||||
_characterController.setComfortFlyingAllowed(_enableFlying);
|
||||
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
||||
}
|
||||
|
||||
|
@ -2323,23 +2325,25 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
||||
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
||||
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
|
||||
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
|
||||
|
||||
if (_fullAvatarModelName.isEmpty()) {
|
||||
// Store the FST file name into preferences
|
||||
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
|
||||
if (mapping.value("name").isValid()) {
|
||||
_fullAvatarModelName = mapping.value("name").toString();
|
||||
}
|
||||
}
|
||||
if (_fullAvatarModelName.isEmpty()) {
|
||||
// Store the FST file name into preferences
|
||||
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
|
||||
if (mapping.value("name").isValid()) {
|
||||
_fullAvatarModelName = mapping.value("name").toString();
|
||||
}
|
||||
}
|
||||
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
_skeletonModelLoaded = true;
|
||||
}
|
||||
QObject::disconnect(*skeletonConnection);
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
initFlowFromFST();
|
||||
|
||||
_skeletonModelLoaded = true;
|
||||
}
|
||||
QObject::disconnect(*skeletonConnection);
|
||||
});
|
||||
|
||||
saveAvatarUrl();
|
||||
|
@ -2963,6 +2967,10 @@ void MyAvatar::initAnimGraph() {
|
|||
graphUrl = _fstAnimGraphOverrideUrl;
|
||||
} else {
|
||||
graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json");
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
|
||||
graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation_withSplineIKNode.json");
|
||||
#endif
|
||||
}
|
||||
|
||||
emit animGraphUrlChanged(graphUrl);
|
||||
|
@ -5339,6 +5347,81 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar) {
|
||||
auto &flow = _skeletonModel->getRig().getFlow();
|
||||
for (auto &handJointName : HAND_COLLISION_JOINTS) {
|
||||
int jointIndex = otherAvatar->getJointIndex(handJointName);
|
||||
if (jointIndex != -1) {
|
||||
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
|
||||
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
_skeletonModel->getRig().initFlow(isActive);
|
||||
auto &flow = _skeletonModel->getRig().getFlow();
|
||||
auto &collisionSystem = flow.getCollisionSystem();
|
||||
collisionSystem.setActive(isCollidable);
|
||||
auto physicsGroups = physicsConfig.keys();
|
||||
if (physicsGroups.size() > 0) {
|
||||
for (auto &groupName : physicsGroups) {
|
||||
auto settings = physicsConfig[groupName].toMap();
|
||||
FlowPhysicsSettings physicsSettings;
|
||||
if (settings.contains("active")) {
|
||||
physicsSettings._active = settings["active"].toBool();
|
||||
}
|
||||
if (settings.contains("damping")) {
|
||||
physicsSettings._damping = settings["damping"].toFloat();
|
||||
}
|
||||
if (settings.contains("delta")) {
|
||||
physicsSettings._delta = settings["delta"].toFloat();
|
||||
}
|
||||
if (settings.contains("gravity")) {
|
||||
physicsSettings._gravity = settings["gravity"].toFloat();
|
||||
}
|
||||
if (settings.contains("inertia")) {
|
||||
physicsSettings._inertia = settings["inertia"].toFloat();
|
||||
}
|
||||
if (settings.contains("radius")) {
|
||||
physicsSettings._radius = settings["radius"].toFloat();
|
||||
}
|
||||
if (settings.contains("stiffness")) {
|
||||
physicsSettings._stiffness = settings["stiffness"].toFloat();
|
||||
}
|
||||
flow.setPhysicsSettingsForGroup(groupName, physicsSettings);
|
||||
}
|
||||
}
|
||||
auto collisionJoints = collisionsConfig.keys();
|
||||
if (collisionJoints.size() > 0) {
|
||||
collisionSystem.resetCollisions();
|
||||
for (auto &jointName : collisionJoints) {
|
||||
int jointIndex = getJointIndex(jointName);
|
||||
FlowCollisionSettings collisionsSettings;
|
||||
auto settings = collisionsConfig[jointName].toMap();
|
||||
collisionsSettings._entityID = getID();
|
||||
if (settings.contains("radius")) {
|
||||
collisionsSettings._radius = settings["radius"].toFloat();
|
||||
}
|
||||
if (settings.contains("offset")) {
|
||||
collisionsSettings._offset = vec3FromVariant(settings["offset"]);
|
||||
}
|
||||
collisionSystem.addCollisionSphere(jointIndex, collisionsSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::initFlowFromFST() {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
auto &flowData = _skeletonModel->getHFMModel().flowData;
|
||||
if (flowData.shouldInitFlow()) {
|
||||
useFlow(true, flowData.shouldInitCollisions(), flowData._physicsConfig, flowData._collisionsConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
|
|
|
@ -1544,6 +1544,20 @@ public:
|
|||
* @comment Uses the base class's JSDoc.
|
||||
*/
|
||||
int sendAvatarDataPacket(bool sendAll = false) override;
|
||||
|
||||
void addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar);
|
||||
|
||||
/**jsdoc
|
||||
* Init flow simulation on avatar.
|
||||
* @function MyAvatar.useFlow
|
||||
* @param {boolean} - Set to <code>true</code> to activate flow simulation.
|
||||
* @param {boolean} - Set to <code>true</code> to activate collisions.
|
||||
* @param {Object} physicsConfig - object with the customized physic parameters
|
||||
* i.e. {"hair": {"active": true, "stiffness": 0.0, "radius": 0.04, "gravity": -0.035, "damping": 0.8, "inertia": 0.8, "delta": 0.35}}
|
||||
* @param {Object} collisionsConfig - object with the customized collision parameters
|
||||
* i.e. {"Spine2": {"type": "sphere", "radius": 0.14, "offset": {"x": 0.0, "y": 0.2, "z": 0.0}}}
|
||||
*/
|
||||
Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap());
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -2201,6 +2215,7 @@ private:
|
|||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void initHeadBones();
|
||||
void initAnimGraph();
|
||||
void initFlowFromFST();
|
||||
|
||||
// Avatar Preferences
|
||||
QUrl _fullAvatarURLFromPreferences;
|
||||
|
|
|
@ -10,12 +10,14 @@
|
|||
|
||||
#include <avatars-renderer/Avatar.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <CubicHermiteSpline.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
|
||||
|
||||
MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
|
||||
}
|
||||
|
||||
|
@ -33,6 +35,22 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
};
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
|
||||
static glm::vec3 computeSpine2WithHeadHipsSpline(MyAvatar* myAvatar, AnimPose hipsIKTargetPose, AnimPose headIKTargetPose) {
|
||||
|
||||
// the the ik targets to compute the spline with
|
||||
CubicHermiteSplineFunctorWithArcLength splineFinal(headIKTargetPose.rot(), headIKTargetPose.trans(), hipsIKTargetPose.rot(), hipsIKTargetPose.trans());
|
||||
|
||||
// measure the total arc length along the spline
|
||||
float totalArcLength = splineFinal.arcLength(1.0f);
|
||||
float tFinal = splineFinal.arcLengthInverse(myAvatar->getSpine2SplineRatio() * totalArcLength);
|
||||
glm::vec3 spine2Translation = splineFinal(tFinal);
|
||||
|
||||
return spine2Translation + myAvatar->getSpine2SplineOffset();
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
|
||||
|
||||
|
@ -233,6 +251,12 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() &&
|
||||
!(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) {
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
|
||||
AnimPose headAvatarSpace(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
|
||||
AnimPose headRigSpace = avatarToRigPose * headAvatarSpace;
|
||||
AnimPose hipsRigSpace = sensorToRigPose * sensorHips;
|
||||
glm::vec3 spine2TargetTranslation = computeSpine2WithHeadHipsSpline(myAvatar, hipsRigSpace, headRigSpace);
|
||||
#endif
|
||||
const float SPINE2_ROTATION_FILTER = 0.5f;
|
||||
AnimPose currentSpine2Pose;
|
||||
AnimPose currentHeadPose;
|
||||
|
@ -243,6 +267,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
if (spine2Exists && headExists && hipsExists) {
|
||||
|
||||
AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace());
|
||||
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
|
||||
rigSpaceYaw.rot() = safeLerp(Quaternions::IDENTITY, rigSpaceYaw.rot(), 0.5f);
|
||||
#endif
|
||||
glm::vec3 u, v, w;
|
||||
glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
glm::vec3 up = currentHeadPose.trans() - currentHipsPose.trans();
|
||||
|
@ -253,6 +280,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
}
|
||||
generateBasisVectors(up, fwd, u, v, w);
|
||||
AnimPose newSpinePose(glm::mat4(glm::vec4(w, 0.0f), glm::vec4(u, 0.0f), glm::vec4(v, 0.0f), glm::vec4(glm::vec3(0.0f, 0.0f, 0.0f), 1.0f)));
|
||||
#if defined(Q_OS_ANDROID) || defined(HIFI_USE_OPTIMIZED_IK)
|
||||
currentSpine2Pose.trans() = spine2TargetTranslation;
|
||||
#endif
|
||||
currentSpine2Pose.rot() = safeLerp(currentSpine2Pose.rot(), newSpinePose.rot(), SPINE2_ROTATION_FILTER);
|
||||
params.primaryControllerPoses[Rig::PrimaryControllerType_Spine2] = currentSpine2Pose;
|
||||
params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated;
|
||||
|
|
|
@ -368,7 +368,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
|
|||
PROFILE_RANGE(simulation, "grabs");
|
||||
applyGrabChanges();
|
||||
}
|
||||
|
||||
updateFadingStatus();
|
||||
}
|
||||
|
||||
|
@ -504,6 +503,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
|
|||
// then set the the original ID for the changes to take effect
|
||||
// TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes
|
||||
// side effects...remove the following three lines
|
||||
|
||||
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
|
||||
entity->setParentID(NULL_ID);
|
||||
entity->setParentID(oldParentID);
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
void rebuildCollisionShape() override;
|
||||
|
||||
void setWorkloadRegion(uint8_t region);
|
||||
uint8_t getWorkloadRegion() { return _workloadRegion; }
|
||||
bool shouldBeInPhysicsSimulation() const;
|
||||
bool needsPhysicsUpdate() const;
|
||||
|
||||
|
|
|
@ -62,8 +62,8 @@ public:
|
|||
* <tr><th>Value</th><th>Meaning</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>0</code></td><td>Not logged in</td><td>The user isn't logged in.</td></tr>
|
||||
* <tr><td><code>1</code></td><td>Not set up</td><td>The user's wallet isn't set up.</td></tr>
|
||||
* <tr><td><code>0</code></td><td>Not logged in</td><td>The user is not logged in.</td></tr>
|
||||
* <tr><td><code>1</code></td><td>Not set up</td><td>The user's wallet has not been set up.</td></tr>
|
||||
* <tr><td><code>2</code></td><td>Pre-existing</td><td>There is a wallet present on the server but not one
|
||||
* locally.</td></tr>
|
||||
* <tr><td><code>3</code></td><td>Conflicting</td><td>There is a wallet present on the server plus one present locally,
|
||||
|
@ -73,8 +73,8 @@ public:
|
|||
* <tr><td><code>5</code></td><td>Ready</td><td>The wallet is ready for use.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* <p>Wallets used to be stored locally but now they're stored on the server, unless the computer once had a wallet stored
|
||||
* locally in which case the wallet may be present in both places.</p>
|
||||
* <p>Wallets used to be stored locally but now they're only stored on the server. A wallet is present in both places if
|
||||
* your computer previously stored its information locally.</p>
|
||||
* @typedef {number} WalletScriptingInterface.WalletStatus
|
||||
*/
|
||||
enum WalletStatus {
|
||||
|
|
|
@ -72,7 +72,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
|
|||
Locker lock(_lock);
|
||||
EntityItemPointer entity = _entityTree->findEntityByID(entityID);
|
||||
|
||||
if (entity && entity->getCreated() < _startTime) {
|
||||
if (entity && !entity->isLocalEntity() && entity->getCreated() < _startTime) {
|
||||
|
||||
_trackedEntities.emplace(entityID, entity);
|
||||
int trackedEntityCount = (int)_trackedEntities.size();
|
||||
|
@ -81,7 +81,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
|
|||
_maxTrackedEntityCount = trackedEntityCount;
|
||||
_trackedEntityStabilityCount = 0;
|
||||
}
|
||||
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
|
||||
//qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,7 +172,10 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3&
|
|||
properties.setVisible(true);
|
||||
properties.setIgnorePickIntersection(doesPathIgnorePicks());
|
||||
QVector<float> widths;
|
||||
widths.append(getLineWidth() * parentScale);
|
||||
float width = getLineWidth() * parentScale;
|
||||
widths.append(width);
|
||||
widths.append(width);
|
||||
properties.setStrokeWidths(widths);
|
||||
DependencyManager::get<EntityScriptingInterface>()->editEntity(getPathID(), properties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,18 @@ PickFilter getPickFilter(unsigned int filter) {
|
|||
unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
||||
|
||||
#if defined (Q_OS_ANDROID)
|
||||
QString jointName { "" };
|
||||
if (propMap["joint"].isValid()) {
|
||||
QString jointName = propMap["joint"].toString();
|
||||
const QString MOUSE_JOINT = "Mouse";
|
||||
if (jointName == MOUSE_JOINT) {
|
||||
return PointerEvent::INVALID_POINTER_ID;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool enabled = false;
|
||||
if (propMap["enabled"].isValid()) {
|
||||
enabled = propMap["enabled"].toBool();
|
||||
|
|
|
@ -150,6 +150,17 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
|
|||
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
|
||||
QVariantMap propertyMap = properties.toMap();
|
||||
|
||||
#if defined (Q_OS_ANDROID)
|
||||
QString jointName { "" };
|
||||
if (propertyMap["joint"].isValid()) {
|
||||
QString jointName = propertyMap["joint"].toString();
|
||||
const QString MOUSE_JOINT = "Mouse";
|
||||
if (jointName == MOUSE_JOINT) {
|
||||
return PointerEvent::INVALID_POINTER_ID;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool faceAvatar = false;
|
||||
if (propertyMap["faceAvatar"].isValid()) {
|
||||
faceAvatar = propertyMap["faceAvatar"].toBool();
|
||||
|
|
|
@ -89,6 +89,8 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl
|
|||
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
|
||||
}
|
||||
|
||||
|
||||
|
||||
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
|
@ -102,6 +104,19 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3
|
|||
return pos2D;
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
|
||||
glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint;
|
||||
|
||||
glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.z));
|
||||
if (unNormalized) {
|
||||
pos2D *= glm::vec2(dimensions.x, dimensions.z);
|
||||
}
|
||||
return pos2D;
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
EntityPropertyFlags desiredProperties;
|
||||
desiredProperties += PROP_POSITION;
|
||||
|
|
|
@ -85,9 +85,11 @@ public:
|
|||
static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction);
|
||||
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
|
||||
|
||||
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
|
||||
static glm::vec2 projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNoemalized);
|
||||
|
||||
private:
|
||||
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);
|
||||
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
|
||||
};
|
||||
|
||||
#endif // hifi_RayPick_h
|
||||
|
|
|
@ -137,13 +137,14 @@ PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) c
|
|||
}
|
||||
|
||||
PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
|
||||
std::vector<StylusPickResult> results;
|
||||
auto entityTree = qApp->getEntities()->getTree();
|
||||
StylusPickResult nearestTarget(pick.toVariantMap());
|
||||
for (const auto& target : getIncludeItems()) {
|
||||
if (target.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entity = qApp->getEntities()->getTree()->findEntityByEntityItemID(target);
|
||||
auto entity = entityTree->findEntityByEntityItemID(target);
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
@ -154,31 +155,45 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
|
|||
|
||||
const auto entityRotation = entity->getWorldOrientation();
|
||||
const auto entityPosition = entity->getWorldPosition();
|
||||
const auto entityType = entity->getType();
|
||||
glm::vec3 normal;
|
||||
|
||||
glm::vec3 normal = entityRotation * Vectors::UNIT_Z;
|
||||
// TODO: Use the xz projection method for Sphere and Quad.
|
||||
if (entityType == EntityTypes::Gizmo) {
|
||||
normal = entityRotation * Vectors::UNIT_Y;
|
||||
} else {
|
||||
normal = entityRotation * Vectors::UNIT_Z;
|
||||
}
|
||||
float distance = glm::dot(pick.position - entityPosition, normal);
|
||||
glm::vec3 intersection = pick.position - (normal * distance);
|
||||
if (distance < nearestTarget.distance) {
|
||||
const auto entityDimensions = entity->getScaledDimensions();
|
||||
const auto entityRegistrationPoint = entity->getRegistrationPoint();
|
||||
glm::vec3 intersection = pick.position - (normal * distance);
|
||||
glm::vec2 pos2D;
|
||||
|
||||
glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(target, intersection, false);
|
||||
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
|
||||
IntersectionType type = IntersectionType::ENTITY;
|
||||
if (getFilter().doesPickLocalEntities()) {
|
||||
EntityPropertyFlags desiredProperties;
|
||||
desiredProperties += PROP_ENTITY_HOST_TYPE;
|
||||
if (DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(target, desiredProperties).getEntityHostType() == entity::HostType::LOCAL) {
|
||||
type = IntersectionType::LOCAL_ENTITY;
|
||||
}
|
||||
|
||||
auto entityType = entity->getType();
|
||||
|
||||
if (entityType == EntityTypes::Gizmo) {
|
||||
pos2D = RayPick::projectOntoXZPlane(intersection, entityPosition, entityRotation,
|
||||
entityDimensions, entityRegistrationPoint, false);
|
||||
} else {
|
||||
pos2D = RayPick::projectOntoXYPlane(intersection, entityPosition, entityRotation,
|
||||
entityDimensions, entityRegistrationPoint, false);
|
||||
}
|
||||
|
||||
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
|
||||
IntersectionType type = IntersectionType::ENTITY;
|
||||
if (getFilter().doesPickLocalEntities()) {
|
||||
if (entity->getEntityHostType() == entity::HostType::LOCAL) {
|
||||
type = IntersectionType::LOCAL_ENTITY;
|
||||
}
|
||||
}
|
||||
nearestTarget = StylusPickResult(type, target, distance, intersection, pick, normal);
|
||||
}
|
||||
results.push_back(StylusPickResult(type, target, distance, intersection, pick, normal));
|
||||
}
|
||||
}
|
||||
|
||||
StylusPickResult nearestTarget(pick.toVariantMap());
|
||||
for (const auto& result : results) {
|
||||
if (result.distance < nearestTarget.distance) {
|
||||
nearestTarget = result;
|
||||
}
|
||||
}
|
||||
return std::make_shared<StylusPickResult>(nearestTarget);
|
||||
}
|
||||
|
||||
|
|
|
@ -115,8 +115,8 @@ DownloadInfoResult::DownloadInfoResult() :
|
|||
/**jsdoc
|
||||
* Information on the assets currently being downloaded and pending download.
|
||||
* @typedef {object} AccountServices.DownloadInfoResult
|
||||
* @property {number[]} downloading - The percentage complete for each asset currently being downloaded.
|
||||
* @property {number} pending - The number of assets waiting to be download.
|
||||
* @property {number[]} downloading - The download percentage remaining of each asset currently downloading.
|
||||
* @property {number} pending - The number of assets pending download.
|
||||
*/
|
||||
QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) {
|
||||
QScriptValue object = engine->newObject();
|
||||
|
|
|
@ -38,19 +38,19 @@ class AccountServicesScriptingInterface : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
/**jsdoc
|
||||
* The <code>AccountServices</code> API provides functions related to user connectivity, visibility, and asset download
|
||||
* progress.
|
||||
* The <code>AccountServices</code> API provides functions that give information on user connectivity, visibility, and
|
||||
* asset download progress.
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @namespace AccountServices
|
||||
* @property {string} username - The user name if the user is logged in, otherwise <code>"Unknown user"</code>.
|
||||
* <em>Read-only.</em>
|
||||
* @property {string} username - The user name of the user logged in. If there is no user logged in, it is
|
||||
* <code>"Unknown user"</code>. <em>Read-only.</em>
|
||||
* @property {boolean} loggedIn - <code>true</code> if the user is logged in, otherwise <code>false</code>.
|
||||
* <em>Read-only.</em>
|
||||
* @property {string} findableBy - The user's visibility to other people:<br />
|
||||
* @property {string} findableBy - The user's visibility to other users:<br />
|
||||
* <code>"none"</code> - user appears offline.<br />
|
||||
* <code>"friends"</code> - user is visible only to friends.<br />
|
||||
* <code>"connections"</code> - user is visible to friends and connections.<br />
|
||||
|
@ -74,23 +74,23 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Get information on the progress of downloading assets in the domain.
|
||||
* Gets information on the download progress of assets in the domain.
|
||||
* @function AccountServices.getDownloadInfo
|
||||
* @returns {AccountServices.DownloadInfoResult} Information on the progress of assets download.
|
||||
* @returns {AccountServices.DownloadInfoResult} Information on the download progress of assets.
|
||||
*/
|
||||
DownloadInfoResult getDownloadInfo();
|
||||
|
||||
/**jsdoc
|
||||
* Cause a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal to be triggered with information on the
|
||||
* current progress of the download of assets in the domain.
|
||||
* Triggers a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal with information on the current
|
||||
* download progress of the assets in the domain.
|
||||
* @function AccountServices.updateDownloadInfo
|
||||
*/
|
||||
void updateDownloadInfo();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether the user is logged in.
|
||||
* Checks whether the user is logged in.
|
||||
* @function AccountServices.isLoggedIn
|
||||
* @returns {boolean} <code>true</code> if the user is logged in, <code>false</code> otherwise.
|
||||
* @returns {boolean} <code>true</code> if the user is logged in, <code>false</code> if not.
|
||||
* @example <caption>Report whether you are logged in.</caption>
|
||||
* var isLoggedIn = AccountServices.isLoggedIn();
|
||||
* print("You are logged in: " + isLoggedIn); // true or false
|
||||
|
@ -98,9 +98,9 @@ public slots:
|
|||
bool isLoggedIn();
|
||||
|
||||
/**jsdoc
|
||||
* Prompts the user to log in (the login dialog is displayed) if they're not already logged in.
|
||||
* The function returns the login status of the user and prompts the user to log in (with a login dialog) if they're not already logged in.
|
||||
* @function AccountServices.checkAndSignalForAccessToken
|
||||
* @returns {boolean} <code>true</code> if the user is already logged in, <code>false</code> otherwise.
|
||||
* @returns {boolean} <code>true</code> if the user is logged in, <code>false</code> if not.
|
||||
*/
|
||||
bool checkAndSignalForAccessToken();
|
||||
|
||||
|
@ -140,7 +140,7 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when the username logged in with changes, i.e., when the user logs in or out.
|
||||
* @function AccountServices.myUsernameChanged
|
||||
* @param {string} username - The username logged in with if the user is logged in, otherwise <code>""</code>.
|
||||
* @param {string} username - The user name of the user logged in. If there is no user logged in, it is <code>""</code>.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when your username changes.</caption>
|
||||
* AccountServices.myUsernameChanged.connect(function (username) {
|
||||
|
@ -150,9 +150,9 @@ signals:
|
|||
void myUsernameChanged(const QString& username);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the progress of the download of assets for the domain changes.
|
||||
* Triggered when the download progress of the assets in the domain changes.
|
||||
* @function AccountServices.downloadInfoChanged
|
||||
* @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the progress of assets download.
|
||||
* @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the download progress of assets.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void downloadInfoChanged(DownloadInfoResult info);
|
||||
|
@ -186,7 +186,7 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when the login status of the user changes.
|
||||
* @function AccountServices.loggedInChanged
|
||||
* @param {boolean} loggedIn - <code>true</code> if the user is logged in, otherwise <code>false</code>.
|
||||
* @param {boolean} loggedIn - <code>true</code> if the user is logged in, <code>false</code> if not.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when your login status changes.</caption>
|
||||
* AccountServices.loggedInChanged.connect(function(loggedIn) {
|
||||
|
|
|
@ -27,8 +27,9 @@ QString Audio::HMD { "VR" };
|
|||
Setting::Handle<bool> enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
|
||||
|
||||
float Audio::loudnessToLevel(float loudness) {
|
||||
float level = 6.02059991f * fastLog2f(loudness); // level in dBFS
|
||||
level = (level + 48.0f) * (1/39.0f); // map [-48, -9] dBFS to [0, 1]
|
||||
float level = loudness * (1/32768.0f); // level in [0, 1]
|
||||
level = 6.02059991f * fastLog2f(level); // convert to dBFS
|
||||
level = (level + 48.0f) * (1/42.0f); // map [-48, -6] dBFS to [0, 1]
|
||||
return glm::clamp(level, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ public:
|
|||
*
|
||||
* @property {WalletScriptingInterface.WalletStatus} walletStatus - The status of the user's wallet. <em>Read-only.</em>
|
||||
* @property {boolean} limitedCommerce - <code>true</code> if Interface is running in limited commerce mode. In limited commerce
|
||||
* mode, certain Interface functionality is disabled, e.g., users can't buy non-free items from the Marketplace. The Oculus
|
||||
* Store version of Interface runs in limited commerce mode. <em>Read-only.</em>
|
||||
* mode, certain Interface functionalities are disabled, e.g., users can't buy items that are not free from the Marketplace.
|
||||
* The Oculus Store version of Interface runs in limited commerce mode. <em>Read-only.</em>
|
||||
*/
|
||||
class WalletScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
@ -55,16 +55,16 @@ public:
|
|||
WalletScriptingInterface();
|
||||
|
||||
/**jsdoc
|
||||
* Check and update the user's wallet status.
|
||||
* Checks and updates the user's wallet status.
|
||||
* @function WalletScriptingInterface.refreshWalletStatus
|
||||
*/
|
||||
Q_INVOKABLE void refreshWalletStatus();
|
||||
|
||||
/**jsdoc
|
||||
* Get the current status of the user's wallet.
|
||||
* Gets the current status of the user's wallet.
|
||||
* @function WalletScriptingInterface.getWalletStatus
|
||||
* @returns {WalletScriptingInterface.WalletStatus}
|
||||
* @example <caption>Two ways to report your wallet status.</caption>
|
||||
* @example <caption>Use two methods to report your wallet's status.</caption>
|
||||
* print("Wallet status: " + WalletScriptingInterface.walletStatus); // Same value as next line.
|
||||
* print("Wallet status: " + WalletScriptingInterface.getWalletStatus());
|
||||
*/
|
||||
|
@ -74,11 +74,11 @@ public:
|
|||
* Check that a certified avatar entity is owned by the avatar whose entity it is. The result of the check is provided via
|
||||
* the {@link WalletScriptingInterface.ownershipVerificationSuccess|ownershipVerificationSuccess} and
|
||||
* {@link WalletScriptingInterface.ownershipVerificationFailed|ownershipVerificationFailed} signals.<br />
|
||||
* <strong>Warning:</strong> Neither of these signals fire if the entity is not an avatar entity or it's not a certified
|
||||
* entity.
|
||||
* <strong>Warning:</strong> Neither of these signals are triggered if the entity is not an avatar entity or is not
|
||||
* certified.
|
||||
* @function WalletScriptingInterface.proveAvatarEntityOwnershipVerification
|
||||
* @param {Uuid} entityID - The ID of the avatar entity to check.
|
||||
* @example <caption>Check ownership of all nearby certified avatar entities.</caption>
|
||||
* @param {Uuid} entityID - The avatar entity's ID.
|
||||
* @example <caption>Check the ownership of all nearby certified avatar entities.</caption>
|
||||
* // Set up response handling.
|
||||
* function ownershipSuccess(entityID) {
|
||||
* print("Ownership test succeeded for: " + entityID);
|
||||
|
@ -118,7 +118,7 @@ public:
|
|||
signals:
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the status of the user's wallet changes.
|
||||
* Triggered when the user's wallet status changes.
|
||||
* @function WalletScriptingInterface.walletStatusChanged
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when your wallet status changes, e.g., when you log in and out.</caption>
|
||||
|
@ -136,7 +136,7 @@ signals:
|
|||
void limitedCommerceChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user rezzes a certified entity but the user's wallet is not ready and so the certified location of the
|
||||
* Triggered when the user rezzes a certified entity but the user's wallet is not ready. So the certified location of the
|
||||
* entity cannot be updated in the metaverse.
|
||||
* @function WalletScriptingInterface.walletNotSetup
|
||||
* @returns {Signal}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue