Merge remote-tracking branch 'upstream/master' into breakpad_android

This commit is contained in:
Gabriel Calero 2018-06-07 19:00:16 -03:00
commit 3911a93a38
67 changed files with 1132 additions and 721 deletions

View file

@ -66,19 +66,19 @@ ext {
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
def appDir = new File(projectDir, 'app')
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
def baseUrl = ''
def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
def qtFile='https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
def qtVersionId='8QbCma4ryEPgBYn_8kgYgB10IvNx9I1W'
if (Os.isFamily(Os.FAMILY_MAC)) {
qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
qtVersionId='2gfgoYCggJGyXxKiazaPGsMs1Gn9j4og'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtChecksum='c3e25db64002d0f43cf565e0ef708911'
qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT'
qtVersionId='xKIteC6HO0xrmcWeMmhQcmKyPEsnUrcZ'
}
def packages = [
@ -88,79 +88,84 @@ def packages = [
checksum: qtChecksum,
],
bullet: [
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/bullet-2.88_armv8-libcpp.tgz',
file: 'bullet-2.88_armv8-libcpp.tgz',
versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
checksum: '81642779ccb110f8c7338e8739ac38a0',
],
draco: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/draco_armv8-libcpp.tgz',
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
file: 'draco_armv8-libcpp.tgz',
versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8',
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
],
glad: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/glad_armv8-libcpp.zip',
versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
file: 'glad_armv8-libcpp.zip',
versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY',
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
],
glm: [
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/glm-0.9.8.5-patched.tgz',
file: 'glm-0.9.8.5-patched.tgz',
versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
checksum: '067b5fe16b220b5b1a1039ba51b062ae',
],
gvr: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/gvrsdk_v1.101.0.tgz',
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
file: 'gvrsdk_v1.101.0.tgz',
versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r',
checksum: '57fd02baa069176ba18597a29b6b4fc7',
],
nvtt: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/nvtt_armv8-libcpp.zip',
versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An',
file: 'nvtt_armv8-libcpp.zip',
versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO',
checksum: 'eb46d0b683e66987190ed124aabf8910',
sharedLibFolder: 'lib',
includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'],
],
openssl: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/openssl-1.1.0g_armv8.tgz',
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
file: 'openssl-1.1.0g_armv8.tgz',
versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
checksum: 'cabb681fbccd79594f65fcc266e02f32',
],
polyvox: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/polyvox_armv8-libcpp.tgz',
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
checksum: '349ad5b72aaf2749ca95d847e60c5314',
file: 'polyvox_armv8-libcpp.tgz',
versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92',
checksum: 'dba88b3a098747af4bb169e9eb9af57e',
sharedLibFolder: 'lib',
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
],
tbb: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/tbb-2018_U1_armv8_libcpp.tgz',
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
file: 'tbb-2018_U1_armv8_libcpp.tgz',
versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB',
checksum: '20768f298f53b195e71b414b0ae240c4',
sharedLibFolder: 'lib/release',
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
],
hifiAC: [
file: 'https://hifi-public.s3.amazonaws.com/austin/android/libplugins_libhifiCodec.zip',
versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G',
file: 'libplugins_libhifiCodec.zip',
versionId: 'i31pW.qNbvFOXRxbyiJUxg3sphaFNmZU',
checksum: '9412a8e12c88a4096c1fc843bb9fe52d',
sharedLibFolder: '',
includeLibs: ['libplugins_libhifiCodec.so']
],
etc2comp: [
file: 'etc2comp-patched-armv8-libcpp.tgz',
versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU',
checksum: '14b02795d774457a33bbc60e00a786bc'
]
]
def scribeLocalFile='scribe' + EXEC_SUFFIX
def scribeFile='https://hifi-public.s3.amazonaws.com/austin/android/scribe_linux_x86_64'
def scribeFile='scribe_linux_x86_64'
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G'
if (Os.isFamily(Os.FAMILY_MAC)) {
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_osx_x86_64'
scribeFile = 'scribe_osx_x86_64'
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_win32_x86_64.exe'
scribeFile = 'scribe_win32_x86_64.exe'
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC'
scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7'
}
def options = [
@ -361,6 +366,7 @@ task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(bas
task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyDependencyDownloads(dependsOn: downloadDependencies) { }
verifyDependencyDownloads.dependsOn verifyQt
@ -371,6 +377,7 @@ verifyDependencyDownloads.dependsOn verifyOpenSSL
verifyDependencyDownloads.dependsOn verifyPolyvox
verifyDependencyDownloads.dependsOn verifyTBB
verifyDependencyDownloads.dependsOn verifyHifiAC
verifyDependencyDownloads.dependsOn verifyEtc2Comp
task extractDependencies(dependsOn: verifyDependencyDownloads) {
doLast {
@ -608,4 +615,4 @@ task testElf (dependsOn: 'externalNativeBuildDebug') {
}
}
}
*/
*/

View file

@ -233,7 +233,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
_argc(0),
_argv(NULL),
_parsedArgV(NULL),
_httpManager(NULL),
_statusPort(0),
_packetsPerClientPerInterval(10),
_packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL),
@ -285,7 +284,7 @@ void OctreeServer::initHTTPManager(int port) {
QString documentRoot = QString("%1/web").arg(PathUtils::getAppDataPath());
// setup an httpManager with us as the request handler and the parent
_httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this);
_httpManager.reset(new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this));
}
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {

View file

@ -183,7 +183,7 @@ protected:
bool _isShuttingDown = false;
HTTPManager* _httpManager;
std::unique_ptr<HTTPManager> _httpManager;
int _statusPort;
QString _statusHost;

View file

@ -17,8 +17,8 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://static.oculus.com/sdk-downloads/1.11.0/Public/1486063832/ovr_sdk_win_1.11.0_public.zip
URL_MD5 ea484403757cbfdfa743b6577fb1f9d2
URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.26.0_public.zip
URL_MD5 06804ff9727b910dcd04a37c800053b5
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
LOG_DOWNLOAD 1

View file

@ -4,7 +4,7 @@ project(LibOVR)
include_directories(LibOVR/Include LibOVR/Src)
file(GLOB HEADER_FILES LibOVR/Include/*.h)
file(GLOB EXTRA_HEADER_FILES LibOVR/Include/Extras/*.h)
file(GLOB_RECURSE SOURCE_FILES LibOVR/Src/*.c LibOVR/Src/*.cpp)
file(GLOB_RECURSE SOURCE_FILES LibOVR/Shim/*.c LibOVR/Shim/*.cpp)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOVR_BUILD_DEBUG")
add_library(LibOVR STATIC ${SOURCE_FILES} ${HEADER_FILES} ${EXTRA_HEADER_FILES})
set_target_properties(LibOVR PROPERTIES DEBUG_POSTFIX "d")

55
cmake/externals/etc2comp/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,55 @@
set(EXTERNAL_NAME etc2comp)
if (ANDROID)
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
endif ()
if (APPLE)
set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++)
endif ()
include(ExternalProject)
# We use a patched version of etc2comp that properly generates all the necessary mips
# See https://github.com/google/etc2comp/pull/29
# We also use part of https://github.com/google/etc2comp/pull/1, which fixes a bug
# that would override CMAKE_CXX_FLAGS
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/etc2comp-patched.zip
URL_MD5 4c96153eb179acbe619e3d99d3330595
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} ${EXTRA_CMAKE_FLAGS}
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp debug library")
# use generator expression to ensure the correct library is found when building different configurations in VS
set(_LIB_FOLDER "$<$<CONFIG:RelWithDebInfo>:build/EtcLib/RelWithDebInfo>")
set(_LIB_FOLDER "${_LIB_FOLDER}$<$<CONFIG:MinSizeRel>:build/EtcLib/MinSizeRel>")
set(_LIB_FOLDER "${_LIB_FOLDER}$<$<OR:$<CONFIG:Release>,$<CONFIG:Debug>>:build/EtcLib/Release>")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/${_LIB_FOLDER}/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp release library")
elseif (APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/libEtcLib.a CACHE FILEPATH "Path to EtcLib debug library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/Release/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to EtcLib debug library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
endif ()
set(ETC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/Etc CACHE FILEPATH "Path to Etc2Comp/Etc include directory")
set(ETCCODEC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/EtcCodec CACHE FILEPATH "Path to Etc2Comp/EtcCodec include directory")
# ETC2COMP_INCLUDE_DIRS will be set later by FindEtc2Comp

View file

@ -0,0 +1,22 @@
#
# Copyright 2018 High Fidelity, Inc.
# Created by Sam Gondelman on 5/2/2018
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_ETC2COMP)
if (ANDROID)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/etc2comp)
set(ETC2COMP_INCLUDE_DIRS "${INSTALL_DIR}/include/Etc" "${INSTALL_DIR}/include/EtcCodec")
set(ETC2COMP_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libEtcLib.a)
set(ETC2COMP_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libEtcLib.a)
select_library_configurations(ETC2COMP)
else()
add_dependency_external_projects(etc2comp)
find_package(Etc2Comp REQUIRED)
endif()
target_include_directories(${TARGET_NAME} PRIVATE ${ETC2COMP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${ETC2COMP_LIBRARIES})
endmacro()

View file

@ -0,0 +1,37 @@
#
# FindEtc2Comp.cmake
#
# Try to find the Etc2Comp compression library.
#
# Once done this will define
#
# ETC2COMP_FOUND - system found Etc2Comp
# ETC2COMP_INCLUDE_DIRS - the Etc2Comp include directory
# ETC2COMP_LIBRARIES - link to this to use Etc2Comp
#
# Created on 5/2/2018 by Sam Gondelman
# Copyright 2018 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("etc2comp")
find_path(ETC_INCLUDE_DIR NAMES Etc.h HINTS ${ETC2COMP_SEARCH_DIRS})
find_path(ETCCODEC_INCLUDE_DIR NAMES EtcBlock4x4.h HINTS ${ETC2COMP_SEARCH_DIRS})
set(ETC2COMP_INCLUDE_DIRS "${ETC_INCLUDE_DIR}" "${ETCCODEC_INCLUDE_DIR}")
find_library(ETC2COMP_LIBRARY_DEBUG NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Debug HINTS ${ETC2COMP_SEARCH_DIRS})
find_library(ETC2COMP_LIBRARY_RELEASE NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Release EtcLib HINTS ${ETC2COMP_SEARCH_DIRS})
include(SelectLibraryConfigurations)
select_library_configurations(ETC2COMP)
set(ETC2COMP_LIBRARIES ${ETC2COMP_LIBRARY})
find_package_handle_standard_args(ETC2COMP "Could NOT find ETC2COMP, try to set the path to ETC2COMP root folder in the system variable ETC2COMP_ROOT_DIR or create a directory etc2comp in HIFI_LIB_DIR and paste the necessary files there"
ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES)
mark_as_advanced(ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES ETC2COMP_SEARCH_DIRS)

View file

@ -16,6 +16,8 @@
#include <openssl/x509.h>
#include <random>
#include <QDataStream>
#include <AccountManager.h>
#include <Assignment.h>

View file

@ -149,7 +149,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_httpsManager(NULL),
_allAssignments(),
_unfulfilledAssignments(),
_isUsingDTLS(false),
@ -385,6 +384,8 @@ DomainServer::~DomainServer() {
_contentManager->terminate();
}
DependencyManager::destroy<AccountManager>();
// cleanup the AssetClient thread
DependencyManager::destroy<AssetClient>();
_assetClientThread.quit();
@ -439,7 +440,7 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
QSslCertificate sslCertificate(&certFile);
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
_httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this);
_httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this));
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;

View file

@ -220,7 +220,7 @@ private:
DomainGatekeeper _gatekeeper;
HTTPManager _httpManager;
HTTPSManager* _httpsManager;
std::unique_ptr<HTTPSManager> _httpsManager;
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;

View file

@ -13,6 +13,7 @@
#include <openssl/x509.h>
#include <QtCore/QDataStream>
#include <QtCore/QJsonDocument>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkReply>

View file

@ -133,13 +133,7 @@ if (APPLE)
# set where in the bundle to put the resources file
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(DISCOVERED_RESOURCES "")
# use the add_resources_to_os_x_bundle macro to recurse into resources
add_resources_to_os_x_bundle("${CMAKE_CURRENT_SOURCE_DIR}/resources")
# append the discovered resources to our list of interface sources
list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES})
list(APPEND INTERFACE_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME})
endif()
@ -317,18 +311,27 @@ if (APPLE)
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
set(RESOURCES_DEV_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>/../Resources")
# copy script files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
# copy script files beside the executable
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/scripts"
"${RESOURCES_DEV_DIR}/scripts"
)
# copy JSDoc files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
"${CMAKE_SOURCE_DIR}/scripts"
"${RESOURCES_DEV_DIR}/scripts"
# copy JSDoc files beside the executable
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${RESOURCES_DEV_DIR}/jsdoc"
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${RESOURCES_DEV_DIR}/jsdoc"
# copy the resources files beside the executable
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${RESOURCES_RCC}"
"${RESOURCES_DEV_DIR}"
# FIXME, the edit script code loads HTML from the scripts folder
# which in turn relies on CSS that refers to the fonts. In theory
# we should be able to modify the CSS to reference the QRC path to
# the ttf files, but doing so generates a CORS policy violation,
# so we have to retain a copy of the fonts outside of the resources binary
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources/fonts"
"${RESOURCES_DEV_DIR}/fonts"
)
# call the fixup_interface macro to add required bundling commands for installation
@ -357,13 +360,10 @@ else()
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json"
"${RESOURCES_DEV_DIR}/serverless/tutorial.json"
)
# copy JSDoc files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
# copy JSDoc files beside the executable
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${INTERFACE_EXEC_DIR}/jsdoc"
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${INTERFACE_EXEC_DIR}/jsdoc"
)
# link target to external libraries

View file

@ -331,6 +331,7 @@ ModalWindow {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -450,7 +451,7 @@ ModalWindow {
rows = 0,
i;
var newFilesModel = filesModelBuilder.createObject(root);
filesModel = filesModelBuilder.createObject(root);
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
@ -472,7 +473,7 @@ ModalWindow {
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
@ -481,7 +482,7 @@ ModalWindow {
}
}
newFilesModel.insert(lower, {
filesModel.insert(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
@ -492,9 +493,6 @@ ModalWindow {
rows++;
}
filesModel = newFilesModel;
d.clearSelection();
}
}

View file

@ -332,6 +332,7 @@ ModalWindow {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -451,7 +452,7 @@ ModalWindow {
rows = 0,
i;
var newFilesModel = filesModelBuilder.createObject(root);
filesModel = filesModelBuilder.createObject(root);
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
@ -473,7 +474,7 @@ ModalWindow {
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
@ -482,7 +483,7 @@ ModalWindow {
}
}
newFilesModel.insert(lower, {
filesModel.insert(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
@ -493,9 +494,6 @@ ModalWindow {
rows++;
}
filesModel = newFilesModel;
d.clearSelection();
}
}

View file

@ -295,7 +295,8 @@ TabletModalWindow {
}
onFolderChanged: {
fileTableModel.update()
d.clearSelection();
fileTableModel.update();
}
function getItem(index, field) {
@ -413,7 +414,7 @@ TabletModalWindow {
rows = 0,
i;
var newFilesModel = filesModelBuilder.createObject(root);
filesModel = filesModelBuilder.createObject(root);
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
@ -435,7 +436,7 @@ TabletModalWindow {
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
if (comparisonFunction(sortValue, newFilesModel.get(middle)[sortField])) {
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
@ -444,7 +445,7 @@ TabletModalWindow {
}
}
newFilesModel.insert(lower, {
filesModel.insert(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
@ -455,9 +456,6 @@ TabletModalWindow {
rows++;
}
filesModel = newFilesModel;
d.clearSelection();
}
}

View file

@ -8,12 +8,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.1
import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Controls 2.3
import ".."
import "../../../controls-uit"
@ -30,6 +30,8 @@ Rectangle {
color: hifi.colors.baseGray;
property var filesModel: ListModel { }
Settings {
category: "FileDialog"
property alias width: root.width
@ -149,7 +151,7 @@ Rectangle {
ComboBox {
id: pathSelector
anchors {
anchors {
top: parent.top
topMargin: hifi.dimensions.contentMargin.y
left: navControls.right
@ -247,7 +249,9 @@ Rectangle {
}
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
currentSelectionIsFolder = fileTableView.model.isFolder(row);
currentSelectionIsFolder = fileTableView.model !== filesModel ?
fileTableView.model.isFolder(row) :
fileTableModel.isFolder(row);
if (root.selectDirectory || !currentSelectionIsFolder) {
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
} else {
@ -287,6 +291,7 @@ Rectangle {
}
onFolderChanged: {
d.clearSelection();
fileTableModel.update(); // Update once the data from the folder change is available.
}
@ -327,7 +332,12 @@ Rectangle {
}
}
ListModel {
Component {
id: filesModelBuilder
ListModel { }
}
QtObject {
id: fileTableModel
// FolderListModel has a couple of problems:
@ -379,7 +389,11 @@ Rectangle {
if (row === -1) {
return false;
}
return get(row).fileIsDir;
return filesModel.get(row).fileIsDir;
}
function get(row) {
return filesModel.get(row)
}
function update() {
@ -397,7 +411,7 @@ Rectangle {
rows = 0,
i;
clear();
filesModel = filesModelBuilder.createObject(root);
comparisonFunction = sortOrder === Qt.AscendingOrder
? function(a, b) { return a < b; }
@ -419,7 +433,7 @@ Rectangle {
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
if (comparisonFunction(sortValue, get(middle)[sortField])) {
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
@ -428,7 +442,7 @@ Rectangle {
}
}
insert(lower, {
filesModel.insert(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
@ -439,8 +453,6 @@ Rectangle {
rows++;
}
d.clearSelection();
}
}
@ -465,12 +477,12 @@ Rectangle {
sortIndicatorOrder: Qt.AscendingOrder
sortIndicatorVisible: true
model: fileTableModel
model: filesModel
function updateSort() {
model.sortOrder = sortIndicatorOrder;
model.sortColumn = sortIndicatorColumn;
model.update();
fileTableModel.sortOrder = sortIndicatorOrder;
fileTableModel.sortColumn = sortIndicatorColumn;
fileTableModel.update();
}
onSortIndicatorColumnChanged: { updateSort(); }
@ -522,7 +534,7 @@ Rectangle {
}
}
TableViewColumn {
QQC1.TableViewColumn {
id: fileNameColumn
role: "fileName"
title: "Name"
@ -530,7 +542,7 @@ Rectangle {
movable: false
resizable: true
}
TableViewColumn {
QQC1.TableViewColumn {
id: fileMofifiedColumn
role: "fileModified"
title: "Date"
@ -539,7 +551,7 @@ Rectangle {
resizable: true
visible: !selectDirectory
}
TableViewColumn {
QQC1.TableViewColumn {
role: "fileSize"
title: "Size"
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
@ -554,11 +566,12 @@ Rectangle {
}
function navigateToCurrentRow() {
var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel
var row = fileTableView.currentRow
var isFolder = model.isFolder(row);
var file = model.get(row).filePath;
var isFolder = currentModel.isFolder(row);
var file = currentModel.get(row).filePath;
if (isFolder) {
fileTableView.model.folder = helper.pathToUrl(file);
currentModel.folder = helper.pathToUrl(file);
} else {
okAction.trigger();
}
@ -573,7 +586,8 @@ Rectangle {
var newPrefix = prefix + event.text.toLowerCase();
var matchedIndex = -1;
for (var i = 0; i < model.count; ++i) {
var name = model.get(i).fileName.toLowerCase();
var name = model !== filesModel ? model.get(i).fileName.toLowerCase() :
filesModel.get(i).fileName.toLowerCase();
if (0 === name.indexOf(newPrefix)) {
matchedIndex = i;
break;

View file

@ -802,15 +802,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir);
}
// FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove
// this conditional exclusion
#if !defined(Q_OS_OSX)
{
#if defined(Q_OS_ANDROID)
const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc";
#else
const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc";
#endif
const QString resourcesBinaryFile = PathUtils::getRccPath();
if (!QFile::exists(resourcesBinaryFile)) {
throw std::runtime_error("Unable to find primary resources");
}
@ -818,7 +811,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
throw std::runtime_error("Unable to load primary resources");
}
}
#endif
// Tell the plugin manager about our statically linked plugins
auto pluginManager = PluginManager::getInstance();
@ -3674,9 +3666,21 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
break;
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_1: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::FirstPerson);
break;
}
case Qt::Key_2: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::FullscreenMirror);
break;
}
case Qt::Key_3: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::ThirdPerson);
break;
}
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
@ -4187,7 +4191,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
bool Application::shouldPaint() const {
if (_aboutToQuit) {
if (_aboutToQuit || _window->isMinimized()) {
return false;
}
@ -4747,12 +4751,15 @@ void Application::loadSettings() {
// DONT CHECK IN
//DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false);
Menu::getInstance()->loadSettings();
auto menu = Menu::getInstance();
menu->loadSettings();
// override the menu option show overlays to always be true on startup
menu->setIsOptionChecked(MenuOption::Overlays, true);
// If there is a preferred plugin, we probably messed it up with the menu settings, so fix it.
auto pluginManager = PluginManager::getInstance();
auto plugins = pluginManager->getPreferredDisplayPlugins();
auto menu = Menu::getInstance();
if (plugins.size() > 0) {
for (auto plugin : plugins) {
if (auto action = menu->getActionForOption(plugin->getName())) {
@ -8041,7 +8048,6 @@ void Application::switchDisplayMode() {
setActiveDisplayPlugin(DESKTOP_DISPLAY_PLUGIN_NAME);
startHMDStandBySession();
}
emit activeDisplayPluginChanged();
}
_previousHMDWornStatus = currentHMDWornStatus;
}

View file

@ -30,9 +30,6 @@ void Application::editRenderArgs(RenderArgsEditor editor) {
void Application::paintGL() {
// Some plugins process message events, allowing paintGL to be called reentrantly.
if (_aboutToQuit || _window->isMinimized()) {
return;
}
_renderFrameCount++;
_lastTimeRendered.start();

View file

@ -217,21 +217,21 @@ Menu::Menu() {
// View > First Person
auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::FirstPerson, Qt::Key_1,
viewMenu, MenuOption::FirstPerson, 0,
true, qApp, SLOT(cameraMenuChanged())));
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Third Person
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::ThirdPerson, Qt::Key_3,
viewMenu, MenuOption::ThirdPerson, 0,
false, qApp, SLOT(cameraMenuChanged())));
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Mirror
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::FullscreenMirror, Qt::Key_2,
viewMenu, MenuOption::FullscreenMirror, 0,
false, qApp, SLOT(cameraMenuChanged())));
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));

View file

@ -9,11 +9,13 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Application.h"
#include "SecondaryCamera.h"
#include <TextureCache.h>
#include <gpu/Context.h>
#include <glm/gtx/transform.hpp>
#include <gpu/Context.h>
#include <TextureCache.h>
#include "Application.h"
using RenderArgsPointer = std::shared_ptr<RenderArgs>;

View file

@ -654,8 +654,8 @@ void MyAvatar::simulate(float deltaTime) {
if (success) {
moveOperator.addEntityToMoveList(entity, newCube);
}
// send an edit packet to update the entity-server about the queryAABox. If it's an
// avatar-entity, don't.
// send an edit packet to update the entity-server about the queryAABox
// unless it is client-only
if (packetSender && !entity->getClientOnly()) {
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
@ -663,6 +663,17 @@ void MyAvatar::simulate(float deltaTime) {
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
entity->setLastBroadcast(usecTimestampNow());
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
EntityItemProperties descendantProperties;
descendantProperties.setQueryAACube(descendant->getQueryAACube());
descendantProperties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties);
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
}
});
}
}
});

View file

@ -314,6 +314,7 @@ Wallet::Wallet() {
auto nodeList = DependencyManager::get<NodeList>();
auto ledger = DependencyManager::get<Ledger>();
auto& packetReceiver = nodeList->getPacketReceiver();
_passphrase = new QString("");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
@ -365,6 +366,10 @@ Wallet::~Wallet() {
if (_securityImage) {
delete _securityImage;
}
if (_passphrase) {
delete _passphrase;
}
}
bool Wallet::setPassphrase(const QString& passphrase) {

View file

@ -78,7 +78,7 @@ private:
QByteArray _salt;
QByteArray _iv;
QByteArray _ckey;
QString* _passphrase { new QString("") };
QString* _passphrase { nullptr };
bool _isOverridingServer { false };
bool writeWallet(const QString& newPassphrase = QString(""));

View file

@ -105,9 +105,6 @@ class ScriptEngine;
* <li>{@link Controller.getValue|getValue}</li>
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
* <li>{@link Controller.getPoseValue|getgetPoseValue}</li>
* <li>{@link Controller.getButtonValue|getButtonValue} for a particular device</li>
* <li>{@link Controller.getAxisValue(0)|getAxisValue} for a particular device</li>
* <li>{@link Controller.getPoseValue(0)|getPoseValue} for a particular device</li>
* <li>{@link Controller.getActionValue|getActionValue}</li>
* </ul>
*

View file

@ -349,3 +349,23 @@ void Base3DOverlay::setVisible(bool visible) {
Parent::setVisible(visible);
notifyRenderVariableChange();
}
render::ItemKey Base3DOverlay::getKey() {
auto builder = render::ItemKey::Builder(Overlay::getKey());
if (getDrawInFront()) {
builder.withLayer(render::hifi::LAYER_3D_FRONT);
} else if (getDrawHUDLayer()) {
builder.withLayer(render::hifi::LAYER_3D_HUD);
} else {
builder.withoutLayer();
}
builder.withoutViewSpace();
if (isTransparent()) {
builder.withTransparent();
}
return builder.build();
}

View file

@ -35,6 +35,7 @@ public:
// getters
virtual bool is3D() const override { return true; }
virtual render::ItemKey getKey() override;
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); }
virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); }

View file

@ -115,6 +115,10 @@ void ModelOverlay::update(float deltatime) {
_drawInHUDDirty = false;
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
}
if (_groupCulledDirty) {
_groupCulledDirty = false;
_model->setGroupCulled(_isGroupCulled);
}
scene->enqueueTransaction(transaction);
if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) {
@ -149,13 +153,24 @@ void ModelOverlay::setVisible(bool visible) {
}
void ModelOverlay::setDrawInFront(bool drawInFront) {
Base3DOverlay::setDrawInFront(drawInFront);
_drawInFrontDirty = true;
if (drawInFront != getDrawInFront()) {
Base3DOverlay::setDrawInFront(drawInFront);
_drawInFrontDirty = true;
}
}
void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) {
Base3DOverlay::setDrawHUDLayer(drawHUDLayer);
_drawInHUDDirty = true;
if (drawHUDLayer != getDrawHUDLayer()) {
Base3DOverlay::setDrawHUDLayer(drawHUDLayer);
_drawInHUDDirty = true;
}
}
void ModelOverlay::setGroupCulled(bool groupCulled) {
if (groupCulled != _isGroupCulled) {
_isGroupCulled = groupCulled;
_groupCulledDirty = true;
}
}
void ModelOverlay::setProperties(const QVariantMap& properties) {
@ -210,6 +225,11 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
Q_ARG(const QVariantMap&, textureMap));
}
auto groupCulledValue = properties["isGroupCulled"];
if (groupCulledValue.isValid() && groupCulledValue.canConvert(QVariant::Bool)) {
setGroupCulled(groupCulledValue.toBool());
}
// jointNames is read-only.
// jointPositions is read-only.
// jointOrientations is read-only.
@ -347,6 +367,8 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} isGroupCulled=false - If <code>true</code>, the mesh parts of the model are LOD culled as a group.
* If <code>false</code>, separate mesh parts will be LOD culled individually.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
@ -711,3 +733,11 @@ scriptable::ScriptableModelBase ModelOverlay::getScriptableModel() {
}
return result;
}
render::ItemKey ModelOverlay::getKey() {
auto builder = render::ItemKey::Builder(Base3DOverlay::getKey());
if (_isGroupCulled) {
builder.withMetaCullGroup();
}
return builder.build();
}

View file

@ -33,6 +33,7 @@ public:
virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override;
render::ItemKey getKey() override;
void clearSubRenderItemIDs();
void setSubRenderItemIDs(const render::ItemIDs& ids);
@ -63,6 +64,7 @@ public:
void setVisible(bool visible) override;
void setDrawInFront(bool drawInFront) override;
void setDrawHUDLayer(bool drawHUDLayer) override;
void setGroupCulled(bool groupCulled);
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
@ -121,6 +123,8 @@ private:
bool _visibleDirty { true };
bool _drawInFrontDirty { false };
bool _drawInHUDDirty { false };
bool _isGroupCulled { false };
bool _groupCulledDirty { false };
void processMaterials();

View file

@ -244,4 +244,21 @@ void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& p
void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}
render::ItemKey Overlay::getKey() {
auto builder = render::ItemKey::Builder().withTypeShape();
builder.withViewSpace();
builder.withLayer(render::hifi::LAYER_2D);
if (!getVisible()) {
builder.withInvisible();
}
// always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view
render::hifi::Tag viewTagBits = getIsVisibleInSecondaryCamera() ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW;
builder.withTagBits(viewTagBits);
return builder.build();
}

View file

@ -40,6 +40,7 @@ public:
virtual void update(float deltatime) {}
virtual void render(RenderArgs* args) = 0;
virtual render::ItemKey getKey();
virtual AABox getBounds() const = 0;
virtual bool supportsGetProperty() const { return true; }

View file

@ -32,32 +32,7 @@
namespace render {
template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) {
auto builder = ItemKey::Builder().withTypeShape();
if (overlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
if (overlay3D->getDrawInFront()) {
builder.withLayer(render::hifi::LAYER_3D_FRONT);
} else if (overlay3D->getDrawHUDLayer()) {
builder.withLayer(render::hifi::LAYER_3D_HUD);
}
if (overlay->isTransparent()) {
builder.withTransparent();
}
} else {
builder.withViewSpace();
builder.withLayer(render::hifi::LAYER_2D);
}
if (!overlay->getVisible()) {
builder.withInvisible();
}
// always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view
render::hifi::Tag viewTagBits = overlay->getIsVisibleInSecondaryCamera() ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW;
builder.withTagBits(viewTagBits);
return builder.build();
return overlay->getKey();
}
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay) {
return overlay->getBounds();

View file

@ -92,28 +92,16 @@ namespace controller {
return userInputMapper->getValue(Input((uint32_t)source));
}
float ScriptingInterface::getButtonValue(StandardButtonChannel source, uint16_t device) const {
return getValue(Input(device, source, ChannelType::BUTTON).getID());
}
float ScriptingInterface::getAxisValue(int source) const {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
return userInputMapper->getValue(Input((uint32_t)source));
}
float ScriptingInterface::getAxisValue(StandardAxisChannel source, uint16_t device) const {
return getValue(Input(device, source, ChannelType::AXIS).getID());
}
Pose ScriptingInterface::getPoseValue(const int& source) const {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
return userInputMapper->getPose(Input((uint32_t)source));
}
Pose ScriptingInterface::getPoseValue(StandardPoseChannel source, uint16_t device) const {
return getPoseValue(Input(device, source, ChannelType::POSE).getID());
}
QVector<Action> ScriptingInterface::getAllActions() {
return DependencyManager::get<UserInputMapper>()->getAllActions();
}

View file

@ -206,43 +206,6 @@ namespace controller {
*/
Q_INVOKABLE Pose getPoseValue(const int& source) const;
/**jsdoc
* Get the value of a button on a particular device.
* @function Controller.getButtonValue
* @param {StandardButtonChannel} source - The button to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {number} The current value of the button if the parameters are valid, otherwise <code>0</code>.
* @deprecated This function no longer works.
*/
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/14139
Q_INVOKABLE float getButtonValue(StandardButtonChannel source, uint16_t device = 0) const;
/**jsdoc
* Get the value of an axis control on a particular device.
* @function Controller.getAxisValue
* @variation 0
* @param {StandardAxisChannel} source - The axis to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {number} The current value of the axis if the parameters are valid, otherwise <code>0</code>.
* @deprecated This function no longer works.
*/
Q_INVOKABLE float getAxisValue(StandardAxisChannel source, uint16_t device = 0) const;
/**jsdoc
* Get the value of an pose control on a particular device.
* @function Controller.getPoseValue
* @variation 0
* @param {StandardPoseChannel} source - The pose to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {Pose} The current value of the controller pose output if the parameters are valid, otherwise an invalid
* pose with <code>Pose.valid == false</code>.
* @deprecated This function no longer works.
*/
Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const;
/**jsdoc
* Triggers a haptic pulse on connected and enabled devices that have the capability.
* @function Controller.triggerHapticPulse

View file

@ -11,6 +11,8 @@
#include "HTTPConnection.h"
#include <assert.h>
#include <QBuffer>
#include <QCryptographicHash>
#include <QTcpSocket>
@ -29,11 +31,92 @@ const char* HTTPConnection::StatusCode404 = "404 Not Found";
const char* HTTPConnection::StatusCode500 = "500 Internal server error";
const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1";
HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager) :
class MemoryStorage : public HTTPConnection::Storage {
public:
static std::unique_ptr<MemoryStorage> make(qint64 size);
virtual ~MemoryStorage() = default;
const QByteArray& content() const override { return _array; }
qint64 bytesLeftToWrite() const override { return _array.size() - _bytesWritten; }
void write(const QByteArray& data) override;
private:
MemoryStorage(qint64 size) { _array.resize(size); }
QByteArray _array;
qint64 _bytesWritten { 0 };
};
std::unique_ptr<MemoryStorage> MemoryStorage::make(qint64 size) {
return std::unique_ptr<MemoryStorage>(new MemoryStorage(size));
}
void MemoryStorage::write(const QByteArray& data) {
assert(data.size() <= bytesLeftToWrite());
memcpy(_array.data() + _bytesWritten, data.data(), data.size());
_bytesWritten += data.size();
}
class FileStorage : public HTTPConnection::Storage {
public:
static std::unique_ptr<FileStorage> make(qint64 size);
virtual ~FileStorage();
const QByteArray& content() const override { return _wrapperArray; };
qint64 bytesLeftToWrite() const override { return _mappedMemorySize - _bytesWritten; }
void write(const QByteArray& data) override;
private:
FileStorage(std::unique_ptr<QTemporaryFile> file, uchar* mapped, qint64 size);
// Byte array is const because any edit will trigger a deep copy
// and pull all the data we want to keep on disk in memory.
const QByteArray _wrapperArray;
std::unique_ptr<QTemporaryFile> _file;
uchar* const _mappedMemoryAddress { nullptr };
const qint64 _mappedMemorySize { 0 };
qint64 _bytesWritten { 0 };
};
std::unique_ptr<FileStorage> FileStorage::make(qint64 size) {
auto file = std::unique_ptr<QTemporaryFile>(new QTemporaryFile());
file->open(); // Open for resize
file->resize(size);
auto mapped = file->map(0, size); // map the entire file
return std::unique_ptr<FileStorage>(new FileStorage(std::move(file), mapped, size));
}
// Use QByteArray::fromRawData to avoid a new allocation and access the already existing
// memory directly as long as all operations on the array are const.
FileStorage::FileStorage(std::unique_ptr<QTemporaryFile> file, uchar* mapped, qint64 size) :
_wrapperArray(QByteArray::fromRawData(reinterpret_cast<char*>(mapped), size)),
_file(std::move(file)),
_mappedMemoryAddress(mapped),
_mappedMemorySize(size)
{
}
FileStorage::~FileStorage() {
_file->unmap(_mappedMemoryAddress);
_file->close();
}
void FileStorage::write(const QByteArray& data) {
assert(data.size() <= bytesLeftToWrite());
// We write directly to the mapped memory
memcpy(_mappedMemoryAddress + _bytesWritten, data.data(), data.size());
_bytesWritten += data.size();
}
HTTPConnection::HTTPConnection(QTcpSocket* socket, HTTPManager* parentManager) :
QObject(parentManager),
_parentManager(parentManager),
_socket(socket),
_stream(socket),
_address(socket->peerAddress())
{
// take over ownership of the socket
@ -62,7 +145,7 @@ QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
return QHash<QString, QString>();
}
QUrlQuery form { _requestContent };
QUrlQuery form { _requestContent->content() };
QHash<QString, QString> pairs;
for (auto pair : form.queryItems()) {
auto key = QUrl::fromPercentEncoding(pair.first.toLatin1().replace('+', ' '));
@ -97,7 +180,7 @@ QList<FormData> HTTPConnection::parseFormData() const {
QByteArray end = "\r\n--" + boundary + "--\r\n";
QList<FormData> data;
QBuffer buffer(const_cast<QByteArray*>(&_requestContent));
QBuffer buffer(const_cast<QByteArray*>(&_requestContent->content()));
buffer.open(QIODevice::ReadOnly);
while (buffer.canReadLine()) {
QByteArray line = buffer.readLine().trimmed();
@ -107,12 +190,13 @@ QList<FormData> HTTPConnection::parseFormData() const {
QByteArray line = buffer.readLine().trimmed();
if (line.isEmpty()) {
// content starts after this line
int idx = _requestContent.indexOf(end, buffer.pos());
int idx = _requestContent->content().indexOf(end, buffer.pos());
if (idx == -1) {
qWarning() << "Missing end boundary." << _address;
return data;
}
datum.second = _requestContent.mid(buffer.pos(), idx - buffer.pos());
datum.second = QByteArray::fromRawData(_requestContent->content().data() + buffer.pos(),
idx - buffer.pos());
data.append(datum);
buffer.seek(idx + end.length());
@ -256,7 +340,24 @@ void HTTPConnection::readHeaders() {
_parentManager->handleHTTPRequest(this, _requestUrl);
} else {
_requestContent.resize(clength.toInt());
bool success = false;
auto length = clength.toInt(&success);
if (!success) {
qWarning() << "Invalid header." << _address << trimmed;
respond("400 Bad Request", "The header was malformed.");
return;
}
// Storing big requests in memory gets expensive, especially on servers
// with limited memory. So we store big requests in a temporary file on disk
// and map it to faster read/write access.
static const int MAX_CONTENT_SIZE_IN_MEMORY = 10 * 1000 * 1000;
if (length < MAX_CONTENT_SIZE_IN_MEMORY) {
_requestContent = MemoryStorage::make(length);
} else {
_requestContent = FileStorage::make(length);
}
connect(_socket, SIGNAL(readyRead()), SLOT(readContent()));
// read any content immediately available
@ -285,12 +386,13 @@ void HTTPConnection::readHeaders() {
}
void HTTPConnection::readContent() {
int size = _requestContent.size();
if (_socket->bytesAvailable() < size) {
return;
}
_socket->read(_requestContent.data(), size);
_socket->disconnect(this, SLOT(readContent()));
auto size = std::min(_socket->bytesAvailable(), _requestContent->bytesLeftToWrite());
_parentManager->handleHTTPRequest(this, _requestUrl.path());
_requestContent->write(_socket->read(size));
if (_requestContent->bytesLeftToWrite() == 0) {
_socket->disconnect(this, SLOT(readContent()));
_parentManager->handleHTTPRequest(this, _requestUrl.path());
}
}

View file

@ -16,14 +16,14 @@
#ifndef hifi_HTTPConnection_h
#define hifi_HTTPConnection_h
#include <QDataStream>
#include <QHash>
#include <QtNetwork/QHostAddress>
#include <QIODevice>
#include <QList>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QNetworkAccessManager>
#include <QObject>
#include <QPair>
#include <QTemporaryFile>
#include <QUrl>
#include <memory>
@ -57,52 +57,63 @@ public:
/// WebSocket close status codes.
enum ReasonCode { NoReason = 0, NormalClosure = 1000, GoingAway = 1001 };
class Storage {
public:
Storage() = default;
virtual ~Storage() = default;
virtual const QByteArray& content() const = 0;
virtual qint64 bytesLeftToWrite() const = 0;
virtual void write(const QByteArray& data) = 0;
};
/// Initializes the connection.
HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager);
HTTPConnection(QTcpSocket* socket, HTTPManager* parentManager);
/// Destroys the connection.
virtual ~HTTPConnection ();
virtual ~HTTPConnection();
/// Returns a pointer to the underlying socket, to which WebSocket message bodies should be written.
QTcpSocket* socket () const { return _socket; }
QTcpSocket* socket() const { return _socket; }
/// Returns the request operation.
QNetworkAccessManager::Operation requestOperation () const { return _requestOperation; }
QNetworkAccessManager::Operation requestOperation() const { return _requestOperation; }
/// Returns a reference to the request URL.
const QUrl& requestUrl () const { return _requestUrl; }
const QUrl& requestUrl() const { return _requestUrl; }
/// Returns a copy of the request header value. If it does not exist, it will return a default constructed QByteArray.
QByteArray requestHeader(const QString& key) const { return _requestHeaders.value(key.toLower().toLocal8Bit()); }
/// Returns a reference to the request content.
const QByteArray& requestContent () const { return _requestContent; }
const QByteArray& requestContent() const { return _requestContent->content(); }
/// Parses the request content as form data, returning a list of header/content pairs.
QList<FormData> parseFormData () const;
QList<FormData> parseFormData() const;
/// Parses the request content as a url encoded form, returning a hash of key/value pairs.
/// Duplicate keys are not supported.
QHash<QString, QString> parseUrlEncodedForm();
/// Sends a response and closes the connection.
void respond (const char* code, const QByteArray& content = QByteArray(),
void respond(const char* code, const QByteArray& content = QByteArray(),
const char* contentType = DefaultContentType,
const Headers& headers = Headers());
void respond (const char* code, std::unique_ptr<QIODevice> device,
void respond(const char* code, std::unique_ptr<QIODevice> device,
const char* contentType = DefaultContentType,
const Headers& headers = Headers());
protected slots:
/// Reads the request line.
void readRequest ();
void readRequest();
/// Reads the headers.
void readHeaders ();
void readHeaders();
/// Reads the content.
void readContent ();
void readContent();
protected:
void respondWithStatusAndHeaders(const char* code, const char* contentType, const Headers& headers, qint64 size);
@ -112,9 +123,6 @@ protected:
/// The underlying socket.
QTcpSocket* _socket;
/// The data stream for writing to the socket.
QDataStream _stream;
/// The stored address.
QHostAddress _address;
@ -132,7 +140,7 @@ protected:
QByteArray _lastRequestHeader;
/// The content of the request.
QByteArray _requestContent;
std::unique_ptr<Storage> _requestContent;
/// Response content
std::unique_ptr<QIODevice> _responseDevice;

View file

@ -24,8 +24,7 @@
const int SOCKET_ERROR_EXIT_CODE = 2;
const int SOCKET_CHECK_INTERVAL_IN_MS = 30000;
HTTPManager::HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
QTcpServer(parent),
HTTPManager::HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler) :
_listenAddress(listenAddress),
_documentRoot(documentRoot),
_requestHandler(requestHandler),

View file

@ -33,7 +33,7 @@ class HTTPManager : public QTcpServer, public HTTPRequestHandler {
Q_OBJECT
public:
/// Initializes the manager.
HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0);
HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = nullptr);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;

View file

@ -23,4 +23,4 @@ protected slots:
void handleSSLErrors(const QList<QSslError>& errors);
};
#endif // hifi_HTTPSConnection_h
#endif // hifi_HTTPSConnection_h

View file

@ -16,8 +16,8 @@
#include "HTTPSConnection.h"
HTTPSManager::HTTPSManager(QHostAddress listenAddress, quint16 port, const QSslCertificate& certificate, const QSslKey& privateKey,
const QString& documentRoot, HTTPSRequestHandler* requestHandler, QObject* parent) :
HTTPManager(listenAddress, port, documentRoot, requestHandler, parent),
const QString& documentRoot, HTTPSRequestHandler* requestHandler) :
HTTPManager(listenAddress, port, documentRoot, requestHandler),
_certificate(certificate),
_privateKey(privateKey),
_sslRequestHandler(requestHandler)

View file

@ -31,7 +31,7 @@ public:
const QSslCertificate& certificate,
const QSslKey& privateKey,
const QString& documentRoot,
HTTPSRequestHandler* requestHandler = NULL, QObject* parent = 0);
HTTPSRequestHandler* requestHandler = nullptr);
void setCertificate(const QSslCertificate& certificate) { _certificate = certificate; }
void setPrivateKey(const QSslKey& privateKey) { _privateKey = privateKey; }
@ -48,4 +48,4 @@ private:
HTTPSRequestHandler* _sslRequestHandler;
};
#endif // hifi_HTTPSManager_h
#endif // hifi_HTTPSManager_h

View file

@ -684,9 +684,9 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture");
#ifndef USE_GLES
auto mipFormat = cubeTexture.getStoredMipFormat();
std::function<glm::vec3(uint32)> unpackFunc;
switch (mipFormat.getSemantic()) {
case gpu::R11G11B10:
unpackFunc = glm::unpackF2x11_1x10;
@ -698,6 +698,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
assert(false);
break;
}
#endif
const uint sqOrder = order*order;
@ -732,7 +733,11 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
PROFILE_RANGE(render_gpu, "ProcessFace");
#ifndef USE_GLES
auto data = reinterpret_cast<const uint32*>( cubeTexture.accessStoredMipFace(0, face)->readData() );
#else
auto data = cubeTexture.accessStoredMipFace(0, face)->readData();
#endif
if (data == nullptr) {
continue;
}
@ -816,8 +821,15 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
glm::vec3 color{ 0.0f, 0.0f, 0.0f };
for (int i = 0; i < stride; ++i) {
for (int j = 0; j < stride; ++j) {
#ifndef USE_GLES
int k = (int)(x + i - halfStride + (y + j - halfStride) * width);
color += unpackFunc(data[k]);
#else
const int NUM_COMPONENTS_PER_PIXEL = 4;
int k = NUM_COMPONENTS_PER_PIXEL * (int)(x + i - halfStride + (y + j - halfStride) * width);
// BGRA -> RGBA
color += glm::pow(glm::vec3(data[k + 2], data[k + 1], data[k]) / 255.0f, glm::vec3(2.2f));
#endif
}
}

View file

@ -352,7 +352,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
if (!Texture::evalKTXFormat(mipFormat, texelFormat, header)) {
return nullptr;
}
// Set Dimensions
uint32_t numFaces = 1;
switch (texture.getType()) {

View file

@ -2,3 +2,4 @@ set(TARGET_NAME image)
setup_hifi_library()
link_hifi_libraries(shared gpu)
target_nvtt()
target_etc2comp()

View file

@ -31,8 +31,17 @@ using namespace gpu;
#define CPU_MIPMAPS 1
#include <nvtt/nvtt.h>
#ifdef USE_GLES
#include <Etc.h>
#include <EtcFilter.h>
#endif
static const glm::uvec2 SPARSE_PAGE_SIZE(128);
#ifdef Q_OS_ANDROID
static const glm::uvec2 MAX_TEXTURE_SIZE(2048);
#else
static const glm::uvec2 MAX_TEXTURE_SIZE(4096);
#endif
bool DEV_DECIMATE_TEXTURES = false;
std::atomic<size_t> DECIMATED_TEXTURE_COUNT{ 0 };
std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 };
@ -75,7 +84,13 @@ const QStringList getSupportedFormats() {
return stringFormats;
}
// On GLES, we don't use HDR skyboxes
#ifndef USE_GLES
QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30;
#else
QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB32;
#endif
TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) {
switch (type) {
@ -548,13 +563,15 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bo
// https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter
QImage localCopy = std::move(image);
if (localCopy.format() != QImage::Format_ARGB32) {
if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != QIMAGE_HDR_FORMAT) {
localCopy = localCopy.convertToFormat(QImage::Format_ARGB32);
}
const int width = localCopy.width(), height = localCopy.height();
const void* data = static_cast<const void*>(localCopy.constBits());
auto mipFormat = texture->getStoredMipFormat();
#ifndef USE_GLES
const void* data = static_cast<const void*>(localCopy.constBits());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
@ -584,8 +601,6 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bo
nvtt::CompressionOptions compressionOptions;
compressionOptions.setQuality(nvtt::Quality_Production);
// TODO: gles: generate ETC mips instead?
auto mipFormat = texture->getStoredMipFormat();
if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) {
compressionOptions.setFormat(nvtt::Format_BC1);
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) {
@ -669,21 +684,94 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bo
nvtt::Compressor compressor;
compressor.setTaskDispatcher(&dispatcher);
compressor.process(inputOptions, compressionOptions, outputOptions);
#else
int numMips = 1 + (int)log2(std::max(width, height));
Etc::RawImage *mipMaps = new Etc::RawImage[numMips];
Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT;
if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) {
etcFormat = Etc::Image::Format::RGB8;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) {
etcFormat = Etc::Image::Format::SRGB8;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) {
etcFormat = Etc::Image::Format::RGB8A1;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) {
etcFormat = Etc::Image::Format::SRGB8A1;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) {
etcFormat = Etc::Image::Format::RGBA8;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) {
etcFormat = Etc::Image::Format::SRGBA8;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) {
etcFormat = Etc::Image::Format::R11;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) {
etcFormat = Etc::Image::Format::SIGNED_R11;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) {
etcFormat = Etc::Image::Format::RG11;
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) {
etcFormat = Etc::Image::Format::SIGNED_RG11;
} else {
qCWarning(imagelogging) << "Unknown mip format";
Q_UNREACHABLE();
return;
}
const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA;
const float effort = 1.0f;
const int numEncodeThreads = 4;
int encodingTime;
const float MAX_COLOR = 255.0f;
std::vector<vec4> floatData;
floatData.resize(width * height);
for (int y = 0; y < height; y++) {
QRgb *line = (QRgb *) localCopy.scanLine(y);
for (int x = 0; x < width; x++) {
QRgb &pixel = line[x];
floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR;
}
}
// free up the memory afterward to avoid bloating the heap
localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one.
Etc::EncodeMipmaps(
(float *)floatData.data(), width, height,
etcFormat, errorMetric, effort,
numEncodeThreads, numEncodeThreads,
numMips, Etc::FILTER_WRAP_NONE,
mipMaps, &encodingTime
);
for (int i = 0; i < numMips; i++) {
if (mipMaps[i].paucEncodingBits.get()) {
if (face >= 0) {
texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
} else {
texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast<const gpu::Byte*>(mipMaps[i].paucEncodingBits.get()));
}
}
}
delete[] mipMaps;
#endif
}
#endif
void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic<bool>& abortProcessing = false, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
#ifndef USE_GLES
if (image.format() == QIMAGE_HDR_FORMAT) {
generateHDRMips(texture, std::move(image), abortProcessing, face);
} else {
generateLDRMips(texture, std::move(image), abortProcessing, face);
}
#else
generateLDRMips(texture, std::move(image), abortProcessing, face);
#endif
#else
texture->setAutoGenerateMips(true);
#endif
@ -738,7 +826,6 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma
gpu::Element formatMip;
gpu::Element formatGPU;
if (isColorTexturesCompressionEnabled()) {
#ifndef USE_GLES
if (validAlpha) {
// NOTE: This disables BC1a compression because it was producing odd artifacts on text textures
// for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts).
@ -746,22 +833,15 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma
} else {
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB;
}
#else
if (validAlpha) {
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA;
} else {
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB;
}
#endif
formatMip = formatGPU;
} else {
#ifdef USE_GLES
// GLES does not support GL_BGRA
formatMip = gpu::Element::COLOR_SRGBA_32;
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA;
formatMip = formatGPU;
#else
formatGPU = gpu::Element::COLOR_SRGBA_32;
formatMip = gpu::Element::COLOR_SBGRA_32;
#endif
formatGPU = gpu::Element::COLOR_SRGBA_32;
}
if (isStrict) {
@ -876,16 +956,18 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr
gpu::TexturePointer theTexture = nullptr;
if ((image.width() > 0) && (image.height() > 0)) {
gpu::Element formatMip = gpu::Element::VEC2NU8_XY;
gpu::Element formatGPU = gpu::Element::VEC2NU8_XY;
gpu::Element formatMip;
gpu::Element formatGPU;
if (isNormalTexturesCompressionEnabled()) {
#ifndef USE_GLES
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY;
#else
} else {
#ifdef USE_GLES
formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY;
#else
formatGPU = gpu::Element::VEC2NU8_XY;
#endif
formatMip = formatGPU;
}
formatMip = formatGPU;
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
@ -917,16 +999,15 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr
gpu::Element formatMip;
gpu::Element formatGPU;
if (isGrayscaleTexturesCompressionEnabled()) {
#ifndef USE_GLES
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED;
#else
formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED;
#endif
formatMip = formatGPU;
} else {
formatMip = gpu::Element::COLOR_R_8;
#ifdef USE_GLES
formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED;
#else
formatGPU = gpu::Element::COLOR_R_8;
#endif
}
formatMip = formatGPU;
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
theTexture->setSource(srcImageName);
@ -1283,19 +1364,25 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
QImage image = processSourceImage(std::move(localCopy), true);
if (image.format() != QIMAGE_HDR_FORMAT) {
#ifndef USE_GLES
image = convertToHDRFormat(std::move(image), HDR_FORMAT);
#else
image = image.convertToFormat(QImage::Format_RGB32);
#endif
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
// TODO: gles: pick HDR ETC format
formatMip = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
} else {
formatMip = HDR_FORMAT;
#ifdef USE_GLES
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB;
#else
formatGPU = HDR_FORMAT;
#endif
}
formatMip = formatGPU;
// Find the layout of the cubemap in the 2D image
// Use the original image size since processSourceImage may have altered the size / aspect ratio
@ -1342,9 +1429,16 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
gpu::Element irradianceFormat;
// TODO: we could locally compress the irradiance texture on Android, but we don't need to
#ifndef USE_GLES
irradianceFormat = HDR_FORMAT;
#else
irradianceFormat = gpu::Element::COLOR_SRGBA_32;
#endif
auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(HDR_FORMAT);
irradianceTexture->setStoredMipFormat(irradianceFormat);
for (uint8 face = 0; face < faces.size(); ++face) {
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}

View file

@ -350,6 +350,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxMoveStartingPointCandidate != -1) {
_moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id();
_unusedTouches.erase(_moveCurrentTouchId);
thisPoint.x = tPoints[idxMoveStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxMoveStartingPointCandidate].pos().y();
moveTouchBegin(thisPoint);
} else {
moveTouchEnd();
@ -359,6 +361,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxViewStartingPointCandidate != -1) {
_viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id();
_unusedTouches.erase(_viewCurrentTouchId);
thisPoint.x = tPoints[idxViewStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxViewStartingPointCandidate].pos().y();
viewTouchBegin(thisPoint);
} else {
viewTouchEnd();
@ -368,6 +372,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) {
if (idxJumpStartingPointCandidate != -1) {
_jumpCurrentTouchId = tPoints[idxJumpStartingPointCandidate].id();
_unusedTouches.erase(_jumpCurrentTouchId);
thisPoint.x = tPoints[idxJumpStartingPointCandidate].pos().x();
thisPoint.y = tPoints[idxJumpStartingPointCandidate].pos().y();
jumpTouchBegin(thisPoint);
} else {
if (_jumpHasValidTouch) {
@ -424,6 +430,7 @@ void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) {
} else {
_moveRefTouchPoint = touchPoint;
}
_moveCurrentTouchPoint = touchPoint;
_moveHasValidTouch = true;
}
}

View file

@ -446,17 +446,13 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
// shouldSendUpdate() sould NOT be triggering updates to maintain the queryAACube of dynamic entities.
// The server is supposed to predict the transform of such moving things. The client performs a "double prediction"
// where it predicts what the the server is doing, and only sends updates whent the entity's true transform
// differs significantly. That is what the remoteSimulationOutOfSync() logic is all about.
if (_entity->dynamicDataNeedsTransmit() ||
(!_entity->getDynamic() && _entity->queryAACubeNeedsUpdate())) {
if (_entity->dynamicDataNeedsTransmit()) {
return true;
}
if (_entity->shouldSuppressLocationEdits()) {
return false;
// "shouldSuppressLocationEdits" really means: "the entity has a 'Hold' action therefore
// we don't need send an update unless the entity is not contained by its queryAACube"
return _entity->queryAACubeNeedsUpdate();
}
return remoteSimulationOutOfSync(simulationStep);

View file

@ -87,7 +87,8 @@ void CauterizedModel::createVisibleRenderItemSet() {
for (int partIndex = 0; partIndex < numParts; partIndex++) {
auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
_modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
_modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName());
auto material = getGeometry()->getShapeMaterial(shapeID);
_modelMeshMaterialNames.push_back(material ? material->getName() : "");
_modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i });
shapeID++;
}

View file

@ -149,6 +149,7 @@ public:
Builder& withTypeMeta() { _flags.set(TYPE_META); return (*this); }
Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); }
Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); }
Builder& withoutViewSpace() { _flags.reset(VIEW_SPACE); return (*this); }
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }

View file

@ -419,7 +419,7 @@ void ScriptEngine::waitTillDoneRunning() {
// Wait for the scripting thread to stop running, as
// flooding it with aborts/exceptions will persist it longer
static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND;
if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {
workerThread->terminate();
}
}

View file

@ -32,15 +32,13 @@
#include "shared/GlobalAppProperties.h"
#include "SharedUtil.h"
// Format: AppName-PID-Timestamp
// Example: ...
QString TEMP_DIR_FORMAT { "%1-%2-%3" };
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
static bool USE_SOURCE_TREE_RESOURCES() {
#if defined(Q_OS_OSX)
return true;
#else
static bool result = false;
static std::once_flag once;
std::call_once(once, [&] {
@ -48,10 +46,28 @@ static bool USE_SOURCE_TREE_RESOURCES() {
result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG);
});
return result;
#endif
}
#endif
const QString& PathUtils::getRccPath() {
static QString rccLocation;
static std::once_flag once;
std::call_once(once, [&] {
static const QString rccName{ "/resources.rcc" };
#if defined(Q_OS_OSX)
char buffer[8192];
uint32_t bufferSize = sizeof(buffer);
_NSGetExecutablePath(buffer, &bufferSize);
rccLocation = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + rccName;
#elif defined(Q_OS_ANDROID)
rccLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + rccName;
#else
rccLocation = QCoreApplication::applicationDirPath() + rccName;
#endif
});
return rccLocation;
}
#ifdef DEV_BUILD
const QString& PathUtils::projectRootPath() {
static QString sourceFolder;
@ -65,23 +81,9 @@ const QString& PathUtils::projectRootPath() {
#endif
const QString& PathUtils::resourcesPath() {
static QString staticResourcePath;
static QString staticResourcePath{ ":/" };
static std::once_flag once;
std::call_once(once, [&]{
#if defined(Q_OS_OSX)
// FIXME fix the OSX installer to install the resources.rcc instead of the
// individual resource files
// FIXME the first call to fetch the resources location seems to return
// nothing for QCoreApplication::applicationDirPath()
char buffer[8192];
uint32_t bufferSize = sizeof(buffer);
_NSGetExecutablePath(buffer, &bufferSize);
staticResourcePath = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + "/";
#else
staticResourcePath = ":/";
#endif
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
if (USE_SOURCE_TREE_RESOURCES()) {
// For dev builds, optionally load content from the Git source tree
@ -90,21 +92,13 @@ const QString& PathUtils::resourcesPath() {
#endif
qDebug() << "Resource path resolved to " << staticResourcePath;
});
return staticResourcePath;
}
const QString& PathUtils::resourcesUrl() {
static QString staticResourcePath;
static QString staticResourcePath{ "qrc:///" };
static std::once_flag once;
std::call_once(once, [&]{
#if defined(Q_OS_OSX)
staticResourcePath = QUrl::fromLocalFile(resourcesPath()).toString();
#else
staticResourcePath = "qrc:///";
#endif
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
if (USE_SOURCE_TREE_RESOURCES()) {
// For dev builds, optionally load content from the Git source tree

View file

@ -37,6 +37,7 @@ class PathUtils : public QObject, public Dependency {
Q_PROPERTY(QString resources READ resourcesPath CONSTANT)
Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT)
public:
static const QString& getRccPath();
static const QString& resourcesUrl();
static QUrl resourcesUrl(const QString& relative);
static const QString& resourcesPath();

View file

@ -47,7 +47,7 @@ static_assert(
const char* SDL2Manager::NAME = "SDL2";
const char* SDL2Manager::SDL2_ID_STRING = "SDL2";
const bool DEFAULT_ENABLED = false;
const bool DEFAULT_ENABLED = true;
SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) {
SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller);

View file

@ -15,19 +15,25 @@
#include "OculusHelpers.h"
using namespace hifi;
void OculusBaseDisplayPlugin::resetSensors() {
ovr_RecenterTrackingOrigin(_session);
_currentRenderFrameInfo.renderPose = glm::mat4(); // identity
}
bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
handleOVREvents();
if (quitRequested()) {
ovrSessionStatus status{};
if (!OVR_SUCCESS(ovr_GetSessionStatus(_session, &status))) {
qCWarning(oculusLog) << "Unable to fetch Oculus session status" << ovr::getError();
return false;
}
if (ovr::quitRequested(status)) {
QMetaObject::invokeMethod(qApp, "quit");
return false;
}
if (reorientRequested()) {
if (ovr::reorientRequested(status)) {
emit resetSensorsRequested();
}
@ -35,18 +41,18 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_currentRenderFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, frameIndex);
auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue);
_currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose);
_currentRenderFrameInfo.renderPose = ovr::toGlm(trackingState.HeadPose.ThePose);
_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose;
std::array<glm::mat4, 2> handPoses;
// Make controller poses available to the presentation thread
ovr_for_each_hand([&](ovrHandType hand) {
ovr::for_each_hand([&](ovrHandType hand) {
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked;
if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
return;
}
auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]);
auto correctedPose = ovr::toControllerPose(hand, trackingState.HandPoses[hand]);
static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y);
handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION);
});
@ -58,7 +64,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
}
bool OculusBaseDisplayPlugin::isSupported() const {
return oculusAvailable();
return ovr::available();
}
glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
@ -71,7 +77,7 @@ glm::mat4 OculusBaseDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& ba
ovrFovPort fovPort = _hmdDesc.DefaultEyeFov[eye];
ovrEyeRenderDesc& erd = ovr_GetRenderDesc(_session, ovrEye, fovPort);
ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(erd.Fov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL);
return toGlm(ovrPerspectiveProjection);
return ovr::toGlm(ovrPerspectiveProjection);
} else {
return baseProjection;
}
@ -85,7 +91,7 @@ glm::mat4 OculusBaseDisplayPlugin::getCullingProjection(const glm::mat4& basePro
float baseFarClip = baseFrustum.getFarClip();
auto combinedFov = _eyeFovs[0];
combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan);
return toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL));
return ovr::toGlm(ovrMatrix4f_Projection(combinedFov, baseNearClip, baseFarClip, ovrProjection_ClipRangeOpenGL));
} else {
return baseProjection;
}
@ -102,7 +108,7 @@ void OculusBaseDisplayPlugin::uncustomizeContext() {
}
bool OculusBaseDisplayPlugin::internalActivate() {
_session = acquireOculusSession();
_session = ovr::acquireRenderSession();
if (!_session) {
return false;
}
@ -113,21 +119,21 @@ bool OculusBaseDisplayPlugin::internalActivate() {
_viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
_ipd = 0;
ovr_for_each_eye([&](ovrEyeType eye) {
ovr::for_each_eye([&](ovrEyeType eye) {
_eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye];
ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_session, eye, _eyeFovs[eye]);
ovrMatrix4f ovrPerspectiveProjection =
ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL);
_eyeProjections[eye] = toGlm(ovrPerspectiveProjection);
_eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeOffset));
eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f));
_viewScaleDesc.HmdToEyeOffset[eye] = erd.HmdToEyeOffset;
_ipd += glm::abs(glm::length(toGlm(erd.HmdToEyeOffset)));
_eyeProjections[eye] = ovr::toGlm(ovrPerspectiveProjection);
_eyeOffsets[eye] = ovr::toGlm(erd.HmdToEyePose);
eyeSizes[eye] = ovr::toGlm(ovr_GetFovTextureSize(_session, eye, erd.Fov, 1.0f));
_viewScaleDesc.HmdToEyePose[eye] = erd.HmdToEyePose;
_ipd += glm::abs(glm::length(ovr::toGlm(erd.HmdToEyePose.Position)));
});
auto combinedFov = _eyeFovs[0];
combinedFov.LeftTan = combinedFov.RightTan = std::max(combinedFov.LeftTan, combinedFov.RightTan);
_cullingProjection = toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL));
_cullingProjection = ovr::toGlm(ovrMatrix4f_Projection(combinedFov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_ClipRangeOpenGL));
_renderTargetSize = uvec2(
eyeSizes[0].x + eyeSizes[1].x,
@ -136,7 +142,7 @@ bool OculusBaseDisplayPlugin::internalActivate() {
memset(&_sceneLayer, 0, sizeof(ovrLayerEyeFov));
_sceneLayer.Header.Type = ovrLayerType_EyeFov;
_sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;
ovr_for_each_eye([&](ovrEyeType eye) {
ovr::for_each_eye([&](ovrEyeType eye) {
ovrFovPort & fov = _sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov;
ovrSizei & size = _sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_session, eye, fov, 1.0f);
_sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 };
@ -150,28 +156,14 @@ bool OculusBaseDisplayPlugin::internalActivate() {
void OculusBaseDisplayPlugin::internalDeactivate() {
Parent::internalDeactivate();
ovr::releaseRenderSession(_session);
}
bool OculusBaseDisplayPlugin::activateStandBySession() {
if (!_session) {
_session = acquireOculusSession();
}
return _session;
}
void OculusBaseDisplayPlugin::deactivateSession() {
// FIXME
// Switching to Qt 5.9 exposed a race condition or similar issue that caused a crash when putting on an Rift
// while already in VR mode. Commenting these out is a workaround.
//releaseOculusSession();
//_session = nullptr;
}
void OculusBaseDisplayPlugin::updatePresentPose() {
ovrTrackingState trackingState;
_currentPresentFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_currentPresentFrameInfo.predictedDisplayTime = ovr_GetPredictedDisplayTime(_session, 0);
auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse);
_currentPresentFrameInfo.presentPose = toGlm(trackingState.HeadPose.ThePose);
trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrFalse);
_currentPresentFrameInfo.presentPose = ovr::toGlm(trackingState.HeadPose.ThePose);
_currentPresentFrameInfo.renderPose = _currentPresentFrameInfo.presentPose;
}
OculusBaseDisplayPlugin::~OculusBaseDisplayPlugin() {
}

View file

@ -16,37 +16,32 @@
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
using Parent = HmdDisplayPlugin;
public:
~OculusBaseDisplayPlugin();
bool isSupported() const override;
bool hasAsyncReprojection() const override { return true; }
bool getSupportsAutoSwitch() override final { return true; }
glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override;
glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override;
bool hasAsyncReprojection() const override { return true; }
// Stereo specific methods
void resetSensors() override final;
bool beginFrameRender(uint32_t frameIndex) override;
float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; }
bool getSupportsAutoSwitch() override final { return true; }
protected:
void customizeContext() override;
void uncustomizeContext() override;
bool internalActivate() override;
void internalDeactivate() override;
bool activateStandBySession() override;
void deactivateSession() override;
void updatePresentPose() override;
protected:
ovrSession _session { nullptr };
ovrSession _session{ nullptr };
ovrGraphicsLuid _luid;
ovrEyeRenderDesc _eyeRenderDescs[2];
ovrFovPort _eyeFovs[2];
std::array<ovrEyeRenderDesc, 2> _eyeRenderDescs;
std::array<ovrFovPort, 2> _eyeFovs;
ovrHmdDesc _hmdDesc;
ovrLayerEyeFov _sceneLayer;
ovrViewScaleDesc _viewScaleDesc;
// ovrLayerEyeFovDepth _depthLayer;
};

View file

@ -22,34 +22,23 @@
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <OVR_CAPI.h>
#include <ovr_capi.h>
#include "OculusHelpers.h"
Q_DECLARE_LOGGING_CATEGORY(oculus)
static const char* MENU_PARENT = "Avatar";
static const char* MENU_NAME = "Oculus Touch Controllers";
static const char* MENU_PATH = "Avatar" ">" "Oculus Touch Controllers";
using namespace hifi;
const char* OculusControllerManager::NAME = "Oculus";
const quint64 LOST_TRACKING_DELAY = 3000000;
bool OculusControllerManager::isSupported() const {
return oculusAvailable();
return hifi::ovr::available();
}
bool OculusControllerManager::activate() {
InputPlugin::activate();
if (!_session) {
_session = acquireOculusSession();
}
Q_ASSERT(_session);
checkForConnectedDevices();
return true;
}
@ -58,33 +47,30 @@ void OculusControllerManager::checkForConnectedDevices() {
return;
}
unsigned int controllerConnected = ovr_GetConnectedControllerTypes(_session);
ovr::withSession([&] (ovrSession session) {
unsigned int controllerConnected = ovr_GetConnectedControllerTypes(session);
if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_remote = std::make_shared<RemoteDevice>(*this);
userInputMapper->registerDevice(_remote);
if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) {
if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_remote = std::make_shared<RemoteDevice>(*this);
userInputMapper->registerDevice(_remote);
}
}
}
if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_touch = std::make_shared<TouchDevice>(*this);
userInputMapper->registerDevice(_touch);
if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) {
if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState))) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
_touch = std::make_shared<TouchDevice>(*this);
userInputMapper->registerDevice(_touch);
}
}
}
});
}
void OculusControllerManager::deactivate() {
InputPlugin::deactivate();
if (_session) {
releaseOculusSession();
_session = nullptr;
}
// unregister with UserInputMapper
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
if (_touch) {
@ -100,20 +86,30 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In
checkForConnectedDevices();
if (_touch) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) {
_touch->update(deltaTime, inputCalibrationData);
} else {
qCWarning(oculus) << "Unable to read Oculus touch input state";
bool updateRemote = false, updateTouch = false;
ovr::withSession([&](ovrSession session) {
if (_touch) {
updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState));
if (!updateTouch) {
qCWarning(oculusLog) << "Unable to read Oculus touch input state" << ovr::getError();
}
}
if (_remote) {
updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState));
if (!updateRemote) {
qCWarning(oculusLog) << "Unable to read Oculus remote input state" << ovr::getError();
}
}
});
if (_touch && updateTouch) {
_touch->update(deltaTime, inputCalibrationData);
}
if (_remote) {
if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) {
_remote->update(deltaTime, inputCalibrationData);
} else {
qCWarning(oculus) << "Unable to read Oculus remote input state";
}
if (_remote && updateRemote) {
_remote->update(deltaTime, inputCalibrationData);
}
}
@ -210,15 +206,6 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() {
_buttonPressedMap.clear();
}
bool OculusControllerManager::isHeadControllerMounted() const {
ovrSessionStatus status;
bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status));
if (!success) {
return false;
}
return status.HmdMounted == ovrTrue;
}
void OculusControllerManager::TouchDevice::update(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData) {
_buttonPressedMap.clear();
@ -226,10 +213,19 @@ void OculusControllerManager::TouchDevice::update(float deltaTime,
int numTrackedControllers = 0;
quint64 currentTime = usecTimestampNow();
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked;
auto tracking = ovr_GetTrackingState(_parent._session, 0, false);
ovr_for_each_hand([&](ovrHandType hand) {
bool hasInputFocus = ovr::hasInputFocus();
auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false);
ovr::for_each_hand([&](ovrHandType hand) {
++numTrackedControllers;
int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND);
// Disable hand tracking while in Oculus Dash (Dash renders it's own hands)
if (!hasInputFocus) {
_poseStateMap.erase(controller);
_poseStateMap[controller].valid = false;
return;
}
if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
_poseStateMap.erase(controller);
handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]);
@ -253,7 +249,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime,
handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]);
});
if (_parent.isHeadControllerMounted()) {
if (ovr::hmdMounted()) {
handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose);
} else {
_poseStateMap[controller::HEAD].valid = false;
@ -311,7 +307,7 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
ovrHandType hand, const ovrPoseStatef& handPose) {
auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
auto& pose = _poseStateMap[poseId];
pose = ovrControllerPoseToHandPose(hand, handPose);
pose = ovr::toControllerPose(hand, handPose);
// transform into avatar frame
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
pose = pose.transform(controllerToAvatar);
@ -320,15 +316,15 @@ void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData,
const ovrPoseStatef& headPose) {
glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation),
toGlm(headPose.ThePose.Position));
glm::mat4 mat = createMatFromQuatAndPos(ovr::toGlm(headPose.ThePose.Orientation),
ovr::toGlm(headPose.ThePose.Position));
//perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
glm::mat4 matYFlip = mat * Matrices::Y_180;
controller::Pose pose(extractTranslation(matYFlip),
glmExtractRotation(matYFlip),
toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
toGlm(headPose.AngularVelocity));
ovr::toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
ovr::toGlm(headPose.AngularVelocity));
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
@ -343,7 +339,7 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const
auto poseId = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND);
auto& pose = _poseStateMap[poseId];
auto lastHandPose = _lastControllerPose[poseId];
pose = ovrControllerRotationToHandRotation(hand, handPose, lastHandPose);
pose = ovr::toControllerPose(hand, handPose, lastHandPose);
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
pose = pose.transform(controllerToAvatar);
}
@ -351,36 +347,40 @@ void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const
bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
Locker locker(_lock);
bool toReturn = true;
if (hand == controller::BOTH || hand == controller::LEFT) {
if (strength == 0.0f) {
_leftHapticStrength = 0.0f;
_leftHapticDuration = 0.0f;
} else {
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) {
toReturn = false;
ovr::withSession([&](ovrSession session) {
if (hand == controller::BOTH || hand == controller::LEFT) {
if (strength == 0.0f) {
_leftHapticStrength = 0.0f;
_leftHapticDuration = 0.0f;
} else {
_leftHapticStrength = (duration > _leftHapticDuration) ? strength : _leftHapticStrength;
if (ovr_SetControllerVibration(session, ovrControllerType_LTouch, 1.0f, _leftHapticStrength) != ovrSuccess) {
toReturn = false;
}
_leftHapticDuration = std::max(duration, _leftHapticDuration);
}
_leftHapticDuration = std::max(duration, _leftHapticDuration);
}
}
if (hand == controller::BOTH || hand == controller::RIGHT) {
if (strength == 0.0f) {
_rightHapticStrength = 0.0f;
_rightHapticDuration = 0.0f;
} else {
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
if (ovr_SetControllerVibration(_parent._session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) {
toReturn = false;
if (hand == controller::BOTH || hand == controller::RIGHT) {
if (strength == 0.0f) {
_rightHapticStrength = 0.0f;
_rightHapticDuration = 0.0f;
} else {
_rightHapticStrength = (duration > _rightHapticDuration) ? strength : _rightHapticStrength;
if (ovr_SetControllerVibration(session, ovrControllerType_RTouch, 1.0f, _rightHapticStrength) != ovrSuccess) {
toReturn = false;
}
_rightHapticDuration = std::max(duration, _rightHapticDuration);
}
_rightHapticDuration = std::max(duration, _rightHapticDuration);
}
}
});
return toReturn;
}
void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) {
auto handType = (leftHand ? ovrControllerType_LTouch : ovrControllerType_RTouch);
ovr_SetControllerVibration(_parent._session, handType, 0.0f, 0.0f);
ovr::withSession([&](ovrSession session) {
ovr_SetControllerVibration(session, handType, 0.0f, 0.0f);
});
}
/**jsdoc

View file

@ -26,10 +26,8 @@ public:
// Plugin functions
bool isSupported() const override;
const QString getName() const override { return NAME; }
bool isHandController() const override { return _touch != nullptr; }
bool isHeadController() const override { return true; }
bool isHeadControllerMounted() const;
QStringList getSubdeviceNames() override;
bool activate() override;
@ -105,7 +103,6 @@ private:
void checkForConnectedDevices();
ovrSession _session { nullptr };
ovrInputState _inputState {};
RemoteDevice::Pointer _remote;
TouchDevice::Pointer _touch;

View file

@ -19,6 +19,8 @@
#include "OculusHelpers.h"
using namespace hifi;
const char* OculusDisplayPlugin::NAME { "Oculus Rift" };
static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off;
@ -63,7 +65,7 @@ void OculusDisplayPlugin::cycleDebugOutput() {
void OculusDisplayPlugin::customizeContext() {
Parent::customizeContext();
_outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y));
_outputFramebuffer.reset(gpu::Framebuffer::create("OculusOutput", gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y));
ovrTextureSwapChainDesc desc = { };
desc.Type = ovrTexture_2D;
desc.ArraySize = 1;
@ -76,14 +78,14 @@ void OculusDisplayPlugin::customizeContext() {
ovrResult result = ovr_CreateTextureSwapChainGL(_session, &desc, &_textureSwapChain);
if (!OVR_SUCCESS(result)) {
logCritical("Failed to create swap textures");
qCritical(oculusLog) << "Failed to create swap textures" << ovr::getError();
return;
}
int length = 0;
result = ovr_GetTextureSwapChainLength(_session, _textureSwapChain, &length);
if (!OVR_SUCCESS(result) || !length) {
logCritical("Unable to count swap chain textures");
qCritical(oculusLog) << "Unable to count swap chain textures" << ovr::getError();
return;
}
for (int i = 0; i < length; ++i) {
@ -164,8 +166,8 @@ void OculusDisplayPlugin::hmdPresent() {
auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain);
Q_ASSERT(OVR_SUCCESS(result));
_sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime;
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovr::poseFromGlm(_currentPresentFrameInfo.renderPose);
auto submitStart = usecTimestampNow();
uint64_t nonSubmitInterval = 0;
@ -192,7 +194,7 @@ void OculusDisplayPlugin::hmdPresent() {
}
if (!OVR_SUCCESS(result)) {
logWarning("Failed to present");
qWarning(oculusLog) << "Failed to present" << ovr::getError();
}
static int compositorDroppedFrames = 0;
@ -234,9 +236,7 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const {
}
bool OculusDisplayPlugin::isHmdMounted() const {
ovrSessionStatus status;
return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) &&
(ovrFalse != status.HmdMounted));
return ovr::hmdMounted();
}
QString OculusDisplayPlugin::getPreferredAudioInDevice() const {

View file

@ -24,41 +24,17 @@
#include <NumericalConstants.h>
Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
Q_LOGGING_CATEGORY(oculus, "hifi.plugins.display.oculus")
static std::atomic<uint32_t> refCount { 0 };
static ovrSession session { nullptr };
static bool _quitRequested { false };
static bool _reorientRequested { false };
inline ovrErrorInfo getError() {
ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
return error;
}
void logWarning(const char* what) {
qWarning(oculus) << what << ":" << getError().ErrorString;
}
void logCritical(const char* what) {
std::string error("[oculus] ");
error += what;
error += ": ";
error += getError().ErrorString;
qCritical(error.c_str());
}
Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus")
using namespace hifi;
static wchar_t* REQUIRED_OCULUS_DLL = L"LibOVRRT64_1.dll";
static wchar_t FOUND_PATH[MAX_PATH];
bool oculusAvailable() {
bool ovr::available() {
static std::once_flag once;
static bool result { false };
static bool result{ false };
std::call_once(once, [&] {
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR");
static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
if (enableDebugOpenVR) {
@ -81,113 +57,107 @@ bool oculusAvailable() {
return result;
}
ovrSession acquireOculusSession() {
if (!session && !oculusAvailable()) {
qCDebug(oculus) << "oculus: no runtime or HMD present";
class ovrImpl {
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
std::mutex mutex;
ovrSession session{ nullptr };
size_t renderCount{ 0 };
private:
void setupSession(bool render) {
if (session) {
return;
}
ovrInitParams initParams{ ovrInit_RequestVersion | ovrInit_FocusAware, OVR_MINOR_VERSION, nullptr, 0, 0 };
if (render) {
initParams.Flags |= ovrInit_MixedRendering;
} else {
initParams.Flags |= ovrInit_Invisible;
}
if (!OVR_SUCCESS(ovr_Initialize(&initParams))) {
qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError();
return;
}
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError();
return;
}
}
void releaseSession() {
if (!session) {
return;
}
ovr_Destroy(session);
session = nullptr;
ovr_Shutdown();
}
public:
void withSession(const std::function<void(ovrSession)>& f) {
Lock lock(mutex);
if (!session) {
setupSession(false);
}
f(session);
}
ovrSession acquireRenderSession() {
Lock lock(mutex);
if (renderCount++ == 0) {
releaseSession();
setupSession(true);
}
return session;
}
if (!session) {
ovrInitParams initParams {
ovrInit_RequestVersion | ovrInit_MixedRendering, OVR_MINOR_VERSION, nullptr, 0, 0
};
if (!OVR_SUCCESS(ovr_Initialize(&initParams))) {
logWarning("Failed to initialize Oculus SDK");
return session;
}
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
// we were unable to initialize the platform for entitlement check - fail the check
_quitRequested = true;
} else {
qCDebug(oculus) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
#endif
Q_ASSERT(0 == refCount);
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
logWarning("Failed to acquire Oculus session");
return session;
void releaseRenderSession(ovrSession session) {
Lock lock(mutex);
if (--renderCount == 0) {
releaseSession();
}
}
} _ovr;
++refCount;
return session;
ovrSession ovr::acquireRenderSession() {
return _ovr.acquireRenderSession();
}
void releaseOculusSession() {
Q_ASSERT(refCount > 0 && session);
// HACK the Oculus runtime doesn't seem to play well with repeated shutdown / restart.
// So for now we'll just hold on to the session
#if 0
if (!--refCount) {
qCDebug(oculus) << "oculus: zero refcount, shutdown SDK and session";
ovr_Destroy(session);
ovr_Shutdown();
session = nullptr;
}
#endif
void ovr::releaseRenderSession(ovrSession session) {
_ovr.releaseRenderSession(session);
}
void handleOVREvents() {
if (!session) {
return;
}
void ovr::withSession(const std::function<void(ovrSession)>& f) {
_ovr.withSession(f);
}
ovrSessionStatus status;
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
return;
}
_quitRequested = status.ShouldQuit;
_reorientRequested = status.ShouldRecenter;
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, set our flag so the app can stop
qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
_quitRequested = true;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
ovrSessionStatus ovr::getStatus() {
ovrSessionStatus status{};
withSession([&](ovrSession session) {
if (!OVR_SUCCESS(ovr_GetSessionStatus(session, &status))) {
qCWarning(oculusLog) << "Failed to get session status" << ovr::getError();
}
}
#endif
});
return status;
}
bool quitRequested() {
return _quitRequested;
}
bool reorientRequested() {
return _reorientRequested;
ovrTrackingState ovr::getTrackingState() {
ovrTrackingState result{};
withSession([&](ovrSession session) { result = ovr_GetTrackingState(session, 0, ovrFalse); });
return result;
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose) {
QString ovr::getError() {
static ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
return QString(error.ErrorString);
}
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
@ -247,9 +217,8 @@ controller::Pose ovrControllerPoseToHandPose(
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
-CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
@ -268,12 +237,13 @@ controller::Pose ovrControllerPoseToHandPose(
return pose;
}
controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovrPoseStatef& handPose,
const ovrPoseStatef& lastHandPose) {
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand,
const ovrPoseStatef& handPose,
const ovrPoseStatef& lastHandPose) {
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
@ -281,9 +251,8 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
-CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
@ -301,3 +270,52 @@ controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand, const ovr
pose.valid = true;
return pose;
}
// FIXME These should be moved to an oculusPlatform plugin, they don't interact with the controller or session state
#if 0
void handleOVREvents() {
updateSessionStatus(true);
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled:
{
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, set our flag so the app can stop
qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
_quitRequested = true;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
}
}
#endif
}
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
// we were unable to initialize the platform for entitlement check - fail the check
_quitRequested = true;
} else {
qCDebug(oculusLog) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
#endif
#endif

View file

@ -7,10 +7,9 @@
//
#pragma once
#include <QtCore/QLoggingCategory>
#include <OVR_CAPI_GL.h>
#include <ovr_capi.h>
#include <GLMHelpers.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
@ -18,106 +17,107 @@
#include <controllers/Forward.h>
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
Q_DECLARE_LOGGING_CATEGORY(oculus)
Q_DECLARE_LOGGING_CATEGORY(oculusLog)
void logWarning(const char* what);
void logCritical(const char* what);
bool oculusAvailable();
ovrSession acquireOculusSession();
void releaseOculusSession();
namespace hifi {
struct ovr {
static bool available();
static ovrSession acquireRenderSession();
static void releaseRenderSession(ovrSession session);
static void withSession(const std::function<void(ovrSession)>& f);
static ovrSessionStatus getStatus();
static ovrTrackingState getTrackingState();
static QString getError();
void handleOVREvents();
bool quitRequested();
bool reorientRequested();
static inline bool quitRequested() { return quitRequested(getStatus()); }
static inline bool reorientRequested() { return reorientRequested(getStatus()); }
static inline bool hmdMounted() { return hmdMounted(getStatus()); }
static inline bool hasInputFocus() { return hasInputFocus(getStatus()); }
// Convenience method for looping over each eye with a lambda
template <typename Function>
inline void ovr_for_each_eye(Function function) {
for (ovrEyeType eye = ovrEyeType::ovrEye_Left;
eye < ovrEyeType::ovrEye_Count;
eye = static_cast<ovrEyeType>(eye + 1)) {
function(eye);
static inline bool quitRequested(const ovrSessionStatus& status) { return status.ShouldQuit != ovrFalse; }
static inline bool reorientRequested(const ovrSessionStatus& status) { return status.ShouldRecenter != ovrFalse; }
static inline bool hmdMounted(const ovrSessionStatus& status) { return status.HmdMounted != ovrFalse; }
static inline bool hasInputFocus(const ovrSessionStatus& status) { return status.HasInputFocus != ovrFalse; }
// Convenience method for looping over each eye with a lambda
static inline void for_each_eye(const std::function<void(ovrEyeType eye)>& f) {
for (ovrEyeType eye = ovrEye_Left; eye < ovrEye_Count; eye = static_cast<ovrEyeType>(eye + 1)) {
f(eye);
}
}
}
template <typename Function>
inline void ovr_for_each_hand(Function function) {
for (ovrHandType hand = ovrHandType::ovrHand_Left;
hand <= ovrHandType::ovrHand_Right;
hand = static_cast<ovrHandType>(hand + 1)) {
function(hand);
static inline void for_each_hand(const std::function<void(ovrHandType eye)>& f) {
for (ovrHandType hand = ovrHand_Left; hand < ovrHand_Count; hand = static_cast<ovrHandType>(hand + 1)) {
f(hand);
}
}
}
static inline glm::mat4 toGlm(const ovrMatrix4f& om) {
return glm::transpose(glm::make_mat4(&om.M[0][0]));
}
static inline glm::mat4 toGlm(const ovrFovPort& fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) {
return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true));
}
static inline glm::vec3 toGlm(const ovrVector3f& ov) {
return glm::make_vec3(&ov.x);
}
static inline glm::vec2 toGlm(const ovrVector2f& ov) {
return glm::make_vec2(&ov.x);
}
inline glm::mat4 toGlm(const ovrMatrix4f & om) {
return glm::transpose(glm::make_mat4(&om.M[0][0]));
}
static inline glm::uvec2 toGlm(const ovrSizei& ov) {
return glm::uvec2(ov.w, ov.h);
}
inline glm::mat4 toGlm(const ovrFovPort & fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) {
return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true));
}
static inline glm::quat toGlm(const ovrQuatf& oq) {
return glm::make_quat(&oq.x);
}
inline glm::vec3 toGlm(const ovrVector3f & ov) {
return glm::make_vec3(&ov.x);
}
static inline glm::mat4 toGlm(const ovrPosef& op) {
glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
return translation * orientation;
}
inline glm::vec2 toGlm(const ovrVector2f & ov) {
return glm::make_vec2(&ov.x);
}
static inline ovrMatrix4f fromGlm(const glm::mat4& m) {
ovrMatrix4f result;
glm::mat4 transposed(glm::transpose(m));
memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
return result;
}
inline glm::uvec2 toGlm(const ovrSizei & ov) {
return glm::uvec2(ov.w, ov.h);
}
static inline ovrVector3f fromGlm(const glm::vec3& v) {
return { v.x, v.y, v.z };
}
inline glm::quat toGlm(const ovrQuatf & oq) {
return glm::make_quat(&oq.x);
}
static inline ovrVector2f fromGlm(const glm::vec2& v) {
return { v.x, v.y };
}
inline glm::mat4 toGlm(const ovrPosef & op) {
glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
return translation * orientation;
}
static inline ovrSizei fromGlm(const glm::uvec2& v) {
return { (int)v.x, (int)v.y };
}
inline ovrMatrix4f ovrFromGlm(const glm::mat4 & m) {
ovrMatrix4f result;
glm::mat4 transposed(glm::transpose(m));
memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
return result;
}
static inline ovrQuatf fromGlm(const glm::quat& q) {
return { q.x, q.y, q.z, q.w };
}
inline ovrVector3f ovrFromGlm(const glm::vec3 & v) {
return{ v.x, v.y, v.z };
}
static inline ovrPosef poseFromGlm(const glm::mat4& m) {
glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
glm::quat orientation = glm::quat_cast(m);
ovrPosef result;
result.Orientation = fromGlm(orientation);
result.Position = fromGlm(translation);
return result;
}
inline ovrVector2f ovrFromGlm(const glm::vec2 & v) {
return{ v.x, v.y };
}
static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose);
static controller::Pose toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose);
inline ovrSizei ovrFromGlm(const glm::uvec2 & v) {
return{ (int)v.x, (int)v.y };
}
};
inline ovrQuatf ovrFromGlm(const glm::quat & q) {
return{ q.x, q.y, q.z, q.w };
}
inline ovrPosef ovrPoseFromGlm(const glm::mat4 & m) {
glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
glm::quat orientation = glm::quat_cast(m);
ovrPosef result;
result.Orientation = ovrFromGlm(orientation);
result.Position = ovrFromGlm(translation);
return result;
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose);
controller::Pose ovrControllerRotationToHandRotation(ovrHandType hand,
const ovrPoseStatef& handPose, const ovrPoseStatef& lastHandPose);
} // namespace hifi

View file

@ -57,7 +57,6 @@ if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_I
menuItemName: MENU_ITEM,
isCheckable: true,
isChecked: previousSetting,
grouping: "Advanced"
});
}

View file

@ -2362,26 +2362,21 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");
var propertiesTool = new PropertiesTool();
var particleExplorerTool = new ParticleExplorerTool();
var selectedParticleEntity = 0;
var selectedParticleEntityID = null;
function selectParticleEntity(entityID) {
var properties = Entities.getEntityProperties(entityID);
selectedParticleEntityID = entityID;
var properties = Entities.getEntityProperties(entityID);
if (properties.emitOrientation) {
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
}
var particleData = {
messageType: "particle_settings",
currentProperties: properties
};
particleExplorerTool.destroyWebView();
particleExplorerTool.createWebView();
selectedParticleEntity = entityID;
particleExplorerTool.setActiveParticleEntity(entityID);
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
particleExplorerTool.setActiveParticleProperties(properties);
// Switch to particle explorer
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
@ -2404,13 +2399,13 @@ entityListTool.webView.webEventReceived.connect(function (data) {
var ids = data.entityIds;
if (ids.length === 1) {
if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
if (JSON.stringify(selectedParticleEntity) === JSON.stringify(ids[0])) {
if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) {
// This particle entity is already selected, so return
return;
}
// Destroy the old particles web view first
} else {
selectedParticleEntity = 0;
selectedParticleEntityID = 0;
particleExplorerTool.destroyWebView();
}
}

View file

@ -115,7 +115,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
}),
dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth },
parentID: MyAvatar.SELF_ID,
visible: visible
visible: visible,
isGroupCulled: true
};
// compute position, rotation & parentJointIndex of the tablet

View file

@ -91,7 +91,7 @@ CameraManager = function() {
}
var CAPTURED_KEYS = [];
for (key in keyToActionMapping) {
for (var key in keyToActionMapping) {
CAPTURED_KEYS.push(key);
}
@ -99,9 +99,9 @@ CameraManager = function() {
var action = keyToActionMapping[event.text];
if (action !== undefined) {
if (event.isShifted) {
if (action == "orbitForward") {
if (action === "orbitForward") {
action = "orbitUp";
} else if (action == "orbitBackward") {
} else if (action === "orbitBackward") {
action = "orbitDown";
}
}
@ -133,7 +133,7 @@ CameraManager = function() {
};
that.enable = function() {
if (Camera.mode == "independent" || that.enabled || HMD.active) {
if (Camera.mode === "independent" || that.enabled || HMD.active) {
return;
}
@ -235,7 +235,7 @@ CameraManager = function() {
}
that.setFocalPoint = function(pos) {
that.targetFocalPoint = pos
that.targetFocalPoint = pos;
that.updateCamera();
}
@ -276,7 +276,7 @@ CameraManager = function() {
}
that.mouseMoveEvent = function(event) {
if (that.enabled && that.mode != MODE_INACTIVE) {
if (that.enabled && that.mode !== MODE_INACTIVE) {
var x = Reticle.getPosition().x;
var y = Reticle.getPosition().y;
if (!hasDragged) {
@ -284,11 +284,11 @@ CameraManager = function() {
that.lastMousePosition.y = y;
hasDragged = true;
}
if (that.mode == MODE_ORBIT) {
if (that.mode === MODE_ORBIT) {
var diffX = x - that.lastMousePosition.x;
var diffY = y - that.lastMousePosition.y;
that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0)
that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0)
that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0);
that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0);
while (that.targetYaw > 180.0) that.targetYaw -= 360;
while (that.targetYaw < -180.0) that.targetYaw += 360;
@ -297,7 +297,7 @@ CameraManager = function() {
if (that.targetPitch < -90) that.targetPitch = -90;
that.updateCamera();
} else if (that.mode == MODE_PAN) {
} else if (that.mode === MODE_PAN) {
var diffX = x - that.lastMousePosition.x;
var diffY = y - that.lastMousePosition.y;
@ -316,19 +316,19 @@ CameraManager = function() {
var newY = y;
var updatePosition = false;
if (x <= Window.x) {
newX = Window.x + Window.innerWidth;
if (x <= 0) {
newX = Window.innerWidth;
updatePosition = true;
} else if (x >= (Window.x + Window.innerWidth)) {
newX = Window.x;
} else if (x >= Window.innerWidth) {
newX = 0;
updatePosition = true;
}
if (y <= Window.y) {
newY = Window.y + Window.innerHeight;
if (y <= 0) {
newY = Window.innerHeight;
updatePosition = true;
} else if (y >= (Window.y + Window.innerHeight)) {
newY = Window.y;
} else if (y >= Window.innerHeight) {
newY = 0;
updatePosition = true;
}
@ -410,7 +410,7 @@ CameraManager = function() {
}
that.updateCamera = function() {
if (!that.enabled || Camera.mode != "independent") {
if (!that.enabled || Camera.mode !== "independent") {
cameraTool.update();
return;
}
@ -464,7 +464,7 @@ CameraManager = function() {
// Ease the position and orbit of the camera
that.update = function(dt) {
if (Camera.mode != "independent") {
if (Camera.mode !== "independent") {
that.updateCamera();
return;
}

View file

@ -1036,6 +1036,29 @@ SelectionDisplay = (function() {
}
};
// FUNCTION: TOGGLE SPACE MODE
that.toggleSpaceMode = function() {
var wantDebug = false;
if (wantDebug) {
print("========> ToggleSpaceMode called. =========");
}
if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) {
if (wantDebug) {
print("Local space editing is not available with multiple selections");
}
return;
}
if (wantDebug) {
print("PreToggle: " + spaceMode);
}
spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL;
that.updateHandles();
if (wantDebug) {
print("PostToggle: " + spaceMode);
print("======== ToggleSpaceMode called. <=========");
}
};
function addHandleTool(overlay, tool) {
handleTools[overlay] = tool;
return tool;

View file

@ -16,37 +16,62 @@ var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html');
ParticleExplorerTool = function() {
var that = {};
that.activeParticleEntity = 0;
that.activeParticleProperties = {};
that.createWebView = function() {
that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
that.webView.setVisible = function(value) {};
that.webView.webEventReceived.connect(that.webEventReceived);
}
};
that.destroyWebView = function() {
if (!that.webView) {
return;
}
that.activeParticleEntity = 0;
that.activeParticleProperties = {};
var messageData = {
messageType: "particle_close"
};
that.webView.emitScriptEvent(JSON.stringify(messageData));
};
function sendActiveParticleProperties() {
that.webView.emitScriptEvent(JSON.stringify({
messageType: "particle_settings",
currentProperties: that.activeParticleProperties
}));
}
that.webEventReceived = function(data) {
var data = JSON.parse(data);
that.webEventReceived = function(message) {
var data = JSON.parse(message);
if (data.messageType === "settings_update") {
if (data.updatedSettings.emitOrientation) {
data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation);
}
Entities.editEntity(that.activeParticleEntity, data.updatedSettings);
for (var key in data.updatedSettings) {
if (that.activeParticleProperties.hasOwnProperty(key)) {
that.activeParticleProperties[key] = data.updatedSettings[key];
}
}
} else if (data.messageType === "page_loaded") {
sendActiveParticleProperties();
}
}
};
that.setActiveParticleEntity = function(id) {
that.activeParticleEntity = id;
}
};
that.setActiveParticleProperties = function(properties) {
that.activeParticleProperties = properties;
sendActiveParticleProperties();
};
return that;
};

View file

@ -295,6 +295,7 @@ function printToPolaroid(image_url) {
"description": "Printed from Snaps",
"modelURL": POLAROID_MODEL_URL,
"dimensions": { "x": 0.5667, "y": 0.0212, "z": 0.4176 },
"position": model_pos,
"rotation": model_rot,