mirror of
https://github.com/overte-org/overte.git
synced 2025-04-19 16:23:39 +02:00
Merge remote-tracking branch 'upstream/master' into 21324
This commit is contained in:
commit
bdcf9a0070
51 changed files with 1178 additions and 622 deletions
25
BUILD.md
25
BUILD.md
|
@ -1,4 +1,4 @@
|
|||
###Dependencies
|
||||
### Dependencies
|
||||
|
||||
* [cmake](https://cmake.org/download/) ~> 3.3.2
|
||||
* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2
|
||||
|
@ -6,7 +6,7 @@
|
|||
* IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
||||
####CMake External Project Dependencies
|
||||
#### CMake External Project Dependencies
|
||||
|
||||
* [boostconfig](https://github.com/boostorg/config) ~> 1.58
|
||||
* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83
|
||||
|
@ -30,16 +30,19 @@ These are not placed in your normal build tree when doing an out of source build
|
|||
|
||||
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
|
||||
|
||||
###OS Specific Build Guides
|
||||
### OS Specific Build Guides
|
||||
|
||||
* [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
|
||||
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
|
||||
* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
|
||||
* [BUILD_ANDROID.md](BUILD_ANDROID.md) - additional instructions for Android
|
||||
|
||||
###CMake
|
||||
### CMake
|
||||
|
||||
Hifi uses CMake to generate build files and project files for your platform.
|
||||
|
||||
####Qt
|
||||
#### Qt
|
||||
|
||||
In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation.
|
||||
|
||||
This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
|
@ -50,7 +53,8 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e
|
|||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
####Generating build files
|
||||
#### Generating build files
|
||||
|
||||
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
|
||||
|
||||
mkdir build
|
||||
|
@ -59,14 +63,15 @@ Create a build directory in the root of your checkout and then run the CMake bui
|
|||
|
||||
If cmake gives you the same error message repeatedly after the build fails (e.g. you had a typo in the QT_CMAKE_PREFIX_PATH that you fixed but the `.cmake` files still cannot be found), try removing `CMakeCache.txt`.
|
||||
|
||||
####Variables
|
||||
#### Variables
|
||||
|
||||
Any variables that need to be set for CMake to find dependencies can be set as ENV variables in your shell profile, or passed directly to CMake with a `-D` flag appended to the `cmake ..` command.
|
||||
|
||||
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
|
||||
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake
|
||||
|
||||
####Finding Dependencies
|
||||
#### Finding Dependencies
|
||||
|
||||
The following applies for dependencies we do not grab via CMake ExternalProject (OpenSSL is an example), or for dependencies you have opted not to grab as a CMake ExternalProject (via -DUSE_LOCAL_$NAME=0). The list of dependencies we grab by default as external projects can be found in [the CMake External Project Dependencies section](#cmake-external-project-dependencies).
|
||||
|
||||
|
@ -78,8 +83,8 @@ In the examples below the variable $NAME would be replaced by the name of the de
|
|||
* $NAME_ROOT_DIR - set this variable in your ENV
|
||||
* HIFI_LIB_DIR - set this variable in your ENV to your High Fidelity lib folder, should contain a folder '$name'
|
||||
|
||||
###Optional Components
|
||||
### Optional Components
|
||||
|
||||
####Devices
|
||||
#### Devices
|
||||
|
||||
You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file.
|
||||
|
||||
###Android Dependencies
|
||||
### Android Dependencies
|
||||
|
||||
You will need the following tools to build our Android targets.
|
||||
|
||||
|
@ -17,23 +17,23 @@ You will need the following tools to build our Android targets.
|
|||
|
||||
You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine.
|
||||
|
||||
####Scribe
|
||||
#### Scribe
|
||||
|
||||
High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable.
|
||||
|
||||
CMake will fatally error if it does not find the scribe executable while using the android toolchain.
|
||||
|
||||
####Optional Components
|
||||
#### Optional Components
|
||||
|
||||
* [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2
|
||||
|
||||
####ANDROID_LIB_DIR
|
||||
#### ANDROID_LIB_DIR
|
||||
|
||||
Since you won't be installing Android dependencies to system paths on your development machine, CMake will need a little help tracking down your Android dependencies.
|
||||
|
||||
This is most easily accomplished by installing all Android dependencies in the same folder. You can place this folder wherever you like on your machine. In this build guide and across our CMakeLists files this folder is referred to as `ANDROID_LIB_DIR`. You can set `ANDROID_LIB_DIR` in your environment or by passing when you run CMake.
|
||||
|
||||
####Qt
|
||||
#### Qt
|
||||
|
||||
Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation.
|
||||
|
||||
|
@ -41,7 +41,7 @@ The component required for the Android build is the `Android armv7` component.
|
|||
|
||||
If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`.
|
||||
|
||||
####OpenSSL
|
||||
#### OpenSSL
|
||||
|
||||
Cross-compilation of OpenSSL has been tested from an OS X machine running 10.10 compiling OpenSSL 1.0.2. It is likely that the steps below will work for other OpenSSL versions than 1.0.2.
|
||||
|
||||
|
@ -76,7 +76,7 @@ This should generate libcrypto and libssl in the root of the OpenSSL directory.
|
|||
|
||||
If you have been building other components it is possible that the OpenSSL compile will fail based on the values other cross-compilations (tbb, bullet) have set. Ensure that you are in a new terminal window to avoid compilation errors from previously set environment variables.
|
||||
|
||||
####Oculus Mobile SDK
|
||||
#### Oculus Mobile SDK
|
||||
|
||||
The Oculus Mobile SDK is optional, for Gear VR support. It is not required to compile gvr-interface.
|
||||
|
||||
|
@ -91,7 +91,7 @@ ndk-build
|
|||
|
||||
This will create the liboculus.a archive that our FindLibOVR module will look for when cmake is run.
|
||||
|
||||
#####Hybrid testing
|
||||
##### Hybrid testing
|
||||
|
||||
Currently the 'vr_dual' mode that would allow us to run a hybrid app has limited support in the Oculus Mobile SDK. The best way to have an application we can launch without having to connect to the GearVR is to put the Gear VR Service into developer mode. This stops Oculus Home from taking over the device when it is plugged into the Gear VR headset, and allows the application to be launched from the Applications page.
|
||||
|
||||
|
@ -99,7 +99,7 @@ To put the Gear VR Service into developer mode you need an application with an O
|
|||
|
||||
Once the application is on your device, go to `Settings->Application Manager->Gear VR Service->Manage Storage`. Tap on `VR Service Version` six times. It will scan your device to verify that you have an osig file in an application on your device, and then it will let you enable Developer mode.
|
||||
|
||||
###CMake
|
||||
### CMake
|
||||
|
||||
We use CMake to generate the makefiles that compile and deploy the Android APKs to your device. In order to create Makefiles for the Android targets, CMake requires that some environment variables are set, and that other variables are passed to it when it is run.
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.
|
||||
|
||||
###Qt5 Dependencies
|
||||
### Qt5 Dependencies
|
||||
|
||||
Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required:
|
||||
|
||||
libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev
|
||||
|
|
11
BUILD_OSX.md
11
BUILD_OSX.md
|
@ -1,12 +1,13 @@
|
|||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
|
||||
|
||||
###Homebrew
|
||||
### Homebrew
|
||||
|
||||
[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
|
||||
|
||||
brew tap homebrew/versions
|
||||
brew install cmake openssl
|
||||
|
||||
###OpenSSL
|
||||
### OpenSSL
|
||||
|
||||
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
|
||||
For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
|
||||
|
@ -15,7 +16,8 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
|
|||
|
||||
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
|
||||
|
||||
###Qt
|
||||
### Qt
|
||||
|
||||
Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg).
|
||||
|
||||
Keep the default components checked when going through the installer.
|
||||
|
@ -23,7 +25,8 @@ Keep the default components checked when going through the installer.
|
|||
Once Qt is installed, you need to manually configure the following:
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory.
|
||||
|
||||
###Xcode
|
||||
### Xcode
|
||||
|
||||
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
|
||||
|
||||
cmake .. -GXcode
|
||||
|
|
26
BUILD_WIN.md
26
BUILD_WIN.md
|
@ -1,32 +1,32 @@
|
|||
This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit.
|
||||
|
||||
###Step 1. Installing Visual Studio 2013
|
||||
### Step 1. Installing Visual Studio 2013
|
||||
|
||||
If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer.
|
||||
|
||||
Note: Newer versions of Visual Studio are not yet compatible.
|
||||
|
||||
###Step 2. Installing CMake
|
||||
### Step 2. Installing CMake
|
||||
|
||||
Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer.
|
||||
|
||||
###Step 3. Installing Qt
|
||||
### Step 3. Installing Qt
|
||||
|
||||
Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe).
|
||||
|
||||
Keep the default components checked when going through the installer.
|
||||
|
||||
###Step 4. Setting Qt Environment Variable
|
||||
### Step 4. Setting Qt Environment Variable
|
||||
|
||||
Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search).
|
||||
* Set "Variable name": QT_CMAKE_PREFIX_PATH
|
||||
* Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake`
|
||||
|
||||
###Step 5. Installing OpenSSL
|
||||
### Step 5. Installing OpenSSL
|
||||
|
||||
Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe).
|
||||
Download and install the [Win64 OpenSSL v1.0.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe).
|
||||
|
||||
###Step 6. Running CMake to Generate Build Files
|
||||
### Step 6. Running CMake to Generate Build Files
|
||||
|
||||
Run Command Prompt from Start and run the following commands:
|
||||
cd "%HIFI_DIR%"
|
||||
|
@ -36,7 +36,7 @@ Run Command Prompt from Start and run the following commands:
|
|||
|
||||
Where %HIFI_DIR% is the directory for the highfidelity repository.
|
||||
|
||||
###Step 7. Making a Build
|
||||
### Step 7. Making a Build
|
||||
|
||||
Open '%HIFI_DIR%\build\hifi.sln' using Visual Studio.
|
||||
|
||||
|
@ -44,7 +44,7 @@ Change the Solution Configuration (next to the green play button) from "Debug" t
|
|||
|
||||
Run Build > Build Solution.
|
||||
|
||||
###Step 8. Testing Interface
|
||||
### Step 8. Testing Interface
|
||||
|
||||
Create another environment variable (see Step #4)
|
||||
* Set "Variable name": _NO_DEBUG_HEAP
|
||||
|
@ -56,7 +56,7 @@ Now, you should have a full build of High Fidelity and be able to run the Interf
|
|||
|
||||
Note: You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Release\interface.exe
|
||||
|
||||
###Troubleshooting
|
||||
### Troubleshooting
|
||||
|
||||
For any problems after Step #6, first try this:
|
||||
* Delete your locally cloned copy of the highfidelity repository
|
||||
|
@ -64,18 +64,18 @@ For any problems after Step #6, first try this:
|
|||
* Redownload the [repository](https://github.com/highfidelity/hifi)
|
||||
* Restart directions from Step #6
|
||||
|
||||
####CMake gives you the same error message repeatedly after the build fails
|
||||
#### CMake gives you the same error message repeatedly after the build fails
|
||||
|
||||
Remove `CMakeCache.txt` found in the '%HIFI_DIR%\build' directory
|
||||
|
||||
####nmake cannot be found
|
||||
#### nmake cannot be found
|
||||
|
||||
Make sure nmake.exe is located at the following path:
|
||||
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin
|
||||
|
||||
If not, add the directory where nmake is located to the PATH environment variable.
|
||||
|
||||
####Qt is throwing an error
|
||||
#### Qt is throwing an error
|
||||
|
||||
Make sure you have the correct version (5.6.2) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly.
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@ Follow the [build guide](BUILD.md) to figure out how to build High Fidelity for
|
|||
|
||||
During generation, CMake should produce an `install` target and a `package` target.
|
||||
|
||||
###Install
|
||||
### Install
|
||||
|
||||
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
|
||||
|
||||
###Packaging
|
||||
### Packaging
|
||||
|
||||
To produce an installer, run the `package` target.
|
||||
|
||||
####Windows
|
||||
#### Windows
|
||||
|
||||
To produce an executable installer on Windows, the following are required:
|
||||
|
||||
|
@ -20,6 +20,6 @@ To produce an executable installer on Windows, the following are required:
|
|||
|
||||
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
|
||||
|
||||
####OS X
|
||||
#### OS X
|
||||
|
||||
Run the `package` target to create an Apple Disk Image (.dmg).
|
||||
|
|
|
@ -410,6 +410,7 @@ void Agent::executeScript() {
|
|||
bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened
|
||||
bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed
|
||||
_audioGateOpen = audioGateOpen;
|
||||
Q_UNUSED(openedInLastBlock);
|
||||
|
||||
// the codec must be flushed to silence before sending silent packets,
|
||||
// so delay the transition to silent packets by one packet after becoming silent.
|
||||
|
|
|
@ -35,35 +35,35 @@
|
|||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{
|
||||
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.RightFoot", "to" : "Standard.RightFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Hips", "to" : "Standard.Hips",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Spine2", "to" : "Standard.Spine2",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}],
|
||||
"when": [ "Application.InHMD"]
|
||||
"when": [ "Application.InHMD" ]
|
||||
},
|
||||
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] },
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm", "when" : [ "Application.InHMD"] },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when" : [ "Application.InHMD"] }
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -388,8 +388,13 @@ Rectangle {
|
|||
sortIndicatorColumn: settings.nearbySortIndicatorColumn;
|
||||
sortIndicatorOrder: settings.nearbySortIndicatorOrder;
|
||||
onSortIndicatorColumnChanged: {
|
||||
settings.nearbySortIndicatorColumn = sortIndicatorColumn;
|
||||
sortModel();
|
||||
if (sortIndicatorColumn > 2) {
|
||||
// these are not sortable, switch back to last column
|
||||
sortIndicatorColumn = settings.nearbySortIndicatorColumn;
|
||||
} else {
|
||||
settings.nearbySortIndicatorColumn = sortIndicatorColumn;
|
||||
sortModel();
|
||||
}
|
||||
}
|
||||
onSortIndicatorOrderChanged: {
|
||||
settings.nearbySortIndicatorOrder = sortIndicatorOrder;
|
||||
|
|
|
@ -136,7 +136,7 @@ Item {
|
|||
for (var i = 0; i < sections.length; i++) {
|
||||
totalHeight += sections[i].height + sections[i].getPreferencesHeight();
|
||||
}
|
||||
var bottomPadding = 100;
|
||||
var bottomPadding = 170;
|
||||
return (totalHeight + bottomPadding);
|
||||
}
|
||||
}
|
||||
|
|
BIN
interface/resources/snapshot/img/no-image.jpg
Normal file
BIN
interface/resources/snapshot/img/no-image.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -436,6 +436,10 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
|
||||
const int listenPort = portStr ? atoi(portStr) : INVALID_PORT;
|
||||
|
||||
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
|
||||
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
|
||||
bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
|
||||
|
||||
Setting::init();
|
||||
|
||||
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
|
||||
|
@ -456,10 +460,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
QCoreApplication::addLibraryPath(audioDLLPath);
|
||||
#endif
|
||||
|
||||
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
|
||||
bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
|
||||
bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
|
||||
|
@ -1459,6 +1459,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
updateSystemTabletMode();
|
||||
|
||||
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
|
||||
|
||||
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
||||
}
|
||||
|
||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||
|
|
|
@ -24,12 +24,15 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include <RunningMarker.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingHelpers.h>
|
||||
|
||||
|
||||
bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) {
|
||||
Settings settings;
|
||||
QSettings::setDefaultFormat(JSON_FORMAT);
|
||||
QSettings settings;
|
||||
settings.beginGroup("Developer");
|
||||
QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions);
|
||||
QVariant askToResetSettingsOption = settings.value(MenuOption::AskToResetSettings);
|
||||
|
@ -106,7 +109,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
return;
|
||||
}
|
||||
|
||||
Settings settings;
|
||||
QSettings settings;
|
||||
const QString ADDRESS_MANAGER_GROUP = "AddressManager";
|
||||
const QString ADDRESS_KEY = "address";
|
||||
const QString AVATAR_GROUP = "Avatar";
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include <QThread>
|
||||
|
||||
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends;
|
||||
const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections;
|
||||
|
||||
DiscoverabilityManager::DiscoverabilityManager() :
|
||||
_mode("discoverabilityMode", DEFAULT_DISCOVERABILITY_MODE)
|
||||
|
|
|
@ -2770,7 +2770,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
|
|||
auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
|
||||
return createMatFromQuatAndPos(leftFootRot, leftFootPos);
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2782,7 +2782,7 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const {
|
|||
auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
|
||||
return createMatFromQuatAndPos(rightFootRot, rightFootPos);
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2805,7 +2805,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const {
|
|||
auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex);
|
||||
return createMatFromQuatAndPos(leftArmRot, leftArmPos);
|
||||
} else {
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS);
|
||||
return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -444,6 +444,9 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
|
|||
|
||||
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||
|
||||
if (this->_pressed && event.getType() == PointerEvent::Move) {
|
||||
return;
|
||||
}
|
||||
// Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
|
||||
// FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times).
|
||||
// This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery".
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <QRunnable>
|
||||
#include <QThreadPool>
|
||||
#include <QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
@ -49,13 +51,43 @@ Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
|
|||
_isAmbisonic(isAmbisonic),
|
||||
_isReady(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Sound::downloadFinished(const QByteArray& data) {
|
||||
// this is a QRunnable, will delete itself after it has finished running
|
||||
SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic);
|
||||
connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess);
|
||||
connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError);
|
||||
QThreadPool::globalInstance()->start(soundProcessor);
|
||||
}
|
||||
|
||||
void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) {
|
||||
|
||||
qCDebug(audio) << "Setting ready state for sound file" << _url.toDisplayString();
|
||||
|
||||
_byteArray = data;
|
||||
_isStereo = stereo;
|
||||
_isAmbisonic = ambisonic;
|
||||
_duration = duration;
|
||||
_isReady = true;
|
||||
finishedLoading(true);
|
||||
|
||||
emit ready();
|
||||
}
|
||||
|
||||
void Sound::soundProcessError(int error, QString str) {
|
||||
qCCritical(audio) << "Failed to process sound file" << _url.toDisplayString() << "code =" << error << str;
|
||||
emit failed(QNetworkReply::UnknownContentError);
|
||||
finishedLoading(false);
|
||||
}
|
||||
|
||||
void SoundProcessor::run() {
|
||||
|
||||
qCDebug(audio) << "Processing sound file" << _url.toDisplayString();
|
||||
|
||||
// replace our byte array with the downloaded data
|
||||
QByteArray rawAudioByteArray = QByteArray(data);
|
||||
QString fileName = getURL().fileName().toLower();
|
||||
QByteArray rawAudioByteArray = QByteArray(_data);
|
||||
QString fileName = _url.fileName().toLower();
|
||||
|
||||
static const QString WAV_EXTENSION = ".wav";
|
||||
static const QString RAW_EXTENSION = ".raw";
|
||||
|
@ -72,31 +104,28 @@ void Sound::downloadFinished(const QByteArray& data) {
|
|||
// since it's raw the only way for us to know that is if the file was called .stereo.raw
|
||||
if (fileName.toLower().endsWith("stereo.raw")) {
|
||||
_isStereo = true;
|
||||
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
|
||||
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << _url << "as stereo audio file.";
|
||||
}
|
||||
|
||||
// Process as 48khz RAW file
|
||||
downSample(rawAudioByteArray, 48000);
|
||||
} else {
|
||||
qCDebug(audio) << "Unknown sound file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unknown sound file type");
|
||||
return;
|
||||
}
|
||||
|
||||
finishedLoading(true);
|
||||
|
||||
_isReady = true;
|
||||
emit ready();
|
||||
emit onSuccess(_data, _isStereo, _isAmbisonic, _duration);
|
||||
}
|
||||
|
||||
void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
||||
void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
||||
|
||||
// we want to convert it to the format that the audio-mixer wants
|
||||
// which is signed, 16-bit, 24Khz
|
||||
|
||||
if (sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
|
||||
// no resampling needed
|
||||
_byteArray = rawAudioByteArray;
|
||||
|
||||
_data = rawAudioByteArray;
|
||||
} else {
|
||||
|
||||
int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
|
@ -106,15 +135,15 @@ void Sound::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
|||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(maxDestinationBytes);
|
||||
_data.resize(maxDestinationBytes);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)_byteArray.data(),
|
||||
(int16_t*)_data.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_byteArray.resize(numDestinationBytes);
|
||||
_data.resize(numDestinationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +192,7 @@ struct WAVEFormat {
|
|||
};
|
||||
|
||||
// returns wavfile sample rate, used for resampling
|
||||
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
|
||||
// Create a data stream to analyze the data
|
||||
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef hifi_Sound_h
|
||||
#define hifi_Sound_h
|
||||
|
||||
#include <QRunnable>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtScript/qscriptengine.h>
|
||||
|
@ -28,12 +29,15 @@ public:
|
|||
bool isAmbisonic() const { return _isAmbisonic; }
|
||||
bool isReady() const { return _isReady; }
|
||||
float getDuration() const { return _duration; }
|
||||
|
||||
|
||||
const QByteArray& getByteArray() const { return _byteArray; }
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
|
||||
protected slots:
|
||||
void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void soundProcessError(int error, QString str);
|
||||
|
||||
private:
|
||||
QByteArray _byteArray;
|
||||
|
@ -42,10 +46,33 @@ private:
|
|||
bool _isReady;
|
||||
float _duration; // In seconds
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
};
|
||||
|
||||
class SoundProcessor : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic)
|
||||
: _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
|
||||
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
signals:
|
||||
void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void onError(int error, QString str);
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
QByteArray _data;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
float _duration;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Sound> SharedSoundPointer;
|
||||
|
|
|
@ -850,7 +850,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
glm::quat sensorToWorldQuat;
|
||||
unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat);
|
||||
float sensorToWorldScale;
|
||||
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||
// Grab a local copy of sensorToWorldScale to be able to use the unpack function with a pointer on it,
|
||||
// a direct pointer on the struct attribute triggers warnings because of potential misalignement.
|
||||
auto srcSensorToWorldScale = data->sensorToWorldScale;
|
||||
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&srcSensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||
glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]);
|
||||
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
|
||||
if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) {
|
||||
|
|
|
@ -42,8 +42,6 @@ enum class Action {
|
|||
|
||||
LEFT_HAND = NUM_COMBINED_AXES,
|
||||
RIGHT_HAND,
|
||||
LEFT_ARM,
|
||||
RIGHT_ARM,
|
||||
LEFT_FOOT,
|
||||
RIGHT_FOOT,
|
||||
HIPS,
|
||||
|
@ -103,6 +101,8 @@ enum class Action {
|
|||
// Bisected aliases for TRANSLATE_CAMERA_Z
|
||||
BOOM_IN,
|
||||
BOOM_OUT,
|
||||
LEFT_ARM,
|
||||
RIGHT_ARM,
|
||||
|
||||
|
||||
NUM_ACTIONS,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "InputRecorder.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
@ -19,9 +18,12 @@
|
|||
#include <QByteArray>
|
||||
#include <QStandardPaths>
|
||||
#include <PathUtils.h>
|
||||
#include <QUrl>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <DependencyManager.h>
|
||||
#include "UserInputMapper.h"
|
||||
|
||||
QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/";
|
||||
QString FILE_PREFIX_NAME = "input-recording-";
|
||||
|
@ -91,7 +93,7 @@ namespace controller {
|
|||
}
|
||||
|
||||
|
||||
void exportToFile(QJsonObject& object) {
|
||||
void exportToFile(const QJsonObject& object) {
|
||||
if (!QDir(SAVE_DIRECTORY).exists()) {
|
||||
QDir().mkdir(SAVE_DIRECTORY);
|
||||
}
|
||||
|
@ -108,6 +110,7 @@ namespace controller {
|
|||
QJsonDocument saveData(object);
|
||||
QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact));
|
||||
saveFile.write(compressedData);
|
||||
saveFile.close();
|
||||
}
|
||||
|
||||
QJsonObject openFile(const QString& file, bool& status) {
|
||||
|
@ -122,6 +125,7 @@ namespace controller {
|
|||
QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData);
|
||||
object = jsonDoc.object();
|
||||
status = true;
|
||||
openFile.close();
|
||||
return object;
|
||||
}
|
||||
|
||||
|
@ -146,31 +150,44 @@ namespace controller {
|
|||
_actionStateList.clear();
|
||||
}
|
||||
|
||||
void InputRecorder::saveRecording() {
|
||||
QJsonObject InputRecorder::recordDataToJson() {
|
||||
QJsonObject data;
|
||||
data["frameCount"] = _framesRecorded;
|
||||
|
||||
data["version"] = "1.0";
|
||||
|
||||
QJsonArray actionArrayList;
|
||||
QJsonArray poseArrayList;
|
||||
for(const ActionStates actionState: _actionStateList) {
|
||||
QJsonArray actionArray;
|
||||
for (const float value: actionState) {
|
||||
actionArray.append(value);
|
||||
for (const auto action: actionState) {
|
||||
QJsonObject actionJson;
|
||||
actionJson["name"] = action.first;
|
||||
actionJson["value"] = action.second;
|
||||
actionArray.append(actionJson);
|
||||
}
|
||||
actionArrayList.append(actionArray);
|
||||
}
|
||||
|
||||
for (const PoseStates poseState: _poseStateList) {
|
||||
QJsonArray poseArray;
|
||||
for (const Pose pose: poseState) {
|
||||
poseArray.append(poseToJsonObject(pose));
|
||||
for (const auto pose: poseState) {
|
||||
QJsonObject poseJson;
|
||||
poseJson["name"] = pose.first;
|
||||
poseJson["pose"] = poseToJsonObject(pose.second);
|
||||
poseArray.append(poseJson);
|
||||
}
|
||||
poseArrayList.append(poseArray);
|
||||
}
|
||||
|
||||
data["actionList"] = actionArrayList;
|
||||
data["poseList"] = poseArrayList;
|
||||
exportToFile(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void InputRecorder::saveRecording() {
|
||||
QJsonObject jsonData = recordDataToJson();
|
||||
exportToFile(jsonData);
|
||||
}
|
||||
|
||||
void InputRecorder::loadRecording(const QString& path) {
|
||||
|
@ -181,8 +198,8 @@ namespace controller {
|
|||
resetFrame();
|
||||
_poseStateList.clear();
|
||||
_actionStateList.clear();
|
||||
QString filePath = path;
|
||||
filePath.remove(0,8);
|
||||
QUrl urlPath(path);
|
||||
QString filePath = urlPath.toLocalFile();
|
||||
QFileInfo info(filePath);
|
||||
QString extension = info.suffix();
|
||||
if (extension != "gz") {
|
||||
|
@ -190,8 +207,9 @@ namespace controller {
|
|||
return;
|
||||
}
|
||||
bool success = false;
|
||||
QJsonObject data = openFile(info.absoluteFilePath(), success);
|
||||
if (success) {
|
||||
QJsonObject data = openFile(filePath, success);
|
||||
auto keyValue = data.find("version");
|
||||
if (success && keyValue != data.end()) {
|
||||
_framesRecorded = data["frameCount"].toInt();
|
||||
QJsonArray actionArrayList = data["actionList"].toArray();
|
||||
QJsonArray poseArrayList = data["poseList"].toArray();
|
||||
|
@ -199,30 +217,58 @@ namespace controller {
|
|||
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
|
||||
QJsonArray actionState = actionArrayList[actionIndex].toArray();
|
||||
for (int index = 0; index < actionState.size(); index++) {
|
||||
_currentFrameActions[index] = actionState[index].toDouble();
|
||||
QJsonObject actionObject = actionState[index].toObject();
|
||||
_currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble();
|
||||
}
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
_currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
|
||||
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
|
||||
QJsonArray poseState = poseArrayList[poseIndex].toArray();
|
||||
for (int index = 0; index < poseState.size(); index++) {
|
||||
_currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject());
|
||||
QJsonObject poseObject = poseState[index].toObject();
|
||||
_currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject());
|
||||
}
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
_currentFramePoses.clear();
|
||||
}
|
||||
} else if (success) {
|
||||
//convert recording to new reacording standard and rewrite file
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
_framesRecorded = data["frameCount"].toInt();
|
||||
QJsonArray actionArrayList = data["actionList"].toArray();
|
||||
QJsonArray poseArrayList = data["poseList"].toArray();
|
||||
|
||||
for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) {
|
||||
QJsonArray actionState = actionArrayList[actionIndex].toArray();
|
||||
for (int index = 0; index < actionState.size(); index++) {
|
||||
QString actionName = userInputMapper->getActionName(Action(index));
|
||||
_currentFrameActions[actionName] = actionState[index].toDouble();
|
||||
}
|
||||
_actionStateList.push_back(_currentFrameActions);
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
|
||||
for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) {
|
||||
QJsonArray poseState = poseArrayList[poseIndex].toArray();
|
||||
for (int index = 0; index < poseState.size(); index++) {
|
||||
QString actionName = userInputMapper->getActionName(Action(index));
|
||||
_currentFramePoses[actionName] = jsonObjectToPose(poseState[index].toObject());
|
||||
}
|
||||
_poseStateList.push_back(_currentFramePoses);
|
||||
_currentFramePoses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
|
||||
void InputRecorder::stopRecording() {
|
||||
_recording = false;
|
||||
_framesRecorded = (int)_actionStateList.size();
|
||||
}
|
||||
|
||||
|
||||
void InputRecorder::startPlayback() {
|
||||
_playback = true;
|
||||
_recording = false;
|
||||
|
@ -234,41 +280,36 @@ namespace controller {
|
|||
_playCount = 0;
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, float value) {
|
||||
void InputRecorder::setActionState(const QString& action, float value) {
|
||||
if (_recording) {
|
||||
_currentFrameActions[toInt(action)] += value;
|
||||
_currentFrameActions[action] += value;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) {
|
||||
void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) {
|
||||
if (_recording) {
|
||||
_currentFramePoses[toInt(action)] = pose;
|
||||
_currentFramePoses[action] = pose;
|
||||
}
|
||||
}
|
||||
|
||||
void InputRecorder::resetFrame() {
|
||||
if (_recording) {
|
||||
for(auto& channel : _currentFramePoses) {
|
||||
channel = Pose();
|
||||
}
|
||||
|
||||
for(auto& channel : _currentFrameActions) {
|
||||
channel = 0.0f;
|
||||
}
|
||||
_currentFramePoses.clear();
|
||||
_currentFrameActions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
float InputRecorder::getActionState(controller::Action action) {
|
||||
float InputRecorder::getActionState(const QString& action) {
|
||||
if (_actionStateList.size() > 0 ) {
|
||||
return _actionStateList[_playCount][toInt(action)];
|
||||
return _actionStateList[_playCount][action];
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
controller::Pose InputRecorder::getPoseState(controller::Action action) {
|
||||
controller::Pose InputRecorder::getPoseState(const QString& action) {
|
||||
if (_poseStateList.size() > 0) {
|
||||
return _poseStateList[_playCount][toInt(action)];
|
||||
return _poseStateList[_playCount][action];
|
||||
}
|
||||
|
||||
return Pose();
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "Pose.h"
|
||||
#include "Actions.h"
|
||||
|
@ -21,8 +23,8 @@
|
|||
namespace controller {
|
||||
class InputRecorder {
|
||||
public:
|
||||
using PoseStates = std::vector<Pose>;
|
||||
using ActionStates = std::vector<float>;
|
||||
using PoseStates = std::map<QString, Pose>;
|
||||
using ActionStates = std::map<QString, float>;
|
||||
|
||||
InputRecorder();
|
||||
~InputRecorder();
|
||||
|
@ -40,20 +42,21 @@ namespace controller {
|
|||
void resetFrame();
|
||||
bool isRecording() { return _recording; }
|
||||
bool isPlayingback() { return (_playback && !_loading); }
|
||||
void setActionState(controller::Action action, float value);
|
||||
void setActionState(controller::Action action, const controller::Pose pose);
|
||||
float getActionState(controller::Action action);
|
||||
controller::Pose getPoseState(controller::Action action);
|
||||
void setActionState(const QString& action, float value);
|
||||
void setActionState(const QString& action, const controller::Pose& pose);
|
||||
float getActionState(const QString& action);
|
||||
controller::Pose getPoseState(const QString& action);
|
||||
QString getSaveDirectory();
|
||||
void frameTick();
|
||||
private:
|
||||
QJsonObject recordDataToJson();
|
||||
bool _recording { false };
|
||||
bool _playback { false };
|
||||
bool _loading { false };
|
||||
std::vector<PoseStates> _poseStateList = std::vector<PoseStates>();
|
||||
std::vector<ActionStates> _actionStateList = std::vector<ActionStates>();
|
||||
PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS));
|
||||
ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS));
|
||||
PoseStates _currentFramePoses;
|
||||
ActionStates _currentFrameActions;
|
||||
|
||||
int _framesRecorded { 0 };
|
||||
int _playCount { 0 };
|
||||
|
|
|
@ -17,31 +17,33 @@ using namespace controller;
|
|||
|
||||
void ActionEndpoint::apply(float newValue, const Pointer& source) {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
if(inputRecorder->isPlayingback()) {
|
||||
newValue = inputRecorder->getActionState(Action(_input.getChannel()));
|
||||
newValue = inputRecorder->getActionState(actionName);
|
||||
}
|
||||
|
||||
_currentValue += newValue;
|
||||
if (_input != Input::INVALID_INPUT) {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->deltaActionState(Action(_input.getChannel()), newValue);
|
||||
}
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), newValue);
|
||||
inputRecorder->setActionState(actionName, newValue);
|
||||
}
|
||||
|
||||
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
|
||||
_currentPose = value;
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
inputRecorder->setActionState(Action(_input.getChannel()), _currentPose);
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
inputRecorder->setActionState(actionName, _currentPose);
|
||||
if (inputRecorder->isPlayingback()) {
|
||||
_currentPose = inputRecorder->getPoseState(Action(_input.getChannel()));
|
||||
_currentPose = inputRecorder->getPoseState(actionName);
|
||||
}
|
||||
|
||||
if (!_currentPose.isValid()) {
|
||||
return;
|
||||
}
|
||||
if (_input != Input::INVALID_INPUT) {
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
userInputMapper->setActionState(Action(_input.getChannel()), _currentPose);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ namespace controller {
|
|||
LowVelocityFilter(float rotationConstant, float translationConstant) :
|
||||
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
|
||||
|
||||
virtual float apply(float value) const override { return value; }
|
||||
virtual Pose apply(Pose newPose) const;
|
||||
virtual bool parseParameters(const QJsonValue& parameters) override;
|
||||
float apply(float value) const override { return value; }
|
||||
Pose apply(Pose newPose) const override;
|
||||
bool parseParameters(const QJsonValue& parameters) override;
|
||||
|
||||
private:
|
||||
float _translationConstant { 0.1f };
|
||||
|
|
|
@ -192,7 +192,7 @@ void EntityTreeRenderer::update() {
|
|||
tree->update();
|
||||
|
||||
// Handle enter/leave entity logic
|
||||
bool updated = checkEnterLeaveEntities();
|
||||
checkEnterLeaveEntities();
|
||||
|
||||
// Even if we're not moving the mouse, if we started clicking on an entity and we have
|
||||
// not yet released the hold then this is still considered a holdingClickOnEntity event
|
||||
|
|
|
@ -367,7 +367,6 @@ void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transacti
|
|||
|
||||
bool sunChanged = _keyLightPropertiesChanged;
|
||||
bool backgroundChanged = _backgroundPropertiesChanged;
|
||||
bool stageChanged = _stagePropertiesChanged;
|
||||
bool skyboxChanged = _skyboxPropertiesChanged;
|
||||
|
||||
transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [=](RenderableZoneEntityItemMeta& data) {
|
||||
|
|
|
@ -211,6 +211,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
|
|||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -484,6 +485,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.internalFormat = GL_R8_SNORM;
|
||||
break;
|
||||
}
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: { // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -527,6 +529,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.internalFormat = GL_DEPTH_COMPONENT24;
|
||||
break;
|
||||
}
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: { // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -641,6 +644,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -211,6 +211,8 @@ namespace khronos {
|
|||
|
||||
template <uint32_t ALIGNMENT>
|
||||
inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) {
|
||||
enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) };
|
||||
static_assert(val, "template parameter ALIGNMENT must be a power of 2");
|
||||
// FIXME add static assert that ALIGNMENT is a power of 2
|
||||
static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1;
|
||||
return (value + ALIGNMENT_REMAINDER) / ALIGNMENT;
|
||||
|
|
|
@ -229,7 +229,7 @@ namespace ktx {
|
|||
} else {
|
||||
Image::FaceBytes faceBytes(NUM_CUBEMAPFACES);
|
||||
auto faceSize = srcImages[l]._faceSize;
|
||||
for (int face = 0; face < NUM_CUBEMAPFACES; face++) {
|
||||
for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) {
|
||||
memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize);
|
||||
faceBytes[face] = currentPtr;
|
||||
currentPtr += faceSize;
|
||||
|
|
|
@ -201,6 +201,13 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
|
|||
}
|
||||
}
|
||||
|
||||
void AccountManager::setSessionID(const QUuid& sessionID) {
|
||||
if (_sessionID != sessionID) {
|
||||
qCDebug(networking) << "Metaverse session ID changed to" << uuidStringWithoutCurlyBraces(sessionID);
|
||||
_sessionID = sessionID;
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::sendRequest(const QString& path,
|
||||
AccountManagerAuth::Type authType,
|
||||
QNetworkAccessManager::Operation operation,
|
||||
|
|
|
@ -90,7 +90,7 @@ public:
|
|||
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);
|
||||
|
||||
QUuid getSessionID() const { return _sessionID; }
|
||||
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
||||
void setSessionID(const QUuid& sessionID);
|
||||
|
||||
void setTemporaryDomain(const QUuid& domainID, const QString& key);
|
||||
const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }
|
||||
|
|
|
@ -11,52 +11,81 @@
|
|||
|
||||
#include "FileCache.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <QDir>
|
||||
#include <QSaveFile>
|
||||
#include <unordered_set>
|
||||
#include <queue>
|
||||
#include <cassert>
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QSaveFile>
|
||||
#include <QtCore/QStorageInfo>
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <sys/utime.h>
|
||||
#else
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
#ifdef NDEBUG
|
||||
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg)
|
||||
|
||||
#else
|
||||
Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache")
|
||||
#endif
|
||||
using namespace cache;
|
||||
|
||||
static const std::string MANIFEST_NAME = "manifest";
|
||||
static const char DIR_SEP = '/';
|
||||
static const char EXT_SEP = '.';
|
||||
|
||||
static const size_t BYTES_PER_MEGABYTES = 1024 * 1024;
|
||||
static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;
|
||||
const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB
|
||||
const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB
|
||||
const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB
|
||||
const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) };
|
||||
const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) };
|
||||
const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) };
|
||||
|
||||
void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) {
|
||||
_unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE);
|
||||
reserve(0);
|
||||
|
||||
std::string getCacheName(const std::string& dirname_str) {
|
||||
QString dirname { dirname_str.c_str() };
|
||||
QDir dir(dirname);
|
||||
if (!dir.isAbsolute()) {
|
||||
return dirname_str;
|
||||
}
|
||||
return dir.dirName().toStdString();
|
||||
}
|
||||
|
||||
std::string getCachePath(const std::string& dirname_str) {
|
||||
QString dirname { dirname_str.c_str() };
|
||||
QDir dir(dirname);
|
||||
if (dir.isAbsolute()) {
|
||||
return dirname_str;
|
||||
}
|
||||
return PathUtils::getAppLocalDataFilePath(dirname).toStdString();
|
||||
}
|
||||
|
||||
void FileCache::setMinFreeSize(size_t size) {
|
||||
_minFreeSpaceSize = size;
|
||||
clean();
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) {
|
||||
_offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE);
|
||||
void FileCache::setMaxSize(size_t maxSize) {
|
||||
_maxSize = std::min(maxSize, MAX_MAX_SIZE);
|
||||
clean();
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) :
|
||||
QObject(parent),
|
||||
_ext(ext),
|
||||
_dirname(dirname),
|
||||
_dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {}
|
||||
_dirname(getCacheName(dirname)),
|
||||
_dirpath(getCachePath(dirname)) {
|
||||
}
|
||||
|
||||
FileCache::~FileCache() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void fileDeleter(File* file) {
|
||||
file->deleter();
|
||||
}
|
||||
|
||||
void FileCache::initialize() {
|
||||
QDir dir(_dirpath.c_str());
|
||||
|
||||
|
@ -84,7 +113,7 @@ void FileCache::initialize() {
|
|||
}
|
||||
|
||||
FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) {
|
||||
FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter);
|
||||
FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter));
|
||||
if (file) {
|
||||
_numTotalFiles += 1;
|
||||
_totalFilesSize += file->getLength();
|
||||
|
@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) {
|
|||
if (it != _files.cend()) {
|
||||
file = it->second.lock();
|
||||
if (file) {
|
||||
file->touch();
|
||||
// if it exists, it is active - remove it from the cache
|
||||
removeUnusedFile(file);
|
||||
qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str());
|
||||
|
@ -155,44 +185,74 @@ FilePointer FileCache::getFile(const Key& key) {
|
|||
}
|
||||
|
||||
std::string FileCache::getFilepath(const Key& key) {
|
||||
return _dirpath + '/' + key + '.' + _ext;
|
||||
return _dirpath + DIR_SEP + key + EXT_SEP + _ext;
|
||||
}
|
||||
|
||||
void FileCache::addUnusedFile(const FilePointer file) {
|
||||
void FileCache::addUnusedFile(const FilePointer& file) {
|
||||
{
|
||||
Lock lock(_filesMutex);
|
||||
_files[file->getKey()] = file;
|
||||
}
|
||||
|
||||
reserve(file->getLength());
|
||||
file->_LRUKey = ++_lastLRUKey;
|
||||
|
||||
{
|
||||
Lock lock(_unusedFilesMutex);
|
||||
_unusedFiles.insert({ file->_LRUKey, file });
|
||||
_unusedFiles.insert(file);
|
||||
_numUnusedFiles += 1;
|
||||
_unusedFilesSize += file->getLength();
|
||||
}
|
||||
clean();
|
||||
|
||||
emit dirty();
|
||||
}
|
||||
|
||||
void FileCache::removeUnusedFile(const FilePointer file) {
|
||||
void FileCache::removeUnusedFile(const FilePointer& file) {
|
||||
Lock lock(_unusedFilesMutex);
|
||||
const auto it = _unusedFiles.find(file->_LRUKey);
|
||||
if (it != _unusedFiles.cend()) {
|
||||
_unusedFiles.erase(it);
|
||||
if (_unusedFiles.erase(file)) {
|
||||
_numUnusedFiles -= 1;
|
||||
_unusedFilesSize -= file->getLength();
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::reserve(size_t length) {
|
||||
size_t FileCache::getOverbudgetAmount() const {
|
||||
size_t result = 0;
|
||||
|
||||
size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree();
|
||||
if (_minFreeSpaceSize > currentFreeSpace) {
|
||||
result = _minFreeSpaceSize - currentFreeSpace;
|
||||
}
|
||||
|
||||
if (_totalFilesSize > _maxSize) {
|
||||
result = std::max(_totalFilesSize - _maxSize, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace cache {
|
||||
struct FilePointerComparator {
|
||||
bool operator()(const FilePointer& a, const FilePointer& b) {
|
||||
return a->_modified > b->_modified;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void FileCache::clean() {
|
||||
size_t overbudgetAmount = getOverbudgetAmount();
|
||||
|
||||
// Avoid sorting the unused files by LRU if we're not over budget / under free space
|
||||
if (0 == overbudgetAmount) {
|
||||
return;
|
||||
}
|
||||
|
||||
Lock unusedLock(_unusedFilesMutex);
|
||||
while (!_unusedFiles.empty() &&
|
||||
_unusedFilesSize + length > _unusedFilesMaxSize) {
|
||||
auto it = _unusedFiles.begin();
|
||||
auto file = it->second;
|
||||
using Queue = std::priority_queue<FilePointer, std::vector<FilePointer>, FilePointerComparator>;
|
||||
Queue queue;
|
||||
for (const auto& file : _unusedFiles) {
|
||||
queue.push(file);
|
||||
}
|
||||
while (!queue.empty() && overbudgetAmount > 0) {
|
||||
auto file = queue.top();
|
||||
queue.pop();
|
||||
auto length = file->getLength();
|
||||
|
||||
unusedLock.unlock();
|
||||
|
@ -203,34 +263,32 @@ void FileCache::reserve(size_t length) {
|
|||
}
|
||||
unusedLock.lock();
|
||||
|
||||
_unusedFiles.erase(it);
|
||||
_unusedFiles.erase(file);
|
||||
_numTotalFiles -= 1;
|
||||
_numUnusedFiles -= 1;
|
||||
_totalFilesSize -= length;
|
||||
_unusedFilesSize -= length;
|
||||
overbudgetAmount -= std::min(length, overbudgetAmount);
|
||||
}
|
||||
}
|
||||
|
||||
void FileCache::clear() {
|
||||
Lock unusedFilesLock(_unusedFilesMutex);
|
||||
for (const auto& pair : _unusedFiles) {
|
||||
auto& file = pair.second;
|
||||
file->_cache = nullptr;
|
||||
// Eliminate any overbudget files
|
||||
clean();
|
||||
|
||||
if (_totalFilesSize > _offlineFilesMaxSize) {
|
||||
_totalFilesSize -= file->getLength();
|
||||
} else {
|
||||
file->_shouldPersist = true;
|
||||
qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
|
||||
}
|
||||
// Mark everything remaining as persisted
|
||||
Lock unusedFilesLock(_unusedFilesMutex);
|
||||
for (auto& file : _unusedFiles) {
|
||||
file->_shouldPersist = true;
|
||||
file->_cache = nullptr;
|
||||
qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str());
|
||||
}
|
||||
_unusedFiles.clear();
|
||||
}
|
||||
|
||||
void File::deleter() {
|
||||
if (_cache) {
|
||||
FilePointer self(this, &fileDeleter);
|
||||
_cache->addUnusedFile(self);
|
||||
_cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter)));
|
||||
} else {
|
||||
deleteLater();
|
||||
}
|
||||
|
@ -239,7 +297,9 @@ void File::deleter() {
|
|||
File::File(Metadata&& metadata, const std::string& filepath) :
|
||||
_key(std::move(metadata.key)),
|
||||
_length(metadata.length),
|
||||
_filepath(filepath) {}
|
||||
_filepath(filepath),
|
||||
_modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
QFile file(getFilepath().c_str());
|
||||
|
@ -248,3 +308,8 @@ File::~File() {
|
|||
file.remove();
|
||||
}
|
||||
}
|
||||
|
||||
void File::touch() {
|
||||
utime(_filepath.c_str(), nullptr);
|
||||
_modified = std::max<int64_t>(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified);
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
@ -35,24 +36,28 @@ class FileCache : public QObject {
|
|||
Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty)
|
||||
Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty)
|
||||
|
||||
static const size_t DEFAULT_UNUSED_MAX_SIZE;
|
||||
static const size_t MAX_UNUSED_MAX_SIZE;
|
||||
static const size_t DEFAULT_OFFLINE_MAX_SIZE;
|
||||
static const size_t DEFAULT_MAX_SIZE;
|
||||
static const size_t MAX_MAX_SIZE;
|
||||
static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE;
|
||||
|
||||
public:
|
||||
// You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path
|
||||
// The file cache will ignore any file that doesn't match the extension provided
|
||||
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
|
||||
virtual ~FileCache();
|
||||
|
||||
size_t getNumTotalFiles() const { return _numTotalFiles; }
|
||||
size_t getNumCachedFiles() const { return _numUnusedFiles; }
|
||||
size_t getSizeTotalFiles() const { return _totalFilesSize; }
|
||||
size_t getSizeCachedFiles() const { return _unusedFilesSize; }
|
||||
|
||||
void setUnusedFileCacheSize(size_t unusedFilesMaxSize);
|
||||
size_t getUnusedFileCacheSize() const { return _unusedFilesSize; }
|
||||
// Set the maximum amount of disk space to use on disk
|
||||
void setMaxSize(size_t maxCacheSize);
|
||||
|
||||
void setOfflineFileCacheSize(size_t offlineFilesMaxSize);
|
||||
|
||||
// initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg")
|
||||
FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr);
|
||||
virtual ~FileCache();
|
||||
// Set the minumum amount of free disk space to retain. This supercedes the max size,
|
||||
// so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected
|
||||
// to free up more space, regardless of the cache max size
|
||||
void setMinFreeSize(size_t size);
|
||||
|
||||
using Key = std::string;
|
||||
struct Metadata {
|
||||
|
@ -76,10 +81,11 @@ public:
|
|||
signals:
|
||||
void dirty();
|
||||
|
||||
protected:
|
||||
public:
|
||||
/// must be called after construction to create the cache on the fs and restore persisted files
|
||||
void initialize();
|
||||
|
||||
// Add file to the cache and return the cache entry.
|
||||
FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false);
|
||||
FilePointer getFile(const Key& key);
|
||||
|
||||
|
@ -95,11 +101,17 @@ private:
|
|||
std::string getFilepath(const Key& key);
|
||||
|
||||
FilePointer addFile(Metadata&& metadata, const std::string& filepath);
|
||||
void addUnusedFile(const FilePointer file);
|
||||
void removeUnusedFile(const FilePointer file);
|
||||
void reserve(size_t length);
|
||||
void addUnusedFile(const FilePointer& file);
|
||||
void removeUnusedFile(const FilePointer& file);
|
||||
void clean();
|
||||
void clear();
|
||||
|
||||
size_t getOverbudgetAmount() const;
|
||||
|
||||
// FIXME it might be desirable to have the min free space variable be static so it can be
|
||||
// shared among multiple instances of FileCache
|
||||
std::atomic<size_t> _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE };
|
||||
std::atomic<size_t> _maxSize { DEFAULT_MAX_SIZE };
|
||||
std::atomic<size_t> _numTotalFiles { 0 };
|
||||
std::atomic<size_t> _numUnusedFiles { 0 };
|
||||
std::atomic<size_t> _totalFilesSize { 0 };
|
||||
|
@ -113,12 +125,8 @@ private:
|
|||
std::unordered_map<Key, std::weak_ptr<File>> _files;
|
||||
Mutex _filesMutex;
|
||||
|
||||
std::map<int, FilePointer> _unusedFiles;
|
||||
std::unordered_set<FilePointer> _unusedFiles;
|
||||
Mutex _unusedFilesMutex;
|
||||
size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE };
|
||||
int _lastLRUKey { 0 };
|
||||
|
||||
size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE };
|
||||
};
|
||||
|
||||
class File : public QObject {
|
||||
|
@ -142,13 +150,15 @@ protected:
|
|||
|
||||
private:
|
||||
friend class FileCache;
|
||||
friend struct FilePointerComparator;
|
||||
|
||||
const Key _key;
|
||||
const size_t _length;
|
||||
const std::string _filepath;
|
||||
|
||||
FileCache* _cache;
|
||||
int _LRUKey { 0 };
|
||||
void touch();
|
||||
FileCache* _cache { nullptr };
|
||||
int64_t _modified { 0 };
|
||||
|
||||
bool _shouldPersist { false };
|
||||
};
|
||||
|
|
|
@ -241,6 +241,9 @@ void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) {
|
|||
std::lock_guard<std::mutex> locker { _handshakeMutex };
|
||||
_hasReceivedHandshakeACK = true;
|
||||
}
|
||||
|
||||
_lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
// Notify on the handshake ACK condition
|
||||
_handshakeACKCondition.notify_one();
|
||||
}
|
||||
|
|
|
@ -112,7 +112,6 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext,
|
|||
skybox->render(batch, args->getViewFrustum());
|
||||
});
|
||||
args->_batch = nullptr;
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
// break;
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
|
|||
batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer());
|
||||
|
||||
batch.setPipeline(getKeyLightPipeline());
|
||||
auto numKeys = keyLightStack.size();
|
||||
auto numKeys = (int) keyLightStack.size();
|
||||
for (int i = numKeys - 1; i >= 0; i--) {
|
||||
model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
|
||||
batch.setModelTransform(model);
|
||||
|
@ -180,7 +180,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
|
|||
}
|
||||
|
||||
batch.setPipeline(getAmbientPipeline());
|
||||
auto numAmbients = ambientLightStack.size();
|
||||
auto numAmbients = (int) ambientLightStack.size();
|
||||
for (int i = numAmbients - 1; i >= 0; i--) {
|
||||
model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
|
||||
batch.setModelTransform(model);
|
||||
|
@ -194,7 +194,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I
|
|||
}
|
||||
|
||||
batch.setPipeline(getBackgroundPipeline());
|
||||
auto numBackgrounds = skyboxStack.size();
|
||||
auto numBackgrounds = (int) skyboxStack.size();
|
||||
for (int i = numBackgrounds - 1; i >= 0; i--) {
|
||||
model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0)));
|
||||
batch.setModelTransform(model);
|
||||
|
|
|
@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000;
|
|||
|
||||
#define KB_TO_BYTES_SHIFT 10
|
||||
#define MB_TO_BYTES_SHIFT 20
|
||||
#define GB_TO_BYTES_SHIFT 30
|
||||
|
||||
#define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT)
|
||||
#define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT)
|
||||
#define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT)
|
||||
|
||||
#define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT)
|
||||
#define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT)
|
||||
#define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT)
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <PerfStat.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <gpu/gl/GLbackend.h>
|
||||
#include <gpu/gl/GLBackend.h>
|
||||
|
||||
#include <ui-plugins/PluginContainer.h>
|
||||
|
||||
|
|
|
@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers";
|
|||
static const int MIN_PUCK_COUNT = 2;
|
||||
static const int MIN_FEET_AND_HIPS = 3;
|
||||
static const int MIN_FEET_HIPS_CHEST = 4;
|
||||
static const int MIN_FEET_HIPS_HEAD = 4;
|
||||
static const int MIN_FEET_HIPS_SHOULDERS = 5;
|
||||
static const int MIN_FEET_HIPS_CHEST_HEAD = 5;
|
||||
static const int FIRST_FOOT = 0;
|
||||
static const int SECOND_FOOT = 1;
|
||||
static const int HIP = 2;
|
||||
static const int CHEST = 3;
|
||||
static float HEAD_PUCK_Y_OFFSET = -0.0254f;
|
||||
static float HEAD_PUCK_Z_OFFSET = -0.152f;
|
||||
|
||||
const char* ViveControllerManager::NAME { "OpenVR" };
|
||||
|
||||
|
@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck)
|
|||
return (firstPuck.second.translation.x < secondPuck.second.translation.x);
|
||||
}
|
||||
|
||||
static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) {
|
||||
glm::vec3 poseAPosition = poseA.getTranslation();
|
||||
glm::vec3 poseBPosition = poseB.getTranslation();
|
||||
|
||||
glm::vec3 poseAVector = poseAPosition - axisOrigin;
|
||||
glm::vec3 poseBVector = poseBPosition - axisOrigin;
|
||||
|
||||
float poseAProjection = glm::dot(poseAVector, axis);
|
||||
float poseBProjection = glm::dot(poseBVector, axis);
|
||||
return (poseAProjection > poseBProjection);
|
||||
}
|
||||
|
||||
static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
|
||||
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
|
||||
return glmExtractRotation(finalHead) * Vectors::UNIT_X;
|
||||
}
|
||||
|
||||
static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) {
|
||||
glm::mat4 finalHead = defaultToReferenceMat * defaultHead;
|
||||
return extractTranslation(finalHead);
|
||||
}
|
||||
|
||||
static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) {
|
||||
QString result;
|
||||
auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult);
|
||||
|
@ -175,6 +201,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro
|
|||
_configStringMap[Config::FeetAndHips] = QString("FeetAndHips");
|
||||
_configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest");
|
||||
_configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders");
|
||||
_configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead");
|
||||
_configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead");
|
||||
|
||||
if (openVrSupported()) {
|
||||
createPreferences();
|
||||
|
@ -355,6 +383,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr
|
|||
int firstShoulderIndex = 3;
|
||||
int secondShoulderIndex = 4;
|
||||
calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex);
|
||||
} else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) {
|
||||
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
|
||||
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
|
||||
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
_overrideHead = true;
|
||||
} else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) {
|
||||
glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration);
|
||||
glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat);
|
||||
calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition);
|
||||
calibrateHips(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateChest(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
calibrateHead(headPuckDefaultToReferenceMat, inputCalibration);
|
||||
_overrideHead = true;
|
||||
} else {
|
||||
qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks";
|
||||
uncalibrate();
|
||||
|
@ -369,6 +414,7 @@ void ViveControllerManager::InputDevice::uncalibrate() {
|
|||
_pucksOffset.clear();
|
||||
_jointToPuckMap.clear();
|
||||
_calibrated = false;
|
||||
_overrideHead = false;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
|
||||
|
@ -378,6 +424,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() {
|
|||
_poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2);
|
||||
_poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM);
|
||||
_poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM);
|
||||
|
||||
if (_overrideHead) {
|
||||
_poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD);
|
||||
}
|
||||
}
|
||||
|
||||
controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const {
|
||||
|
@ -443,6 +493,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u
|
|||
}
|
||||
}
|
||||
|
||||
glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) {
|
||||
glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat;
|
||||
glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat;
|
||||
size_t headPuckIndex = _validTrackedObjects.size() - 1;
|
||||
controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second;
|
||||
glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180;
|
||||
glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat);
|
||||
glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
|
||||
// check that the head puck z axis is not parrallel to the world up
|
||||
const float EPSILON = 1.0e-4f;
|
||||
glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) {
|
||||
headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis));
|
||||
glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime));
|
||||
glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
|
||||
glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f));
|
||||
|
||||
glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f));
|
||||
|
||||
glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset;
|
||||
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
|
||||
|
||||
glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset;
|
||||
|
||||
// calculate the defaultToRefrenceXform
|
||||
glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat);
|
||||
return defaultToReferenceMat;
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) {
|
||||
// Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values.
|
||||
const float CENTER_DEADBAND = 0.6f;
|
||||
|
@ -630,6 +717,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) {
|
||||
auto& firstFoot = _validTrackedObjects[FIRST_FOOT];
|
||||
auto& secondFoot = _validTrackedObjects[SECOND_FOOT];
|
||||
controller::Pose& firstFootPose = firstFoot.second;
|
||||
controller::Pose& secondFootPose = secondFoot.second;
|
||||
|
||||
if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) {
|
||||
_jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first;
|
||||
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose);
|
||||
_jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first;
|
||||
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose);
|
||||
} else {
|
||||
_jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first;
|
||||
_pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose);
|
||||
_jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first;
|
||||
_pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose);
|
||||
}
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
|
||||
_jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first;
|
||||
_pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second);
|
||||
|
@ -658,7 +765,19 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo
|
|||
_jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first;
|
||||
_pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
|
||||
size_t headIndex = _validTrackedObjects.size() - 1;
|
||||
const PuckPosePair& head = _validTrackedObjects[headIndex];
|
||||
|
||||
// assume the person is wearing the head puck on his/her forehead
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat;
|
||||
controller::Pose newHead = head.second.postTransform(defaultHeadOffset);
|
||||
|
||||
_jointToPuckMap[controller::HEAD] = head.first;
|
||||
_pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead);
|
||||
}
|
||||
|
||||
|
||||
void ViveControllerManager::InputDevice::loadSettings() {
|
||||
|
@ -694,6 +813,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
|
|||
_preferedConfig = Config::FeetHipsAndChest;
|
||||
} else if (value == "FeetHipsAndShoulders") {
|
||||
_preferedConfig = Config::FeetHipsAndShoulders;
|
||||
} else if (value == "FeetHipsChestAndHead") {
|
||||
_preferedConfig = Config::FeetHipsChestAndHead;
|
||||
} else if (value == "FeetHipsAndHead") {
|
||||
_preferedConfig = Config::FeetHipsAndHead;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -702,11 +825,43 @@ void ViveControllerManager::InputDevice::createPreferences() {
|
|||
auto preferences = DependencyManager::get<Preferences>();
|
||||
static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration";
|
||||
|
||||
{
|
||||
static const float MIN_VALUE = -3.0f;
|
||||
static const float MAX_VALUE = 3.0f;
|
||||
static const float STEP = 0.01f;
|
||||
|
||||
auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; };
|
||||
auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; };
|
||||
|
||||
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter);
|
||||
preference->setMin(MIN_VALUE);
|
||||
preference->setMax(MAX_VALUE);
|
||||
preference->setDecimals(3);
|
||||
preference->setStep(STEP);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
static const float MIN_VALUE = -3.0f;
|
||||
static const float MAX_VALUE = 3.0f;
|
||||
static const float STEP = 0.01f;
|
||||
|
||||
auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; };
|
||||
auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; };
|
||||
|
||||
auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter);
|
||||
preference->setMin(MIN_VALUE);
|
||||
preference->setMax(MAX_VALUE);
|
||||
preference->setStep(STEP);
|
||||
preference->setDecimals(3);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; };
|
||||
auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); };
|
||||
auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter);
|
||||
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"};
|
||||
QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"};
|
||||
preference->setItems(list);
|
||||
preferences->addPreference(preference);
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ private:
|
|||
void calibrate(const controller::InputCalibrationData& inputCalibration);
|
||||
void uncalibrate();
|
||||
controller::Pose addOffsetToPuckPose(int joint) const;
|
||||
glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration);
|
||||
void updateCalibratedLimbs();
|
||||
bool checkForCalibrationEvent();
|
||||
void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand);
|
||||
|
@ -84,11 +85,14 @@ private:
|
|||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition);
|
||||
void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
|
||||
void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration,
|
||||
int firstShoulderIndex, int secondShoulderIndex);
|
||||
void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration);
|
||||
|
||||
|
||||
class FilteredStick {
|
||||
public:
|
||||
|
@ -119,6 +123,8 @@ private:
|
|||
FeetAndHips,
|
||||
FeetHipsAndChest,
|
||||
FeetHipsAndShoulders,
|
||||
FeetHipsChestAndHead,
|
||||
FeetHipsAndHead
|
||||
};
|
||||
Config _config { Config::Auto };
|
||||
Config _preferedConfig { Config::Auto };
|
||||
|
@ -145,6 +151,7 @@ private:
|
|||
bool _triggersPressedHandled { false };
|
||||
bool _calibrated { false };
|
||||
bool _timeTilCalibrationSet { false };
|
||||
bool _overrideHead { false };
|
||||
mutable std::recursive_mutex _lock;
|
||||
|
||||
QString configToString(Config config);
|
||||
|
|
|
@ -21,6 +21,11 @@ var blastShareText = "Blast to my Connections",
|
|||
hifiAlreadySharedText = "Already Shared to Snaps Feed",
|
||||
facebookShareText = "Share to Facebook",
|
||||
twitterShareText = "Share to Twitter";
|
||||
|
||||
function fileExtensionMatches(filePath, extension) {
|
||||
return filePath.split('.').pop().toLowerCase() === extension;
|
||||
}
|
||||
|
||||
function showSetupInstructions() {
|
||||
var snapshotImagesDiv = document.getElementById("snapshot-images");
|
||||
snapshotImagesDiv.className = "snapshotInstructions";
|
||||
|
@ -276,10 +281,10 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
|
|||
if (!image_data.localPath) {
|
||||
return;
|
||||
}
|
||||
var id = "p" + (idCounter++),
|
||||
imageContainer = document.createElement("DIV"),
|
||||
var imageContainer = document.createElement("DIV"),
|
||||
img = document.createElement("IMG"),
|
||||
isGif;
|
||||
isGif = fileExtensionMatches(image_data.localPath, "gif"),
|
||||
id = "p" + (isGif ? "1" : "0");
|
||||
imageContainer.id = id;
|
||||
imageContainer.style.width = "95%";
|
||||
imageContainer.style.height = "240px";
|
||||
|
@ -290,22 +295,27 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
|
|||
imageContainer.style.position = "relative";
|
||||
img.id = id + "img";
|
||||
img.src = image_data.localPath;
|
||||
isGif = img.src.split('.').pop().toLowerCase() === "gif";
|
||||
imageContainer.appendChild(img);
|
||||
document.getElementById("snapshot-images").appendChild(imageContainer);
|
||||
paths.push(image_data.localPath);
|
||||
if (isGif) {
|
||||
imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
|
||||
}
|
||||
if (!isGifLoading) {
|
||||
appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
|
||||
}
|
||||
if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) {
|
||||
shareForUrl(id);
|
||||
}
|
||||
if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
|
||||
updateShareInfo(id, image_data.story_id);
|
||||
}
|
||||
img.onload = function () {
|
||||
if (isGif) {
|
||||
imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
|
||||
}
|
||||
if (!isGifLoading) {
|
||||
appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
|
||||
}
|
||||
if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) {
|
||||
shareForUrl(id);
|
||||
}
|
||||
if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
|
||||
updateShareInfo(id, image_data.story_id);
|
||||
}
|
||||
};
|
||||
img.onerror = function () {
|
||||
img.onload = null;
|
||||
img.src = image_data.errorPath;
|
||||
};
|
||||
}
|
||||
function showConfirmationMessage(selectedID, destination) {
|
||||
if (selectedID.id) {
|
||||
|
@ -632,9 +642,8 @@ window.onload = function () {
|
|||
// The last element of the message contents list contains a bunch of options,
|
||||
// including whether or not we can share stuff
|
||||
// The other elements of the list contain image paths.
|
||||
|
||||
if (messageOptions.containsGif) {
|
||||
if (messageOptions.processingGif) {
|
||||
if (messageOptions.containsGif === true) {
|
||||
if (messageOptions.processingGif === true) {
|
||||
imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
|
||||
message.image_data.push({ localPath: messageOptions.loadingGifPath });
|
||||
message.image_data.forEach(function (element, idx) {
|
||||
|
@ -663,7 +672,7 @@ window.onload = function () {
|
|||
handleCaptureSetting(message.setting);
|
||||
break;
|
||||
case 'snapshotUploadComplete':
|
||||
var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
|
||||
var isGif = fileExtensionMatches(message.image_url, "gif");
|
||||
updateShareInfo(isGif ? "p1" : "p0", message.story_id);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -82,11 +82,23 @@ function showElements(els, show) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateProperty(propertyName, propertyValue) {
|
||||
var properties = {};
|
||||
properties[propertyName] = propertyValue;
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
function updateProperties(properties) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties
|
||||
}));
|
||||
}
|
||||
|
||||
function createEmitCheckedPropertyUpdateFunction(propertyName) {
|
||||
return function() {
|
||||
EventBridge.emitWebEvent(
|
||||
'{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + this.checked + '}}'
|
||||
);
|
||||
updateProperty(propertyName, this.checked);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -105,13 +117,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) {
|
|||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][propertyName] = this.checked;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties
|
||||
})
|
||||
);
|
||||
updateProperties(properties);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -119,10 +125,7 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
|
|||
decimals = decimals == undefined ? 4 : decimals;
|
||||
return function() {
|
||||
var value = parseFloat(this.value).toFixed(decimals);
|
||||
|
||||
EventBridge.emitWebEvent(
|
||||
'{"id":' + lastEntityID + ', "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
|
||||
);
|
||||
updateProperty(propertyName, value);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -131,28 +134,14 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) {
|
|||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][propertyName] = this.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
updateProperties(properties);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function createEmitTextPropertyUpdateFunction(propertyName) {
|
||||
return function() {
|
||||
var properties = {};
|
||||
properties[propertyName] = this.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
updateProperty(propertyName, this.value);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -161,62 +150,44 @@ function createEmitGroupTextPropertyUpdateFunction(group, propertyName) {
|
|||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][propertyName] = this.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
updateProperties(properties);
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) {
|
||||
return function() {
|
||||
var data = {
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {}
|
||||
};
|
||||
data.properties[property] = {
|
||||
var properties = {};
|
||||
properties[property] = {
|
||||
x: elX.value,
|
||||
y: elY.value,
|
||||
z: elZ.value,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
|
||||
function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) {
|
||||
return function() {
|
||||
var data = {
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {}
|
||||
};
|
||||
data.properties[group] = {};
|
||||
data.properties[group][property] = {
|
||||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][property] = {
|
||||
x: elX.value,
|
||||
y: elY.value,
|
||||
z: elZ ? elZ.value : 0,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
|
||||
function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
|
||||
return function() {
|
||||
var data = {
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {}
|
||||
};
|
||||
data.properties[property] = {
|
||||
var properties = {};
|
||||
properties[property] = {
|
||||
x: elX.value * multiplier,
|
||||
y: elY.value * multiplier,
|
||||
z: elZ.value * multiplier,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -227,44 +198,35 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue)
|
|||
};
|
||||
|
||||
function emitColorPropertyUpdate(property, red, green, blue, group) {
|
||||
var data = {
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {}
|
||||
};
|
||||
var properties = {};
|
||||
if (group) {
|
||||
data.properties[group] = {};
|
||||
data.properties[group][property] = {
|
||||
properties[group] = {};
|
||||
properties[group][property] = {
|
||||
red: red,
|
||||
green: green,
|
||||
blue: blue,
|
||||
};
|
||||
} else {
|
||||
data.properties[property] = {
|
||||
properties[property] = {
|
||||
red: red,
|
||||
green: green,
|
||||
blue: blue,
|
||||
};
|
||||
}
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
updateProperties(properties);
|
||||
};
|
||||
|
||||
|
||||
function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) {
|
||||
return function() {
|
||||
var data = {
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {}
|
||||
};
|
||||
data.properties[group] = {};
|
||||
|
||||
data.properties[group][property] = {
|
||||
var properties = {};
|
||||
properties[group] = {};
|
||||
properties[group][property] = {
|
||||
red: elRed.value,
|
||||
green: elGreen.value,
|
||||
blue: elBlue.value,
|
||||
};
|
||||
EventBridge.emitWebEvent(JSON.stringify(data));
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -277,18 +239,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen
|
|||
// We've unchecked, so remove
|
||||
propertyValue = propertyValue.replace(subPropertyString + ",", "");
|
||||
}
|
||||
|
||||
var _properties = {}
|
||||
_properties[propertyName] = propertyValue;
|
||||
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: _properties
|
||||
})
|
||||
);
|
||||
|
||||
updateProperty(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
function setUserDataFromEditor(noUpdate) {
|
||||
|
@ -314,18 +265,11 @@ function setUserDataFromEditor(noUpdate) {
|
|||
);
|
||||
return;
|
||||
} else {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: {
|
||||
userData: text
|
||||
},
|
||||
})
|
||||
);
|
||||
updateProperty('userData', text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
|
||||
var properties = {};
|
||||
var parsedData = {};
|
||||
|
@ -372,13 +316,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
|
|||
|
||||
userDataElement.value = properties['userData'];
|
||||
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
updateProperties(properties);
|
||||
}
|
||||
function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) {
|
||||
var val = {}, def = {};
|
||||
|
@ -900,7 +838,6 @@ function loaded() {
|
|||
elCloneable.checked = parsedUserData["grabbableKey"].cloneable;
|
||||
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
|
||||
elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic;
|
||||
elDynamic.checked = elCloneable.checked ? false: properties.dynamic;
|
||||
if (elCloneable.checked) {
|
||||
if ("cloneLifetime" in parsedUserData["grabbableKey"]) {
|
||||
elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300;
|
||||
|
@ -1202,8 +1139,8 @@ function loaded() {
|
|||
});
|
||||
|
||||
elGrabbable.addEventListener('change', function() {
|
||||
if(elCloneable.checked) {
|
||||
elGrabbable.checked = false;
|
||||
if (elCloneable.checked) {
|
||||
elGrabbable.checked = false;
|
||||
}
|
||||
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic);
|
||||
});
|
||||
|
@ -1213,17 +1150,22 @@ function loaded() {
|
|||
elCloneable.addEventListener('change', function (event) {
|
||||
var checked = event.target.checked;
|
||||
if (checked) {
|
||||
multiDataUpdater("grabbableKey",
|
||||
{cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target},
|
||||
elUserData, {});
|
||||
multiDataUpdater("grabbableKey", {
|
||||
cloneLifetime: elCloneableLifetime,
|
||||
cloneLimit: elCloneableLimit,
|
||||
cloneDynamic: elCloneableDynamic,
|
||||
cloneable: event.target,
|
||||
grabbable: null
|
||||
}, elUserData, {});
|
||||
elCloneableGroup.style.display = "block";
|
||||
EventBridge.emitWebEvent(
|
||||
'{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}'
|
||||
);
|
||||
updateProperty('dynamic', false);
|
||||
} else {
|
||||
multiDataUpdater("grabbableKey",
|
||||
{cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false},
|
||||
elUserData, {});
|
||||
multiDataUpdater("grabbableKey", {
|
||||
cloneLifetime: null,
|
||||
cloneLimit: null,
|
||||
cloneDynamic: null,
|
||||
cloneable: false
|
||||
}, elUserData, {});
|
||||
elCloneableGroup.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
@ -1258,15 +1200,7 @@ function loaded() {
|
|||
showUserDataTextArea();
|
||||
showNewJSONEditorButton();
|
||||
hideSaveUserDataButton();
|
||||
var properties = {};
|
||||
properties['userData'] = elUserData.value;
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "update",
|
||||
properties: properties,
|
||||
})
|
||||
);
|
||||
updateProperty('userData', elUserData.value)
|
||||
});
|
||||
|
||||
elSaveUserData.addEventListener("click", function() {
|
||||
|
|
|
@ -613,7 +613,6 @@
|
|||
error = "All participants must be logged in to connect.";
|
||||
}
|
||||
result = error ? {status: 'error', connection: error} : response;
|
||||
UserActivityLogger.makeUserConnection(connectingId, false, error || response);
|
||||
connectionRequestCompleted();
|
||||
} else {
|
||||
result = response;
|
||||
|
@ -668,8 +667,8 @@
|
|||
// to be sure the hand is still close enough. If not, we terminate
|
||||
// the interval, go back to the waiting state. If we make it
|
||||
// the entire CONNECTING_TIME, we make the connection. We pass in
|
||||
// whether or not the connecting id is actually logged in, as now we
|
||||
// will allow to start the connection process but have it stop with a
|
||||
// whether or not the connecting id is actually logged in, as now we
|
||||
// will allow to start the connection process but have it stop with a
|
||||
// fail message before trying to call the backend if the other guy isn't
|
||||
// logged in.
|
||||
function startConnecting(id, jointIndex, isLoggedIn) {
|
||||
|
|
|
@ -36,6 +36,8 @@ var shareAfterLogin = false;
|
|||
var snapshotToShareAfterLogin = [];
|
||||
var METAVERSE_BASE = location.metaverseServerUrl;
|
||||
var isLoggedIn;
|
||||
var numGifSnapshotUploadsPending = 0;
|
||||
var numStillSnapshotUploadsPending = 0;
|
||||
|
||||
// It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story,
|
||||
// POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS
|
||||
|
@ -56,6 +58,10 @@ function removeFromStoryIDsToMaybeDelete(story_id) {
|
|||
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
|
||||
}
|
||||
|
||||
function fileExtensionMatches(filePath, extension) {
|
||||
return filePath.split('.').pop().toLowerCase() === extension;
|
||||
}
|
||||
|
||||
function onMessage(message) {
|
||||
// Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
|
||||
// 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
|
||||
|
@ -139,6 +145,12 @@ function onMessage(message) {
|
|||
if (isLoggedIn) {
|
||||
print('Sharing snapshot with audience "for_url":', message.data);
|
||||
Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
|
||||
var isGif = fileExtensionMatches(message.data, "gif");
|
||||
if (isGif) {
|
||||
numGifSnapshotUploadsPending++;
|
||||
} else {
|
||||
numStillSnapshotUploadsPending++;
|
||||
}
|
||||
} else {
|
||||
shareAfterLogin = true;
|
||||
snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") });
|
||||
|
@ -273,7 +285,8 @@ function fillImageDataFromPrevious() {
|
|||
localPath: previousStillSnapPath,
|
||||
story_id: previousStillSnapStoryID,
|
||||
blastButtonDisabled: previousStillSnapBlastingDisabled,
|
||||
hifiButtonDisabled: previousStillSnapHifiSharingDisabled
|
||||
hifiButtonDisabled: previousStillSnapHifiSharingDisabled,
|
||||
errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
|
||||
});
|
||||
}
|
||||
if (previousAnimatedSnapPath !== "") {
|
||||
|
@ -281,7 +294,8 @@ function fillImageDataFromPrevious() {
|
|||
localPath: previousAnimatedSnapPath,
|
||||
story_id: previousAnimatedSnapStoryID,
|
||||
blastButtonDisabled: previousAnimatedSnapBlastingDisabled,
|
||||
hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled
|
||||
hifiButtonDisabled: previousAnimatedSnapHifiSharingDisabled,
|
||||
errorPath: Script.resolvePath(Script.resourcesPath() + 'snapshot/img/no-image.jpg')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -305,22 +319,39 @@ function onButtonClicked() {
|
|||
|
||||
function snapshotUploaded(isError, reply) {
|
||||
if (!isError) {
|
||||
var replyJson = JSON.parse(reply);
|
||||
var storyID = replyJson.user_story.id;
|
||||
var replyJson = JSON.parse(reply),
|
||||
storyID = replyJson.user_story.id,
|
||||
imageURL = replyJson.user_story.details.image_url,
|
||||
isGif = fileExtensionMatches(imageURL, "gif"),
|
||||
ignoreGifSnapshotData = false,
|
||||
ignoreStillSnapshotData = false;
|
||||
storyIDsToMaybeDelete.push(storyID);
|
||||
var imageURL = replyJson.user_story.details.image_url;
|
||||
var isGif = imageURL.split('.').pop().toLowerCase() === "gif";
|
||||
print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "snapshotUploadComplete",
|
||||
story_id: storyID,
|
||||
image_url: imageURL,
|
||||
}));
|
||||
if (isGif) {
|
||||
Settings.setValue("previousAnimatedSnapStoryID", storyID);
|
||||
numGifSnapshotUploadsPending--;
|
||||
if (numGifSnapshotUploadsPending !== 0) {
|
||||
ignoreGifSnapshotData = true;
|
||||
}
|
||||
} else {
|
||||
Settings.setValue("previousStillSnapStoryID", storyID);
|
||||
numStillSnapshotUploadsPending--;
|
||||
if (numStillSnapshotUploadsPending !== 0) {
|
||||
ignoreStillSnapshotData = true;
|
||||
}
|
||||
}
|
||||
if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) {
|
||||
print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "snapshotUploadComplete",
|
||||
story_id: storyID,
|
||||
image_url: imageURL,
|
||||
}));
|
||||
if (isGif) {
|
||||
Settings.setValue("previousAnimatedSnapStoryID", storyID);
|
||||
} else {
|
||||
Settings.setValue("previousStillSnapStoryID", storyID);
|
||||
}
|
||||
} else {
|
||||
print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID);
|
||||
}
|
||||
} else {
|
||||
print(reply);
|
||||
|
@ -566,6 +597,12 @@ function onUsernameChanged() {
|
|||
snapshotToShareAfterLogin.forEach(function (element) {
|
||||
print('Uploading snapshot after login:', element.path);
|
||||
Window.shareSnapshot(element.path, element.href);
|
||||
var isGif = fileExtensionMatches(element.path, "gif");
|
||||
if (isGif) {
|
||||
numGifSnapshotUploadsPending++;
|
||||
} else {
|
||||
numStillSnapshotUploadsPending++;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -122,6 +122,10 @@ int main(int argc, char** argv) {
|
|||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4()
|
||||
};
|
||||
|
||||
|
@ -144,6 +148,10 @@ int main(int argc, char** argv) {
|
|||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4(),
|
||||
glm::mat4()
|
||||
};
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
# Declare dependencies
|
||||
macro (SETUP_TESTCASE_DEPENDENCIES)
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared ktx gpu image)
|
||||
|
||||
set(TARGET_NAME ktx-test)
|
||||
|
||||
if (WIN32)
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
|
||||
endif()
|
||||
package_libraries_for_deployment()
|
||||
endmacro ()
|
||||
|
||||
# This is not a testcase -- just set it up as a regular hifi project
|
||||
setup_hifi_project(Quick Gui OpenGL)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image)
|
||||
|
||||
package_libraries_for_deployment()
|
||||
setup_hifi_testcase()
|
||||
|
|
195
tests/ktx/src/KtxTests.cpp
Normal file
195
tests/ktx/src/KtxTests.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "KtxTests.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include <ktx/KTX.h>
|
||||
#include <gpu/Texture.h>
|
||||
#include <image/Image.h>
|
||||
|
||||
|
||||
QTEST_GUILESS_MAIN(KtxTests)
|
||||
|
||||
QString getRootPath() {
|
||||
static std::once_flag once;
|
||||
static QString result;
|
||||
std::call_once(once, [&] {
|
||||
QFileInfo file(__FILE__);
|
||||
QDir parent = file.absolutePath();
|
||||
result = QDir::cleanPath(parent.currentPath() + "/../../..");
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void KtxTests::initTestCase() {
|
||||
}
|
||||
|
||||
void KtxTests::cleanupTestCase() {
|
||||
}
|
||||
|
||||
void KtxTests::testKhronosCompressionFunctions() {
|
||||
using namespace khronos::gl::texture;
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1);
|
||||
QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02);
|
||||
QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error);
|
||||
}
|
||||
|
||||
void KtxTests::testKtxEvalFunctions() {
|
||||
QCOMPARE(sizeof(ktx::Header), (size_t)64);
|
||||
QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3);
|
||||
QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2);
|
||||
QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1);
|
||||
QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0);
|
||||
QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x0), 0x0);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x1), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x2), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x3), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x4), 0x4);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x400), 0x400);
|
||||
QCOMPARE(ktx::evalPaddedSize(0x401), 0x404);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1);
|
||||
QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2);
|
||||
}
|
||||
|
||||
void KtxTests::testKtxSerialization() {
|
||||
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
|
||||
QImage image(TEST_IMAGE);
|
||||
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
|
||||
auto ktxMemory = gpu::Texture::serialize(*testTexture);
|
||||
QVERIFY(ktxMemory.get());
|
||||
|
||||
// Serialize the image to a file
|
||||
QTemporaryFile TEST_IMAGE_KTX;
|
||||
{
|
||||
const auto& ktxStorage = ktxMemory->getStorage();
|
||||
QVERIFY(ktx::KTX::validate(ktxStorage));
|
||||
QVERIFY(ktxMemory->isValid());
|
||||
|
||||
auto& outFile = TEST_IMAGE_KTX;
|
||||
if (!outFile.open()) {
|
||||
QFAIL("Unable to open file");
|
||||
}
|
||||
auto ktxSize = ktxStorage->size();
|
||||
outFile.resize(ktxSize);
|
||||
auto dest = outFile.map(0, ktxSize);
|
||||
memcpy(dest, ktxStorage->data(), ktxSize);
|
||||
outFile.unmap(dest);
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto ktxStorage = std::make_shared<storage::FileStorage>(TEST_IMAGE_KTX.fileName());
|
||||
QVERIFY(ktx::KTX::validate(ktxStorage));
|
||||
auto ktxFile = ktx::KTX::create(ktxStorage);
|
||||
QVERIFY(ktxFile.get());
|
||||
QVERIFY(ktxFile->isValid());
|
||||
{
|
||||
const auto& memStorage = ktxMemory->getStorage();
|
||||
const auto& fileStorage = ktxFile->getStorage();
|
||||
QVERIFY(memStorage->size() == fileStorage->size());
|
||||
QVERIFY(memStorage->data() != fileStorage->data());
|
||||
QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
|
||||
QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size());
|
||||
auto imageCount = ktxFile->_images.size();
|
||||
auto startMemory = ktxMemory->_storage->data();
|
||||
auto startFile = ktxFile->_storage->data();
|
||||
for (size_t i = 0; i < imageCount; ++i) {
|
||||
auto memImages = ktxMemory->_images[i];
|
||||
auto fileImages = ktxFile->_images[i];
|
||||
QVERIFY(memImages._padding == fileImages._padding);
|
||||
QVERIFY(memImages._numFaces == fileImages._numFaces);
|
||||
QVERIFY(memImages._imageSize == fileImages._imageSize);
|
||||
QVERIFY(memImages._faceSize == fileImages._faceSize);
|
||||
QVERIFY(memImages._faceBytes.size() == memImages._numFaces);
|
||||
QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces);
|
||||
auto faceCount = fileImages._numFaces;
|
||||
for (uint32_t face = 0; face < faceCount; ++face) {
|
||||
auto memFace = memImages._faceBytes[face];
|
||||
auto memOffset = memFace - startMemory;
|
||||
auto fileFace = fileImages._faceBytes[face];
|
||||
auto fileOffset = fileFace - startFile;
|
||||
QVERIFY(memOffset % 4 == 0);
|
||||
QVERIFY(memOffset == fileOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString());
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
|
||||
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
|
||||
|
||||
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
|
||||
static const QString EXTENSIONS { "*.ktx" };
|
||||
|
||||
int mainTemp(int, char**) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
|
||||
for (auto fileInfo : fileInfoList) {
|
||||
qDebug() << fileInfo.filePath();
|
||||
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
|
||||
|
||||
if (!ktx::KTX::validate(storage)) {
|
||||
qDebug() << "KTX invalid";
|
||||
}
|
||||
|
||||
auto ktxFile = ktx::KTX::create(storage);
|
||||
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
|
||||
|
||||
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
|
||||
for (const auto& kv : ktxDescriptor.keyValues) {
|
||||
qDebug() << "\t" << kv._key.c_str();
|
||||
}
|
||||
|
||||
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
|
||||
if (offsetToMinMipKV) {
|
||||
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
|
||||
auto minMipLevelAvailable = *data;
|
||||
qDebug() << "\tMin mip available " << minMipLevelAvailable;
|
||||
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
|
||||
}
|
||||
auto storageSize = storage->size();
|
||||
for (const auto& faceImageDesc : ktxDescriptor.images) {
|
||||
//assert(0 == (faceImageDesc._faceSize % 4));
|
||||
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
|
||||
assert(0 == (faceOffset % 4));
|
||||
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
|
||||
assert(faceEndOffset <= storageSize);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& faceImage : ktxFile->_images) {
|
||||
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
|
||||
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
21
tests/ktx/src/KtxTests.h
Normal file
21
tests/ktx/src/KtxTests.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class KtxTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testKtxEvalFunctions();
|
||||
void testKhronosCompressionFunctions();
|
||||
void testKtxSerialization();
|
||||
};
|
||||
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/07/01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QThreadPool>
|
||||
#include <QtCore/QSaveFile>
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QResizeEvent>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <shared/RateCounter.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
#include <shared/FileLogger.h>
|
||||
#include <shared/FileUtils.h>
|
||||
#include <StatTracker.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
#include <gl/Config.h>
|
||||
#include <model/TextureMap.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <image/Image.h>
|
||||
|
||||
QSharedPointer<FileLogger> logger;
|
||||
|
||||
gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true);
|
||||
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message);
|
||||
|
||||
if (!logMessage.isEmpty()) {
|
||||
#ifdef Q_OS_WIN
|
||||
OutputDebugStringA(logMessage.toLocal8Bit().constData());
|
||||
OutputDebugStringA("\n");
|
||||
#endif
|
||||
if (logger) {
|
||||
logger->addMessage(qPrintable(logMessage + "\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char * LOG_FILTER_RULES = R"V0G0N(
|
||||
hifi.gpu=true
|
||||
)V0G0N";
|
||||
|
||||
QString getRootPath() {
|
||||
static std::once_flag once;
|
||||
static QString result;
|
||||
std::call_once(once, [&] {
|
||||
QFileInfo file(__FILE__);
|
||||
QDir parent = file.absolutePath();
|
||||
result = QDir::cleanPath(parent.currentPath() + "/../../..");
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png";
|
||||
const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx";
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
QApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationName("KTX");
|
||||
QCoreApplication::setOrganizationName("High Fidelity");
|
||||
QCoreApplication::setOrganizationDomain("highfidelity.com");
|
||||
logger.reset(new FileLogger());
|
||||
|
||||
Q_ASSERT(ktx::evalPadding(0) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1) == 3);
|
||||
Q_ASSERT(ktx::evalPadding(2) == 2);
|
||||
Q_ASSERT(ktx::evalPadding(3) == 1);
|
||||
Q_ASSERT(ktx::evalPadding(4) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1024) == 0);
|
||||
Q_ASSERT(ktx::evalPadding(1025) == 3);
|
||||
Q_ASSERT(ktx::evalPaddedSize(0) == 0);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(2) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(3) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(4) == 4);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1024) == 1024);
|
||||
Q_ASSERT(ktx::evalPaddedSize(1025) == 1028);
|
||||
Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13));
|
||||
|
||||
DependencyManager::set<tracing::Tracer>();
|
||||
qInstallMessageHandler(messageHandler);
|
||||
QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
|
||||
|
||||
QImage image(TEST_IMAGE);
|
||||
gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true);
|
||||
|
||||
auto ktxMemory = gpu::Texture::serialize(*testTexture);
|
||||
{
|
||||
const auto& ktxStorage = ktxMemory->getStorage();
|
||||
Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed");
|
||||
Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed");
|
||||
QSaveFile outFile(TEST_IMAGE_KTX);
|
||||
if (!outFile.open(QFile::WriteOnly)) {
|
||||
throw std::runtime_error("Unable to open file");
|
||||
}
|
||||
auto ktxSize = ktxStorage->size();
|
||||
outFile.resize(ktxSize);
|
||||
auto dest = outFile.map(0, ktxSize);
|
||||
memcpy(dest, ktxStorage->data(), ktxSize);
|
||||
outFile.unmap(dest);
|
||||
outFile.commit();
|
||||
}
|
||||
|
||||
{
|
||||
auto ktxFile = ktx::KTX::create(std::shared_ptr<storage::Storage>(new storage::FileStorage(TEST_IMAGE_KTX)));
|
||||
{
|
||||
const auto& memStorage = ktxMemory->getStorage();
|
||||
const auto& fileStorage = ktxFile->getStorage();
|
||||
Q_ASSERT(memStorage->size() == fileStorage->size());
|
||||
Q_ASSERT(memStorage->data() != fileStorage->data());
|
||||
Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size()));
|
||||
Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size());
|
||||
auto imageCount = ktxFile->_images.size();
|
||||
auto startMemory = ktxMemory->_storage->data();
|
||||
auto startFile = ktxFile->_storage->data();
|
||||
for (size_t i = 0; i < imageCount; ++i) {
|
||||
auto memImages = ktxMemory->_images[i];
|
||||
auto fileImages = ktxFile->_images[i];
|
||||
Q_ASSERT(memImages._padding == fileImages._padding);
|
||||
Q_ASSERT(memImages._numFaces == fileImages._numFaces);
|
||||
Q_ASSERT(memImages._imageSize == fileImages._imageSize);
|
||||
Q_ASSERT(memImages._faceSize == fileImages._faceSize);
|
||||
Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces);
|
||||
Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces);
|
||||
auto faceCount = fileImages._numFaces;
|
||||
for (uint32_t face = 0; face < faceCount; ++face) {
|
||||
auto memFace = memImages._faceBytes[face];
|
||||
auto memOffset = memFace - startMemory;
|
||||
auto fileFace = fileImages._faceBytes[face];
|
||||
auto fileOffset = fileFace - startFile;
|
||||
Q_ASSERT(memOffset % 4 == 0);
|
||||
Q_ASSERT(memOffset == fileOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static const QString TEST_FOLDER { "H:/ktx_cacheold" };
|
||||
//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" };
|
||||
|
||||
//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" };
|
||||
static const QString EXTENSIONS { "*.ktx" };
|
||||
|
||||
int mainTemp(int, char**) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS });
|
||||
for (auto fileInfo : fileInfoList) {
|
||||
qDebug() << fileInfo.filePath();
|
||||
std::shared_ptr<storage::Storage> storage { new storage::FileStorage { fileInfo.filePath() } };
|
||||
|
||||
if (!ktx::KTX::validate(storage)) {
|
||||
qDebug() << "KTX invalid";
|
||||
}
|
||||
|
||||
auto ktxFile = ktx::KTX::create(storage);
|
||||
ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor();
|
||||
|
||||
qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs";
|
||||
for (const auto& kv : ktxDescriptor.keyValues) {
|
||||
qDebug() << "\t" << kv._key.c_str();
|
||||
}
|
||||
|
||||
auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY);
|
||||
if (offsetToMinMipKV) {
|
||||
auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV;
|
||||
auto minMipLevelAvailable = *data;
|
||||
qDebug() << "\tMin mip available " << minMipLevelAvailable;
|
||||
assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels);
|
||||
}
|
||||
auto storageSize = storage->size();
|
||||
for (const auto& faceImageDesc : ktxDescriptor.images) {
|
||||
//assert(0 == (faceImageDesc._faceSize % 4));
|
||||
for (const auto& faceOffset : faceImageDesc._faceOffsets) {
|
||||
assert(0 == (faceOffset % 4));
|
||||
auto faceEndOffset = faceOffset + faceImageDesc._faceSize;
|
||||
assert(faceEndOffset <= storageSize);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& faceImage : ktxFile->_images) {
|
||||
for (const ktx::Byte* faceBytes : faceImage._faceBytes) {
|
||||
assert(0 == (reinterpret_cast<size_t>(faceBytes) % 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "main.moc"
|
||||
|
170
tests/networking/src/FileCacheTests.cpp
Normal file
170
tests/networking/src/FileCacheTests.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// ResoruceTests.cpp
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "FileCacheTests.h"
|
||||
|
||||
#include <FileCache.h>
|
||||
|
||||
QTEST_GUILESS_MAIN(FileCacheTests)
|
||||
|
||||
using namespace cache;
|
||||
|
||||
// Limit the file size to 10 MB
|
||||
static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 };
|
||||
static const QByteArray TEST_DATA { 1024 * 1024, '0' };
|
||||
|
||||
static std::string getFileKey(int i) {
|
||||
return QString(QByteArray { 1, (char)i }.toHex()).toStdString();
|
||||
}
|
||||
|
||||
class TestFile : public File {
|
||||
using Parent = File;
|
||||
public:
|
||||
TestFile(Metadata&& metadata, const std::string& filepath)
|
||||
: Parent(std::move(metadata), filepath) {
|
||||
}
|
||||
};
|
||||
|
||||
class TestFileCache : public FileCache {
|
||||
using Parent = FileCache;
|
||||
|
||||
public:
|
||||
TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
std::unique_ptr<File> createFile(Metadata&& metadata, const std::string& filepath) override {
|
||||
qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str();
|
||||
return std::unique_ptr<File>(new TestFile(std::move(metadata), filepath));
|
||||
}
|
||||
};
|
||||
|
||||
using CachePointer = std::shared_ptr<TestFileCache>;
|
||||
|
||||
// The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop
|
||||
// so we need to call this function to force any pending deletes to occur in the File destructor
|
||||
static void forceDeletes() {
|
||||
while (QCoreApplication::hasPendingEvents()) {
|
||||
QCoreApplication::sendPostedEvents();
|
||||
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
size_t FileCacheTests::getCacheDirectorySize() const {
|
||||
size_t result = 0;
|
||||
QDir dir(_testDir.path());
|
||||
for (const auto& file : dir.entryList({ "*.tmp" })) {
|
||||
result += QFileInfo(dir.absoluteFilePath(file)).size();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CachePointer makeFileCache(QString& location) {
|
||||
auto result = std::make_shared<TestFileCache>(location.toStdString(), "tmp");
|
||||
result->setMaxSize(MAX_UNUSED_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void FileCacheTests::initTestCase() {
|
||||
}
|
||||
|
||||
void FileCacheTests::testUnusedFiles() {
|
||||
auto cache = makeFileCache(_testDir.path());
|
||||
std::list<FilePointer> inUseFiles;
|
||||
|
||||
{
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size()));
|
||||
inUseFiles.push_back(file);
|
||||
forceDeletes();
|
||||
QThread::msleep(10);
|
||||
}
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)100);
|
||||
// Release the in-use files
|
||||
inUseFiles.clear();
|
||||
// Cache state is updated, but the directory state is unchanged,
|
||||
// because the file deletes are triggered by an event loop
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE);
|
||||
forceDeletes();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE);
|
||||
}
|
||||
|
||||
// Reset the cache
|
||||
cache = makeFileCache(_testDir.path());
|
||||
{
|
||||
// Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files
|
||||
for (int i = 0; i < 90; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(!file.get());
|
||||
}
|
||||
|
||||
QThread::msleep(1000);
|
||||
// Test files 90 to 99 are present
|
||||
for (int i = 90; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(file.get());
|
||||
inUseFiles.push_back(file);
|
||||
// Each access touches the file, so we need to sleep here to ensure that the files are
|
||||
// spaced out in numeric order, otherwise later tests can't reliably determine the order
|
||||
// for cache ejection
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)0);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
inUseFiles.clear();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)10);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)10);
|
||||
}
|
||||
}
|
||||
|
||||
size_t FileCacheTests::getFreeSpace() const {
|
||||
return QStorageInfo(_testDir.path()).bytesFree();
|
||||
}
|
||||
|
||||
// FIXME if something else is changing the amount of free space on the target drive concurrently with this test
|
||||
// running, then it may fail
|
||||
void FileCacheTests::testFreeSpacePreservation() {
|
||||
QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE);
|
||||
// Set the target free space to slightly above whatever the current free space is...
|
||||
size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2;
|
||||
|
||||
// Reset the cache
|
||||
auto cache = makeFileCache(_testDir.path());
|
||||
// Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space
|
||||
cache->setMinFreeSize(targetFreeSpace);
|
||||
QVERIFY(getFreeSpace() < targetFreeSpace);
|
||||
forceDeletes();
|
||||
QCOMPARE(cache->getNumCachedFiles(), (size_t)5);
|
||||
QCOMPARE(cache->getNumTotalFiles(), (size_t)5);
|
||||
QVERIFY(getFreeSpace() >= targetFreeSpace);
|
||||
for (int i = 0; i < 95; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(!file.get());
|
||||
}
|
||||
for (int i = 95; i < 100; ++i) {
|
||||
std::string key = getFileKey(i);
|
||||
auto file = cache->getFile(key);
|
||||
QVERIFY(file.get());
|
||||
}
|
||||
}
|
||||
|
||||
void FileCacheTests::cleanupTestCase() {
|
||||
}
|
||||
|
30
tests/networking/src/FileCacheTests.h
Normal file
30
tests/networking/src/FileCacheTests.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// ResourceTests.h
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ResourceTests_h
|
||||
#define hifi_ResourceTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <QtCore/QTemporaryDir>
|
||||
|
||||
class FileCacheTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testUnusedFiles();
|
||||
void testFreeSpacePreservation();
|
||||
void cleanupTestCase();
|
||||
|
||||
private:
|
||||
size_t getFreeSpace() const;
|
||||
size_t getCacheDirectorySize() const;
|
||||
QTemporaryDir _testDir;
|
||||
};
|
||||
|
||||
#endif // hifi_ResourceTests_h
|
Loading…
Reference in a new issue