mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 14:29:35 +02:00
merge from upstream
This commit is contained in:
commit
64a46bf5df
107 changed files with 1469 additions and 608 deletions
35
cmake/externals/crashpad/CMakeLists.txt
vendored
Normal file
35
cmake/externals/crashpad/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
include(ExternalProject)
|
||||
set(EXTERNAL_NAME crashpad)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://backtrace.io/download/crashpad_062317.zip
|
||||
URL_MD5 65817e564b3628492abfc1dbd2a1e98b
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories")
|
||||
|
||||
set(LIB_EXT "lib")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library")
|
||||
|
||||
set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable")
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|
@ -1,34 +0,0 @@
|
|||
#
|
||||
# AddBugSplat.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(add_bugsplat)
|
||||
get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE)
|
||||
|
||||
if (NOT BUGSPLAT_CHECKED)
|
||||
find_package(BugSplat)
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE)
|
||||
endif ()
|
||||
|
||||
if (BUGSPLAT_FOUND)
|
||||
add_definitions(-DHAS_BUGSPLAT)
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES})
|
||||
add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH})
|
||||
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
endif ()
|
||||
endmacro()
|
58
cmake/macros/AddCrashpad.cmake
Normal file
58
cmake/macros/AddCrashpad.cmake
Normal file
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# AddCrashpad.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Clement Brisset on 01/19/18.
|
||||
# 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
|
||||
#
|
||||
|
||||
macro(add_crashpad)
|
||||
set (USE_CRASHPAD TRUE)
|
||||
if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL})
|
||||
endif()
|
||||
|
||||
if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN})
|
||||
endif()
|
||||
|
||||
if (WIN32 AND USE_CRASHPAD)
|
||||
get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE)
|
||||
if (NOT CRASHPAD_CHECKED)
|
||||
|
||||
add_dependency_external_projects(crashpad)
|
||||
find_package(crashpad REQUIRED)
|
||||
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE)
|
||||
endif()
|
||||
|
||||
add_definitions(-DHAS_CRASHPAD)
|
||||
add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\")
|
||||
add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\")
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY})
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
)
|
||||
install(
|
||||
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
|
||||
DESTINATION ${INTERFACE_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
7
cmake/macros/AddCustomQrcPath.cmake
Normal file
7
cmake/macros/AddCustomQrcPath.cmake
Normal file
|
@ -0,0 +1,7 @@
|
|||
# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which
|
||||
# can be given to the GENERATE_QRC command
|
||||
|
||||
function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH)
|
||||
list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}")
|
||||
set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE)
|
||||
endfunction()
|
14
cmake/macros/FindNPM.cmake
Normal file
14
cmake/macros/FindNPM.cmake
Normal file
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# FindNPM.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Thijs Wenker on 01/23/18.
|
||||
# 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
|
||||
#
|
||||
|
||||
macro(find_npm)
|
||||
find_program(NPM_EXECUTABLE "npm")
|
||||
endmacro()
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
function(GENERATE_QRC)
|
||||
set(oneValueArgs OUTPUT PREFIX PATH)
|
||||
set(multiValueArgs GLOBS)
|
||||
set(multiValueArgs CUSTOM_PATHS GLOBS)
|
||||
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
|
||||
set(QRC_PREFIX_PATH /)
|
||||
|
@ -20,6 +20,13 @@ function(GENERATE_QRC)
|
|||
endforeach()
|
||||
endforeach()
|
||||
|
||||
foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS})
|
||||
string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH})
|
||||
list(GET CUSTOM_PATH 0 IMPORT_PATH)
|
||||
list(GET CUSTOM_PATH 1 LOCAL_PATH)
|
||||
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${LOCAL_PATH}\">${IMPORT_PATH}</file>\n")
|
||||
endforeach()
|
||||
|
||||
set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE)
|
||||
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
|
||||
endfunction()
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#
|
||||
# FindBugSplat.cmake
|
||||
# cmake/modules
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (WIN32)
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("BugSplat")
|
||||
|
||||
find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(BUGSPLAT)
|
||||
|
||||
set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE})
|
||||
endif ()
|
||||
|
||||
set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH)
|
||||
find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS})
|
41
cmake/modules/FindCrashpad.cmake
Normal file
41
cmake/modules/FindCrashpad.cmake
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# FindCrashpad.cmake
|
||||
#
|
||||
# Try to find Crashpad libraries and include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# CRASHPAD_FOUND
|
||||
# CRASHPAD_INCLUDE_DIRS
|
||||
# CRASHPAD_LIBRARY
|
||||
# CRASHPAD_BASE_LIBRARY
|
||||
# CRASHPAD_UTIL_LIBRARY
|
||||
#
|
||||
# Created on 01/19/2018 by Clement Brisset
|
||||
# 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("crashpad")
|
||||
|
||||
find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(CRASHPAD)
|
||||
select_library_configurations(CRASHPAD_BASE)
|
||||
select_library_configurations(CRASHPAD_UTIL)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY)
|
|
@ -823,6 +823,7 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\server-console.exe"
|
||||
RMDir /r "$INSTDIR\locales"
|
||||
RMDir /r "$INSTDIR\resources\app"
|
||||
RMDir /r "$INSTDIR\client"
|
||||
Delete "$INSTDIR\resources\atom.asar"
|
||||
Delete "$INSTDIR\build-info.json"
|
||||
Delete "$INSTDIR\content_resources_200_percent.pak"
|
||||
|
|
|
@ -157,7 +157,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
DependencyManager::set<StatTracker>();
|
||||
|
||||
LogUtils::init();
|
||||
Setting::init();
|
||||
|
||||
qDebug() << "Setting up domain-server";
|
||||
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
|
|
|
@ -29,6 +29,8 @@ int main(int argc, char* argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
#ifndef WIN32
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
|
|
@ -11,9 +11,17 @@ function(JOIN VALUES GLUE OUTPUT)
|
|||
set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(CUSTOM_INTERFACE_QRC_PATHS "")
|
||||
|
||||
find_npm()
|
||||
|
||||
if (BUILD_TOOLS AND NPM_EXECUTABLE)
|
||||
add_custom_qrc_path(CUSTOM_INTERFACE_QRC_PATHS "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "auto-complete/hifiJSDoc.json")
|
||||
endif ()
|
||||
|
||||
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
|
@ -163,6 +171,11 @@ else ()
|
|||
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
|
||||
endif ()
|
||||
|
||||
if (BUILD_TOOLS AND NPM_EXECUTABLE)
|
||||
# require JSDoc to be build before interface is deployed (Console Auto-complete)
|
||||
add_dependencies(resources jsdoc)
|
||||
endif()
|
||||
|
||||
add_dependencies(${TARGET_NAME} resources)
|
||||
|
||||
if (WIN32)
|
||||
|
@ -206,6 +219,7 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries
|
|||
|
||||
target_bullet()
|
||||
target_opengl()
|
||||
add_crashpad()
|
||||
|
||||
# perform standard include and linking for found externals
|
||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||
|
@ -289,6 +303,7 @@ if (APPLE)
|
|||
|
||||
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
||||
|
||||
|
||||
# copy script files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
|
@ -350,8 +365,6 @@ if (SCRIPTS_INSTALL_DIR)
|
|||
)
|
||||
endif()
|
||||
|
||||
add_bugsplat()
|
||||
|
||||
if (WIN32)
|
||||
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"")
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ Column {
|
|||
property int labelSize: 20;
|
||||
|
||||
property string metaverseServerUrl: '';
|
||||
property string protocol: '';
|
||||
property string actions: 'snapshot';
|
||||
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
|
||||
property string labelText: actions;
|
||||
|
@ -102,7 +103,7 @@ Column {
|
|||
'include_actions=' + actions,
|
||||
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
|
||||
'require_online=true',
|
||||
'protocol=' + encodeURIComponent(Window.protocolSignature()),
|
||||
'protocol=' + protocol,
|
||||
'page=' + pageNumber
|
||||
];
|
||||
var url = metaverseBase + 'user_stories?' + options.join('&');
|
||||
|
|
|
@ -12,7 +12,7 @@ StackView {
|
|||
HifiConstants { id: hifi }
|
||||
|
||||
function pushSource(path) {
|
||||
editRoot.push(Qt.resolvedUrl(path));
|
||||
editRoot.push(Qt.resolvedUrl("../../" + path));
|
||||
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ StackView {
|
|||
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
|
||||
|
||||
feeds.forEach(function(feed) {
|
||||
feed.protocol = encodeURIComponent(message.protocolSignature);
|
||||
Qt.callLater(feed.fillDestinations);
|
||||
});
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ Item {
|
|||
|
||||
GridView {
|
||||
id: gridView
|
||||
|
||||
flickableDirection: Flickable.AutoFlickIfNeeded
|
||||
keyNavigationEnabled: false
|
||||
highlightFollowsCurrentItem: false
|
||||
|
||||
|
@ -144,23 +144,20 @@ Item {
|
|||
bottomMargin: 0
|
||||
}
|
||||
|
||||
function setButtonState(buttonIndex, buttonstate) {
|
||||
if (buttonIndex < 0 || gridView.contentItem === undefined
|
||||
|| gridView.contentItem.children.length - 1 < buttonIndex) {
|
||||
return;
|
||||
}
|
||||
var itemat = gridView.contentItem.children[buttonIndex].children[0];
|
||||
if (itemat.isActive) {
|
||||
itemat.state = "active state";
|
||||
} else {
|
||||
itemat.state = buttonstate;
|
||||
}
|
||||
onCurrentIndexChanged: {
|
||||
previousGridIndex = currentIndex
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
setButtonState(previousGridIndex, "base state");
|
||||
setButtonState(currentIndex, "hover state");
|
||||
previousGridIndex = currentIndex
|
||||
onMovementStarted: {
|
||||
if (currentIndex < 0 || gridView.currentItem === undefined || gridView.contentItem.children.length - 1 < currentIndex) {
|
||||
return;
|
||||
}
|
||||
var button = gridView.contentItem.children[currentIndex].children[0];
|
||||
if (button.isActive) {
|
||||
button.state = "active state";
|
||||
} else {
|
||||
button.state = "base state";
|
||||
}
|
||||
}
|
||||
|
||||
cellWidth: width/3
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
#include <Midi.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AvatarBookmarks.h>
|
||||
#include <AvatarEntitiesBookmarks.h>
|
||||
#include <CursorManager.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
|
@ -148,6 +149,7 @@
|
|||
#include "avatar/AvatarManager.h"
|
||||
#include "avatar/MyHead.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "Crashpad.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "DiscoverabilityManager.h"
|
||||
#include "GLCanvas.h"
|
||||
|
@ -395,6 +397,7 @@ public:
|
|||
setObjectName("Deadlock Watchdog");
|
||||
// Give the heartbeat an initial value
|
||||
_heartbeat = usecTimestampNow();
|
||||
_paused = false;
|
||||
connect(qApp, &QCoreApplication::aboutToQuit, [this] {
|
||||
_quit = true;
|
||||
});
|
||||
|
@ -412,11 +415,20 @@ public:
|
|||
*crashTrigger = 0xDEAD10CC;
|
||||
}
|
||||
|
||||
static void pause() {
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
static void resume() {
|
||||
_paused = false;
|
||||
updateHeartbeat();
|
||||
}
|
||||
|
||||
void run() override {
|
||||
while (!_quit) {
|
||||
QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS);
|
||||
// Don't do heartbeat detection under nsight
|
||||
if (nsightActive()) {
|
||||
if (nsightActive() || _paused) {
|
||||
continue;
|
||||
}
|
||||
uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us
|
||||
|
@ -472,6 +484,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static std::atomic<bool> _paused;
|
||||
static std::atomic<uint64_t> _heartbeat;
|
||||
static std::atomic<uint64_t> _maxElapsed;
|
||||
static std::atomic<int> _maxElapsedAverage;
|
||||
|
@ -480,6 +493,7 @@ public:
|
|||
bool _quit { false };
|
||||
};
|
||||
|
||||
std::atomic<bool> DeadlockWatchdogThread::_paused;
|
||||
std::atomic<uint64_t> DeadlockWatchdogThread::_heartbeat;
|
||||
std::atomic<uint64_t> DeadlockWatchdogThread::_maxElapsed;
|
||||
std::atomic<int> DeadlockWatchdogThread::_maxElapsedAverage;
|
||||
|
@ -655,8 +669,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
}
|
||||
#endif
|
||||
|
||||
Setting::init();
|
||||
|
||||
// Tell the plugin manager about our statically linked plugins
|
||||
auto pluginManager = PluginManager::getInstance();
|
||||
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
|
||||
|
@ -763,6 +775,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<GooglePolyScriptingInterface>();
|
||||
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
|
||||
DependencyManager::set<AvatarBookmarks>();
|
||||
DependencyManager::set<AvatarEntitiesBookmarks>();
|
||||
DependencyManager::set<LocationBookmarks>();
|
||||
DependencyManager::set<Snapshot>();
|
||||
DependencyManager::set<CloseEventSender>();
|
||||
|
@ -907,6 +920,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
_logger->setSessionID(accountManager->getSessionID());
|
||||
|
||||
setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
|
||||
|
||||
if (steamClient) {
|
||||
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
|
||||
}
|
||||
|
@ -1906,7 +1921,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
entityResult.distance = pickResult->distance;
|
||||
entityResult.surfaceNormal = pickResult->surfaceNormal;
|
||||
entityResult.entityID = pickResult->objectID;
|
||||
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
|
||||
entityResult.extraInfo = pickResult->extraInfo;
|
||||
}
|
||||
}
|
||||
return entityResult;
|
||||
|
@ -2267,6 +2282,11 @@ void Application::initializeGL() {
|
|||
initDisplay();
|
||||
qCDebug(interfaceapp, "Initialized Display.");
|
||||
|
||||
#ifdef Q_OS_OSX
|
||||
// FIXME: on mac os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
|
||||
DeadlockWatchdogThread::pause();
|
||||
#endif
|
||||
|
||||
// Set up the render engine
|
||||
render::CullFunctor cullFunctor = LODManager::shouldRender;
|
||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||
|
@ -2275,6 +2295,11 @@ void Application::initializeGL() {
|
|||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED);
|
||||
#endif
|
||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
|
||||
|
||||
#ifdef Q_OS_OSX
|
||||
DeadlockWatchdogThread::resume();
|
||||
#endif
|
||||
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
|
||||
|
@ -2443,6 +2468,7 @@ void Application::initializeUi() {
|
|||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
// Caches
|
||||
|
@ -5851,6 +5877,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
|
||||
|
@ -6268,7 +6295,7 @@ void Application::showAssetServerWidget(QString filePath) {
|
|||
if (!hmd->getShouldShowTablet() && !isHMDMode()) {
|
||||
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
|
||||
} else {
|
||||
static const QUrl url("../dialogs/TabletAssetServer.qml");
|
||||
static const QUrl url("hifi/dialogs/TabletAssetServer.qml");
|
||||
tablet->pushOntoStack(url);
|
||||
}
|
||||
}
|
||||
|
|
168
interface/src/AvatarEntitiesBookmarks.cpp
Normal file
168
interface/src/AvatarEntitiesBookmarks.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// 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 <QAction>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QQmlContext>
|
||||
#include <QList>
|
||||
|
||||
#include <Application.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <EntityItemProperties.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntityTree.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
|
||||
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
|
||||
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
|
||||
QScriptEngine scriptEngine;
|
||||
for (int index = 0; index < avatarEntities.count(); index++) {
|
||||
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
|
||||
QVariant variantProperties = avatarEntityProperties["properties"];
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties entityProperties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
|
||||
|
||||
entityProperties.setParentID(myNodeID);
|
||||
entityProperties.setClientOnly(true);
|
||||
entityProperties.setOwningAvatarID(myNodeID);
|
||||
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
entityProperties.markAllChanged();
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
bool success = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
|
||||
if (entity) {
|
||||
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
entityProperties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
entityProperties.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() {
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME;
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities);
|
||||
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
|
||||
_bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks);
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark);
|
||||
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
|
||||
|
||||
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
|
||||
addBookmarkToMenu(menubar, it.key(), it.value());
|
||||
}
|
||||
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QMap<QString, QVariant> bookmark = action->data().toMap();
|
||||
|
||||
if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) {
|
||||
myAvatar->removeAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(avatarScale);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark";
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmark() {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString());
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
auto bookmarkName = response.toString();
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
|
||||
QVariantMap *bookmark = new QVariantMap;
|
||||
bookmark->insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION);
|
||||
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark->insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
|
||||
QAction* changeAction = _bookmarksMenu->newAction();
|
||||
changeAction->setData(bookmark);
|
||||
connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities()));
|
||||
if (!_isMenuSorted) {
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
} else {
|
||||
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
}
|
45
interface/src/AvatarEntitiesBookmarks.h
Normal file
45
interface/src/AvatarEntitiesBookmarks.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarEntitiesBookmarks_h
|
||||
#define hifi_AvatarEntitiesBookmarks_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "Bookmarks.h"
|
||||
|
||||
class AvatarEntitiesBookmarks: public Bookmarks, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
AvatarEntitiesBookmarks();
|
||||
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
|
||||
|
||||
public slots:
|
||||
void addBookmark();
|
||||
|
||||
protected:
|
||||
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
|
||||
|
||||
private:
|
||||
const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "AvatarUrl";
|
||||
const QString ENTRY_AVATAR_SCALE = "AvatarScale";
|
||||
const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities";
|
||||
const QString ENTRY_VERSION = "version";
|
||||
|
||||
const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1;
|
||||
|
||||
private slots:
|
||||
void applyBookmarkedAvatarEntities();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,166 +0,0 @@
|
|||
//
|
||||
// CrashReporter.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 11 April 2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "Application.h"
|
||||
#include "CrashReporter.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <new.h>
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
|
||||
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
||||
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
|
||||
// that the applicaiton can handle it itself.
|
||||
// The CAPIHook class referenced in the above article is not openly available, but a similar implementation
|
||||
// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/
|
||||
// The below has been adapted to work with different library and functions.
|
||||
BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn)
|
||||
{
|
||||
HMODULE lib = LoadLibrary(library);
|
||||
if (lib == NULL) return FALSE;
|
||||
void *pOrgEntry = GetProcAddress(lib, function);
|
||||
if (pOrgEntry == NULL) return FALSE;
|
||||
|
||||
DWORD dwOldProtect = 0;
|
||||
SIZE_T jmpSize = 5;
|
||||
#ifdef _M_X64
|
||||
jmpSize = 13;
|
||||
#endif
|
||||
BOOL bProt = VirtualProtect(pOrgEntry, jmpSize,
|
||||
PAGE_EXECUTE_READWRITE, &dwOldProtect);
|
||||
BYTE newJump[20];
|
||||
void *pNewFunc = fn;
|
||||
#ifdef _M_IX86
|
||||
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
|
||||
dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32
|
||||
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
|
||||
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
|
||||
// JMP rel32: Jump near, relative, displacement relative to next instruction.
|
||||
newJump[0] = 0xE9; // JMP rel32
|
||||
memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
|
||||
#elif _M_X64
|
||||
// We must use R10 or R11, because these are "scratch" registers
|
||||
// which need not to be preserved accross function calls
|
||||
// For more info see: Register Usage for x64 64-Bit
|
||||
// http://msdn.microsoft.com/en-us/library/ms794547.aspx
|
||||
// Thanks to Matthew Smith!!!
|
||||
newJump[0] = 0x49; // MOV R11, ...
|
||||
newJump[1] = 0xBB; // ...
|
||||
memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc));
|
||||
//pCur += sizeof (ULONG_PTR);
|
||||
newJump[10] = 0x41; // JMP R11, ...
|
||||
newJump[11] = 0xFF; // ...
|
||||
newJump[12] = 0xE3; // ...
|
||||
#endif
|
||||
SIZE_T bytesWritten;
|
||||
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
|
||||
pOrgEntry, newJump, jmpSize, &bytesWritten);
|
||||
|
||||
if (bProt != FALSE)
|
||||
{
|
||||
DWORD dwBuf;
|
||||
VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf);
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
void printStackTrace(ULONG framesToSkip = 1) {
|
||||
HANDLE process = GetCurrentProcess();
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
void* stack[100];
|
||||
uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL);
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
|
||||
symbol->MaxNameLen = 255;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
|
||||
for (uint16_t i = 0; i < frames; ++i) {
|
||||
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
|
||||
qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16));
|
||||
}
|
||||
|
||||
free(symbol);
|
||||
|
||||
// Try to force the log to sync to the filesystem
|
||||
auto app = qApp;
|
||||
if (app && app->getLogger()) {
|
||||
app->getLogger()->sync();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSignal(int signal) {
|
||||
// Throw so BugSplat can handle
|
||||
throw(signal);
|
||||
}
|
||||
|
||||
void __cdecl handlePureVirtualCall() {
|
||||
qWarning() << "Pure virtual function call detected";
|
||||
printStackTrace(2);
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Pure virtual call");
|
||||
}
|
||||
|
||||
void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file,
|
||||
unsigned int line, uintptr_t pReserved ) {
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Invalid parameter");
|
||||
}
|
||||
|
||||
int handleNewError(size_t size) {
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Errors calling new");
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI noop_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
|
||||
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
||||
|
||||
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
|
||||
: mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS)
|
||||
{
|
||||
signal(SIGSEGV, handleSignal);
|
||||
signal(SIGABRT, handleSignal);
|
||||
_set_purecall_handler(handlePureVirtualCall);
|
||||
_set_invalid_parameter_handler(handleInvalidParameter);
|
||||
_set_new_mode(1);
|
||||
_set_new_handler(handleNewError);
|
||||
|
||||
// Disable WER popup
|
||||
//SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
|
||||
//_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||
|
||||
// QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that.
|
||||
if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) {
|
||||
qWarning() << "Failed to patch _set_purecall_handler";
|
||||
}
|
||||
// Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling.
|
||||
if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) {
|
||||
qWarning() << "Failed to patch setUnhandledExceptionFilter";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// CrashReporter.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 11 April 2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_CrashReporter_h
|
||||
#define hifi_CrashReporter_h
|
||||
|
||||
#include <QString>
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
|
||||
#include <BugSplat.h>
|
||||
|
||||
class CrashReporter {
|
||||
public:
|
||||
CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version);
|
||||
|
||||
MiniDmpSender mpSender;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // hifi_CrashReporter_h
|
101
interface/src/Crashpad.cpp
Normal file
101
interface/src/Crashpad.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// Crashpad.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Clement Brisset on 01/19/18.
|
||||
// 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 "Crashpad.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#if HAS_CRASHPAD
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
|
||||
#include <client/crashpad_client.h>
|
||||
#include <client/crash_report_database.h>
|
||||
#include <client/settings.h>
|
||||
// #include <client/annotation_list.h>
|
||||
// #include <client/crashpad_info.h>
|
||||
|
||||
using namespace crashpad;
|
||||
|
||||
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
|
||||
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
|
||||
|
||||
extern QString qAppFileName();
|
||||
|
||||
// crashpad::AnnotationList* crashpadAnnotations { nullptr };
|
||||
|
||||
bool startCrashHandler() {
|
||||
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CrashpadClient client;
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
std::map<std::string, std::string> annotations;
|
||||
annotations["token"] = BACKTRACE_TOKEN;
|
||||
annotations["format"] = "minidump";
|
||||
annotations["version"] = BuildInfo::VERSION.toStdString();
|
||||
|
||||
arguments.push_back("--no-rate-limit");
|
||||
|
||||
// Setup Crashpad DB directory
|
||||
const auto crashpadDbName = "crashpad-db";
|
||||
const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists
|
||||
const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName;
|
||||
|
||||
// Locate Crashpad handler
|
||||
const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe";
|
||||
|
||||
// Setup different file paths
|
||||
base::FilePath::StringType dbPath;
|
||||
base::FilePath::StringType handlerPath;
|
||||
dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend());
|
||||
handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend());
|
||||
|
||||
base::FilePath db(dbPath);
|
||||
base::FilePath handler(handlerPath);
|
||||
|
||||
auto database = crashpad::CrashReportDatabase::Initialize(db);
|
||||
if (database == nullptr || database->GetSettings() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable automated uploads.
|
||||
database->GetSettings()->SetUploadsEnabled(true);
|
||||
|
||||
return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
// if (!crashpadAnnotations) {
|
||||
// crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak
|
||||
// crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo();
|
||||
// crashpad_info->set_simple_annotations(crashpadAnnotations);
|
||||
// }
|
||||
// crashpadAnnotations->SetKeyValue(name, value);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool startCrashHandler() {
|
||||
qDebug() << "No crash handler available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
}
|
||||
|
||||
#endif
|
20
interface/src/Crashpad.h
Normal file
20
interface/src/Crashpad.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Crashpad.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Clement Brisset on 01/19/18.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_Crashpad_h
|
||||
#define hifi_Crashpad_h
|
||||
|
||||
#include <string>
|
||||
|
||||
bool startCrashHandler();
|
||||
void setCrashAnnotation(std::string name, std::string value);
|
||||
|
||||
#endif // hifi_Crashpad_h
|
|
@ -20,7 +20,7 @@
|
|||
#include <render/Args.h>
|
||||
|
||||
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f;
|
||||
const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f;
|
||||
const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f;
|
||||
const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec
|
||||
const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec
|
||||
const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
|
@ -206,6 +207,9 @@ Menu::Menu() {
|
|||
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
|
||||
avatarBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
|
||||
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
// Display menu ----------------------------------
|
||||
// FIXME - this is not yet matching Alan's spec because it doesn't have
|
||||
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
|
||||
|
|
|
@ -46,9 +46,11 @@ namespace MenuOption {
|
|||
const QString AutoMuteAudio = "Auto Mute Microphone";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString AvatarBookmarks = "Avatar Bookmarks";
|
||||
const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks";
|
||||
const QString Back = "Back";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkAvatar = "Bookmark Avatar";
|
||||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString CalibrateCamera = "Calibrate Camera";
|
||||
const QString CameraEntityMode = "Entity Mode";
|
||||
|
@ -78,6 +80,7 @@ namespace MenuOption {
|
|||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DefaultSkybox = "Default Skybox";
|
||||
const QString DeleteAvatarBookmark = "Delete Avatar Bookmark...";
|
||||
const QString DeleteAvatarEntitiesBookmark = "Delete Avatar Entities Bookmark";
|
||||
const QString DeleteBookmark = "Delete Bookmark...";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
|
|
|
@ -546,7 +546,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
continue;
|
||||
}
|
||||
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection,
|
||||
distance, face, surfaceNormal, extraInfo, true);
|
||||
|
||||
|
@ -554,6 +554,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
result.intersects = true;
|
||||
result.avatarID = avatar->getID();
|
||||
result.distance = distance;
|
||||
result.extraInfo = extraInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ModelEntityItem.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
|
@ -1440,6 +1442,37 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
}
|
||||
|
||||
void MyAvatar::removeAvatarEntities() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList MyAvatar::getAvatarEntitiesVariant() {
|
||||
QVariantList avatarEntitiesData;
|
||||
QScriptEngine scriptEngine;
|
||||
forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(child);
|
||||
if (modelEntity) {
|
||||
QVariantMap avatarEntityData;
|
||||
EntityItemProperties entityProperties = modelEntity->getProperties();
|
||||
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
|
||||
avatarEntityData["properties"] = scriptProperties.toVariant();
|
||||
avatarEntitiesData.append(QVariant(avatarEntityData));
|
||||
}
|
||||
}
|
||||
});
|
||||
return avatarEntitiesData;
|
||||
}
|
||||
|
||||
|
||||
void MyAvatar::resetFullAvatarURL() {
|
||||
auto lastAvatarURL = getFullAvatarURLFromPreferences();
|
||||
|
@ -2440,7 +2473,6 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
};
|
||||
auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
float distance;
|
||||
BoxFace face;
|
||||
const bool visibleOnly = false;
|
||||
|
@ -2452,13 +2484,14 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
|
||||
bool* accurateResult = NULL;
|
||||
|
||||
bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult);
|
||||
if (!intersects || !intersectedEntity) {
|
||||
QVariantMap extraInfo;
|
||||
EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
}
|
||||
intersectionOut = startPointIn + (directionIn * distance);
|
||||
entityIdOut = intersectedEntity->getEntityItemID();
|
||||
entityIdOut = entityID;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ class MyAvatar : public Avatar {
|
|||
* and MyAvatar.customListenOrientation properties.
|
||||
* @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position
|
||||
* of audio spatialization listener.
|
||||
* @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
|
||||
* @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
|
||||
* of the audio spatialization listener.
|
||||
* @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
|
||||
* around the avatar's head.
|
||||
|
@ -512,6 +512,9 @@ public:
|
|||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
QVariantList getAvatarEntitiesVariant();
|
||||
void removeAvatarEntities();
|
||||
|
||||
Q_INVOKABLE bool isFlying();
|
||||
Q_INVOKABLE bool isInAir();
|
||||
Q_INVOKABLE void setFlyingEnabled(bool enabled);
|
||||
|
|
|
@ -26,15 +26,11 @@
|
|||
|
||||
#include "AddressManager.h"
|
||||
#include "Application.h"
|
||||
#include "Crashpad.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "UserActivityLogger.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
#include <BugSplat.h>
|
||||
#include <CrashReporter.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
extern "C" {
|
||||
typedef int(__stdcall * CHECKMINSPECPROC) ();
|
||||
|
@ -42,11 +38,6 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
#if HAS_BUGSPLAT
|
||||
static QString BUG_SPLAT_DATABASE = "interface_alpha";
|
||||
static QString BUG_SPLAT_APPLICATION_NAME = "Interface";
|
||||
CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION };
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
|
||||
|
@ -71,6 +62,17 @@ int main(int argc, const char* argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
// Instance UserActivityLogger now that the settings are loaded
|
||||
auto& ual = UserActivityLogger::getInstance();
|
||||
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
|
||||
|
||||
if (ual.isEnabled()) {
|
||||
auto crashHandlerStarted = startCrashHandler();
|
||||
qDebug() << "Crash handler started:" << crashHandlerStarted;
|
||||
}
|
||||
|
||||
QStringList arguments;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
arguments << argv[i];
|
||||
|
@ -259,7 +261,6 @@ int main(int argc, const char* argv[]) {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Setup local server
|
||||
QLocalServer server { &app };
|
||||
|
@ -268,29 +269,8 @@ int main(int argc, const char* argv[]) {
|
|||
server.removeServer(applicationName);
|
||||
server.listen(applicationName);
|
||||
|
||||
QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection);
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername()));
|
||||
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) {
|
||||
crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername));
|
||||
});
|
||||
|
||||
// BugSplat WILL NOT work with file paths that do not use OS native separators.
|
||||
auto logger = app.getLogger();
|
||||
auto logPath = QDir::toNativeSeparators(logger->getFilename());
|
||||
crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath));
|
||||
|
||||
QMetaObject::Connection connection;
|
||||
connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) {
|
||||
// We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB
|
||||
// zipped limit, so we disconnect here.
|
||||
QObject::disconnect(connection);
|
||||
auto rolledLogPath = QDir::toNativeSeparators(newFilename);
|
||||
crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath));
|
||||
});
|
||||
#endif
|
||||
QObject::connect(&server, &QLocalServer::newConnection,
|
||||
&app, &Application::handleLocalServerConnection, Qt::DirectConnection);
|
||||
|
||||
printSystemInformation();
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
@ -127,6 +128,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
|||
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (overlayRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
|
||||
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (avatarRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ class RayPickResult : public PickResult {
|
|||
public:
|
||||
RayPickResult() {}
|
||||
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
|
||||
}
|
||||
|
||||
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
|
||||
|
@ -29,6 +29,7 @@ public:
|
|||
distance = rayPickResult.distance;
|
||||
intersection = rayPickResult.intersection;
|
||||
surfaceNormal = rayPickResult.surfaceNormal;
|
||||
extraInfo = rayPickResult.extraInfo;
|
||||
}
|
||||
|
||||
IntersectionType type { NONE };
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
QVariantMap extraInfo;
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
|
@ -47,6 +49,7 @@ public:
|
|||
toReturn["intersection"] = vec3toVariant(intersection);
|
||||
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
|
||||
toReturn["searchRay"] = PickResult::toVariantMap();
|
||||
toReturn["extraInfo"] = extraInfo;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
#include "JSConsole.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QScrollBar>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QStringListModel>
|
||||
#include <QListView>
|
||||
#include <QToolTip>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <ScriptEngines.h>
|
||||
|
@ -38,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;";
|
|||
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\"><</span>";
|
||||
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
|
||||
|
||||
const QString JSDOC_LINE_SEPARATOR = "\r";
|
||||
|
||||
const QString JSDOC_STYLE =
|
||||
"<style type=\"text/css\"> \
|
||||
.code { \
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace \
|
||||
} \
|
||||
pre, code { \
|
||||
display: inline; \
|
||||
} \
|
||||
.no-wrap { \
|
||||
white-space: nowrap; \
|
||||
} \
|
||||
</style>";
|
||||
|
||||
const QString JSConsole::_consoleFileName { "about:console" };
|
||||
|
||||
const QString JSON_KEY = "entries";
|
||||
|
@ -49,7 +66,7 @@ QList<QString> _readLines(const QString& filename) {
|
|||
// TODO: check root["version"]
|
||||
return root[JSON_KEY].toVariant().toStringList();
|
||||
}
|
||||
|
||||
|
||||
void _writeLines(const QString& filename, const QList<QString>& lines) {
|
||||
QFile file(filename);
|
||||
file.open(QFile::WriteOnly);
|
||||
|
@ -61,12 +78,71 @@ void _writeLines(const QString& filename, const QList<QString>& lines) {
|
|||
QTextStream(&file) << json;
|
||||
}
|
||||
|
||||
QString _jsdocTypeToString(QJsonValue jsdocType) {
|
||||
return jsdocType.toObject().value("names").toVariant().toStringList().join("/");
|
||||
}
|
||||
|
||||
void JSConsole::readAPI() {
|
||||
QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json");
|
||||
file.open(QFile::ReadOnly);
|
||||
auto json = QTextStream(&file).readAll().toUtf8();
|
||||
_apiDocs = QJsonDocument::fromJson(json).array();
|
||||
}
|
||||
|
||||
QStandardItem* getAutoCompleteItem(QJsonValue propertyObject) {
|
||||
auto propertyItem = new QStandardItem(propertyObject.toObject().value("name").toString());
|
||||
propertyItem->setData(propertyObject.toVariant());
|
||||
return propertyItem;
|
||||
}
|
||||
|
||||
QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) {
|
||||
QString memberOfProperty = nullptr;
|
||||
|
||||
auto model = new QStandardItemModel(this);
|
||||
|
||||
if (memberOf != nullptr) {
|
||||
foreach(auto doc, _apiDocs) {
|
||||
auto object = doc.toObject();
|
||||
if (object.value("name").toString() == memberOf && object.value("scope").toString() == "global" &&
|
||||
object.value("kind").toString() == "namespace") {
|
||||
|
||||
memberOfProperty = object.value("longname").toString();
|
||||
|
||||
auto properties = doc.toObject().value("properties").toArray();
|
||||
foreach(auto propertyObject, properties) {
|
||||
model->appendRow(getAutoCompleteItem(propertyObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (memberOfProperty == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
foreach(auto doc, _apiDocs) {
|
||||
auto object = doc.toObject();
|
||||
auto scope = object.value("scope");
|
||||
if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") ||
|
||||
(memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty &&
|
||||
object.value("kind").toString() != "typedef")) {
|
||||
|
||||
model->appendRow(getAutoCompleteItem(doc));
|
||||
}
|
||||
}
|
||||
model->sort(0);
|
||||
return model;
|
||||
}
|
||||
|
||||
JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
|
||||
QWidget(parent),
|
||||
_ui(new Ui::Console),
|
||||
_currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
|
||||
_savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME),
|
||||
_commandHistory(_readLines(_savedHistoryFilename)) {
|
||||
_commandHistory(_readLines(_savedHistoryFilename)),
|
||||
_completer(new QCompleter(this)) {
|
||||
|
||||
readAPI();
|
||||
|
||||
_ui->setupUi(this);
|
||||
_ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
|
||||
_ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap);
|
||||
|
@ -78,38 +154,174 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
|
|||
setStyleSheet(styleSheet.readAll());
|
||||
}
|
||||
|
||||
connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom()));
|
||||
connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput()));
|
||||
connect(_ui->scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &JSConsole::scrollToBottom);
|
||||
connect(_ui->promptTextEdit, &QTextEdit::textChanged, this, &JSConsole::resizeTextInput);
|
||||
|
||||
_completer->setWidget(_ui->promptTextEdit);
|
||||
_completer->setModel(getAutoCompleteModel(nullptr));
|
||||
_completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
|
||||
_completer->setMaxVisibleItems(12);
|
||||
_completer->setFilterMode(Qt::MatchStartsWith);
|
||||
_completer->setWrapAround(false);
|
||||
_completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
_completer->setCaseSensitivity(Qt::CaseSensitive);
|
||||
|
||||
QListView *listView = new QListView();
|
||||
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
listView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
listView->setModelColumn(_completer->completionColumn());
|
||||
|
||||
_completer->setPopup(listView);
|
||||
_completer->popup()->installEventFilter(this);
|
||||
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::activated), this,
|
||||
&JSConsole::insertCompletion);
|
||||
|
||||
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::highlighted), this,
|
||||
&JSConsole::highlightedCompletion);
|
||||
|
||||
setScriptEngine(scriptEngine);
|
||||
|
||||
resizeTextInput();
|
||||
|
||||
connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished()));
|
||||
connect(&_executeWatcher, &QFutureWatcher<QScriptValue>::finished, this, &JSConsole::commandFinished);
|
||||
}
|
||||
|
||||
void JSConsole::insertCompletion(const QModelIndex& completion) {
|
||||
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
|
||||
auto kind = jsdocObject.value("kind").toString();
|
||||
auto completionString = completion.data().toString();
|
||||
if (kind == "function") {
|
||||
auto params = jsdocObject.value("params").toArray();
|
||||
// automatically add the parenthesis/parentheses for the functions
|
||||
completionString += params.isEmpty() ? "()" : "(";
|
||||
}
|
||||
QTextCursor textCursor = _ui->promptTextEdit->textCursor();
|
||||
int extra = completionString.length() - _completer->completionPrefix().length();
|
||||
textCursor.movePosition(QTextCursor::Left);
|
||||
textCursor.movePosition(QTextCursor::EndOfWord);
|
||||
textCursor.insertText(completionString.right(extra));
|
||||
_ui->promptTextEdit->setTextCursor(textCursor);
|
||||
}
|
||||
|
||||
void JSConsole::highlightedCompletion(const QModelIndex& completion) {
|
||||
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
|
||||
QString memberOf = "";
|
||||
if (!_completerModule.isEmpty()) {
|
||||
memberOf = _completerModule + ".";
|
||||
}
|
||||
auto name = memberOf + "<b>" + jsdocObject.value("name").toString() + "</b>";
|
||||
auto description = jsdocObject.value("description").toString();
|
||||
auto examples = jsdocObject.value("examples").toArray();
|
||||
auto kind = jsdocObject.value("kind").toString();
|
||||
QString returnTypeText = "";
|
||||
|
||||
QString paramsTable = "";
|
||||
if (kind == "function") {
|
||||
auto params = jsdocObject.value("params").toArray();
|
||||
auto returns = jsdocObject.value("returns");
|
||||
if (!returns.isUndefined()) {
|
||||
returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " ";
|
||||
}
|
||||
name += "(";
|
||||
if (!params.isEmpty()) {
|
||||
bool hasDefaultParam = false;
|
||||
bool hasOptionalParam = false;
|
||||
bool firstItem = true;
|
||||
foreach(auto param, params) {
|
||||
auto paramObject = param.toObject();
|
||||
if (!hasOptionalParam && paramObject.value("optional").toBool(false)) {
|
||||
hasOptionalParam = true;
|
||||
name += "<i>[";
|
||||
}
|
||||
if (!firstItem) {
|
||||
name += ", ";
|
||||
} else {
|
||||
firstItem = false;
|
||||
}
|
||||
name += paramObject.value("name").toString();
|
||||
if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) {
|
||||
hasDefaultParam = true;
|
||||
}
|
||||
}
|
||||
if (hasOptionalParam) {
|
||||
name += "]</i>";
|
||||
}
|
||||
|
||||
paramsTable += "<table border=\"1\" cellpadding=\"10\"><thead><tr><th>Name</th><th>Type</th>";
|
||||
if (hasDefaultParam) {
|
||||
paramsTable += "<th>Default</th>";
|
||||
}
|
||||
paramsTable += "<th>Description</th></tr></thead><tbody>";
|
||||
foreach(auto param, params) {
|
||||
auto paramObject = param.toObject();
|
||||
paramsTable += "<tr><td>" + paramObject.value("name").toString() + "</td><td>" +
|
||||
_jsdocTypeToString(paramObject.value("type")) + "</td><td>";
|
||||
if (hasDefaultParam) {
|
||||
paramsTable += paramObject.value("defaultvalue").toVariant().toString() + "</td><td>";
|
||||
}
|
||||
paramsTable += paramObject.value("description").toString() + "</td></tr>";
|
||||
}
|
||||
|
||||
paramsTable += "</tbody></table>";
|
||||
}
|
||||
name += ")";
|
||||
} else if (!jsdocObject.value("type").isUndefined()){
|
||||
returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " ";
|
||||
}
|
||||
auto popupText = JSDOC_STYLE + "<span class=\"no-wrap\">" + returnTypeText + name + "</span>";
|
||||
auto descriptionText = "<p>" + description.replace(JSDOC_LINE_SEPARATOR, "<br>") + "</p>";
|
||||
|
||||
popupText += descriptionText;
|
||||
popupText += paramsTable;
|
||||
auto returns = jsdocObject.value("returns");
|
||||
if (!returns.isUndefined()) {
|
||||
foreach(auto returnEntry, returns.toArray()) {
|
||||
auto returnsObject = returnEntry.toObject();
|
||||
auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "<br>");
|
||||
popupText += "<h4>Returns</h4><p>" + returnsDescription + "</p><h5>Type</h5><pre><code>" +
|
||||
_jsdocTypeToString(returnsObject.value("type")) + "</code></pre>";
|
||||
}
|
||||
}
|
||||
|
||||
if (!examples.isEmpty()) {
|
||||
popupText += "<h4>Examples</h4>";
|
||||
foreach(auto example, examples) {
|
||||
auto exampleText = example.toString();
|
||||
auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR);
|
||||
foreach(auto exampleLine, exampleLines) {
|
||||
if (exampleLine.contains("<caption>")) {
|
||||
popupText += exampleLine.replace("caption>", "h5>");
|
||||
} else {
|
||||
popupText += "<pre><code>" + exampleLine + "\n</code></pre>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()),
|
||||
popupText, _completer->popup());
|
||||
}
|
||||
|
||||
JSConsole::~JSConsole() {
|
||||
if (_scriptEngine) {
|
||||
disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
|
||||
disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
|
||||
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
|
||||
_scriptEngine.reset();
|
||||
}
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) {
|
||||
if (_scriptEngine == scriptEngine && scriptEngine != NULL) {
|
||||
if (_scriptEngine == scriptEngine && scriptEngine != nullptr) {
|
||||
return;
|
||||
}
|
||||
if (_scriptEngine != NULL) {
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError);
|
||||
if (_scriptEngine != nullptr) {
|
||||
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
|
||||
_scriptEngine.reset();
|
||||
}
|
||||
|
||||
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
|
||||
// if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine
|
||||
if (scriptEngine.isNull()) {
|
||||
_scriptEngine = DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false);
|
||||
} else {
|
||||
|
@ -199,45 +411,121 @@ void JSConsole::showEvent(QShowEvent* event) {
|
|||
}
|
||||
|
||||
bool JSConsole::eventFilter(QObject* sender, QEvent* event) {
|
||||
if (sender == _ui->promptTextEdit) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
|
||||
if ((key == Qt::Key_Return || key == Qt::Key_Enter)) {
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
|
||||
// a new QTextBlock isn't created.
|
||||
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
|
||||
} else {
|
||||
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed();
|
||||
|
||||
if (!command.isEmpty()) {
|
||||
QTextCursor cursor = _ui->promptTextEdit->textCursor();
|
||||
_ui->promptTextEdit->clear();
|
||||
|
||||
executeCommand(command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Down) {
|
||||
// Go to the next command in history if the cursor is at the last line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
int blockCount = _ui->promptTextEdit->document()->blockCount();
|
||||
if (blockNumber == blockCount - 1) {
|
||||
setToNextCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Up) {
|
||||
// Go to the previous command in history if the cursor is at the first line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
if (blockNumber == 0) {
|
||||
setToPreviousCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
if (_completer->popup()->isVisible()) {
|
||||
// The following keys are forwarded by the completer to the widget
|
||||
switch (key) {
|
||||
case Qt::Key_Space:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
insertCompletion(_completer->popup()->currentIndex());
|
||||
_completer->popup()->hide();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == Qt::Key_Return || key == Qt::Key_Enter) {
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
|
||||
// a new QTextBlock isn't created.
|
||||
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
|
||||
} else {
|
||||
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n", "\n").trimmed();
|
||||
|
||||
if (!command.isEmpty()) {
|
||||
QTextCursor cursor = _ui->promptTextEdit->textCursor();
|
||||
_ui->promptTextEdit->clear();
|
||||
|
||||
executeCommand(command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Down) {
|
||||
// Go to the next command in history if the cursor is at the last line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
int blockCount = _ui->promptTextEdit->document()->blockCount();
|
||||
if (blockNumber == blockCount - 1) {
|
||||
setToNextCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Up) {
|
||||
// Go to the previous command in history if the cursor is at the first line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
if (blockNumber == 0) {
|
||||
setToPreviousCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
|
||||
// completer shortcut (CTRL + SPACE)
|
||||
bool isCompleterShortcut = ((keyEvent->modifiers() & Qt::ControlModifier) && key == Qt::Key_Space) ||
|
||||
key == Qt::Key_Period;
|
||||
if (_completer->popup()->isVisible() || isCompleterShortcut) {
|
||||
|
||||
const bool ctrlOrShift = keyEvent->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
|
||||
if (ctrlOrShift && keyEvent->text().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word
|
||||
|
||||
if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) {
|
||||
_completer->popup()->hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto textCursor = _ui->promptTextEdit->textCursor();
|
||||
|
||||
textCursor.select(QTextCursor::WordUnderCursor);
|
||||
|
||||
QString completionPrefix = textCursor.selectedText();
|
||||
|
||||
auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position());
|
||||
|
||||
// RegEx [3] [4]
|
||||
// (Module.subModule).(property/subModule)
|
||||
|
||||
const int MODULE_INDEX = 3;
|
||||
const int PROPERTY_INDEX = 4;
|
||||
// TODO: disallow invalid characters on left of property
|
||||
QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$");
|
||||
regExp.indexIn(leftOfCursor);
|
||||
auto rexExpCapturedTexts = regExp.capturedTexts();
|
||||
auto memberOf = rexExpCapturedTexts[MODULE_INDEX];
|
||||
completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX];
|
||||
bool switchedModule = false;
|
||||
if (memberOf != _completerModule) {
|
||||
_completerModule = memberOf;
|
||||
auto autoCompleteModel = getAutoCompleteModel(memberOf);
|
||||
if (autoCompleteModel == nullptr) {
|
||||
_completer->popup()->hide();
|
||||
return false;
|
||||
}
|
||||
_completer->setModel(autoCompleteModel);
|
||||
_completer->popup()->installEventFilter(this);
|
||||
switchedModule = true;
|
||||
}
|
||||
|
||||
if (switchedModule || completionPrefix != _completer->completionPrefix()) {
|
||||
_completer->setCompletionPrefix(completionPrefix);
|
||||
_completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0));
|
||||
}
|
||||
auto cursorRect = _ui->promptTextEdit->cursorRect();
|
||||
cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) +
|
||||
_completer->popup()->verticalScrollBar()->sizeHint().width());
|
||||
_completer->complete(cursorRect);
|
||||
highlightedCompletion(_completer->popup()->currentIndex());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -321,7 +609,7 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) {
|
|||
|
||||
void JSConsole::clear() {
|
||||
QLayoutItem* item;
|
||||
while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) {
|
||||
while ((item = _ui->logArea->layout()->takeAt(0)) != nullptr) {
|
||||
delete item->widget();
|
||||
delete item;
|
||||
}
|
||||
|
|
|
@ -12,12 +12,11 @@
|
|||
#ifndef hifi_JSConsole_h
|
||||
#define hifi_JSConsole_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QEvent>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QSharedPointer>
|
||||
#include <QCompleter>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
#include "ui_console.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
@ -54,12 +53,20 @@ protected slots:
|
|||
void handleError(const QString& message, const QString& scriptName);
|
||||
void commandFinished();
|
||||
|
||||
private slots:
|
||||
void insertCompletion(const QModelIndex& completion);
|
||||
void highlightedCompletion(const QModelIndex& completion);
|
||||
|
||||
private:
|
||||
void appendMessage(const QString& gutter, const QString& message);
|
||||
void setToNextCommandInHistory();
|
||||
void setToPreviousCommandInHistory();
|
||||
void resetCurrentCommandHistory();
|
||||
|
||||
void readAPI();
|
||||
|
||||
QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr);
|
||||
|
||||
QFutureWatcher<QScriptValue> _executeWatcher;
|
||||
Ui::Console* _ui;
|
||||
int _currentCommandInHistory;
|
||||
|
@ -68,6 +75,9 @@ private:
|
|||
QString _rootCommand;
|
||||
ScriptEnginePointer _scriptEngine;
|
||||
static const QString _consoleFileName;
|
||||
QJsonArray _apiDocs;
|
||||
QCompleter* _completer;
|
||||
QString _completerModule {""};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
BoxFace& face, glm::vec3& surfaceNormal);
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
|
||||
}
|
||||
|
||||
|
|
|
@ -449,12 +449,12 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
|
||||
QString subMeshNameTemp;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
||||
QVariantMap extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) override;
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override;
|
||||
|
||||
virtual ModelOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -520,7 +520,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
float thisDistance;
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
QString thisExtraInfo;
|
||||
QVariantMap thisExtraInfo;
|
||||
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo)) {
|
||||
bool isDrawInFront = thisOverlay->getDrawInFront();
|
||||
|
@ -578,7 +578,7 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine,
|
|||
obj.setProperty("face", faceName);
|
||||
auto intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("extraInfo", value.extraInfo);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar
|
|||
value.intersection = newIntersection;
|
||||
}
|
||||
}
|
||||
value.extraInfo = object["extraInfo"].toString();
|
||||
value.extraInfo = object["extraInfo"].toMap();
|
||||
}
|
||||
|
||||
bool Overlays::isLoaded(OverlayID id) {
|
||||
|
|
|
@ -51,6 +51,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
|
|||
* @property {number} distance - The distance from the {@link PickRay} origin to the intersection point.
|
||||
* @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point.
|
||||
* @property {Vec3} intersection - The position of the intersection point.
|
||||
* @property {Object} extraInfo Additional intersection details, if available.
|
||||
*/
|
||||
class RayToOverlayIntersectionResult {
|
||||
public:
|
||||
|
@ -60,7 +61,7 @@ public:
|
|||
BoxFace face;
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -2504,6 +2504,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
obj.setProperty("distance", value.distance);
|
||||
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -2516,6 +2517,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
if (intersection.isValid()) {
|
||||
vec3FromScriptValue(intersection, value.intersection);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
|
|
|
@ -982,6 +982,7 @@ RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
|
|||
QUuid avatarID;
|
||||
float distance;
|
||||
glm::vec3 intersection;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)
|
||||
|
|
|
@ -669,15 +669,16 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
auto properties = rayPickResult.entity->getProperties();
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
auto properties = entity->getProperties();
|
||||
QString urlString = properties.getHref();
|
||||
QUrl url = QUrl(urlString, QUrl::StrictMode);
|
||||
if (url.isValid() && !url.isEmpty()){
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
|
||||
}
|
||||
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -708,8 +709,9 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -738,10 +740,11 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
// qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -757,7 +760,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
|||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
||||
// we're releasing the button, then this is considered a clickReleaseOn event
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -782,8 +785,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -797,7 +801,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
|
||||
// then we need to send the hover leave.
|
||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -828,7 +832,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
|
||||
// send the hover leave for our previous entity
|
||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
|
|
@ -282,7 +282,7 @@ bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
|
|||
|
||||
bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
|
||||
glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const {
|
||||
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
auto model = getModel();
|
||||
if (!model) {
|
||||
return true;
|
||||
|
@ -290,9 +290,8 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
// qCDebug(entitiesrenderer) << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:"
|
||||
// << precisionPicking;
|
||||
|
||||
QString extraInfo;
|
||||
return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
|
||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::getCollisionGeometryResource() {
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setShapeType(ShapeType type) override;
|
||||
virtual void setCompoundShapeURL(const QString& url) override;
|
||||
|
|
|
@ -565,7 +565,7 @@ public:
|
|||
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const
|
||||
QVariantMap& extraInfo, bool precisionPicking) const
|
||||
{
|
||||
// TODO -- correctly pick against marching-cube generated meshes
|
||||
if (!precisionPicking) {
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setVoxelData(const QByteArray& voxelData) override;
|
||||
virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override;
|
||||
|
|
|
@ -160,7 +160,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const { return true; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
|
||||
|
||||
// attributes applicable to all entity types
|
||||
EntityTypes::EntityType getType() const { return _type; }
|
||||
|
|
|
@ -816,13 +816,12 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
|
|||
RayToEntityIntersectionResult result;
|
||||
if (_entityTree) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction,
|
||||
result.entityID = _entityTree->findRayIntersection(ray.origin, ray.direction,
|
||||
entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, result.distance, result.face, result.surfaceNormal,
|
||||
(void**)&intersectedEntity, lockType, &result.accurate);
|
||||
if (result.intersects && intersectedEntity) {
|
||||
result.entityID = intersectedEntity->getEntityItemID();
|
||||
result.extraInfo, lockType, &result.accurate);
|
||||
result.intersects = !result.entityID.isNull();
|
||||
if (result.intersects) {
|
||||
result.intersection = ray.origin + (ray.direction * result.distance);
|
||||
}
|
||||
}
|
||||
|
@ -988,8 +987,7 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
|||
accurate(true), // assume it's accurate
|
||||
entityID(),
|
||||
distance(0),
|
||||
face(),
|
||||
entity(NULL)
|
||||
face()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1036,6 +1034,7 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -1071,6 +1070,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
if (surfaceNormal.isValid()) {
|
||||
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) {
|
||||
|
|
|
@ -62,7 +62,7 @@ public:
|
|||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
EntityItemPointer entity;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
|
||||
|
@ -139,7 +139,7 @@ public slots:
|
|||
Q_INVOKABLE bool canRezTmpCertified();
|
||||
|
||||
/**jsdoc
|
||||
* @function Entities.canWriteAsseets
|
||||
* @function Entities.canWriteAssets
|
||||
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server
|
||||
*/
|
||||
Q_INVOKABLE bool canWriteAssets();
|
||||
|
|
|
@ -59,8 +59,8 @@ public:
|
|||
float& distance;
|
||||
BoxFace& face;
|
||||
glm::vec3& surfaceNormal;
|
||||
void** intersectedObject;
|
||||
bool found;
|
||||
QVariantMap& extraInfo;
|
||||
EntityItemID entityID;
|
||||
};
|
||||
|
||||
|
||||
|
@ -748,23 +748,24 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData)
|
|||
RayArgs* args = static_cast<RayArgs*>(extraData);
|
||||
bool keepSearching = true;
|
||||
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
if (entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching,
|
||||
EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching,
|
||||
args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->intersectedObject, args->precisionPicking)) {
|
||||
args->found = true;
|
||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
||||
if (!entityID.isNull()) {
|
||||
args->entityID = entityID;
|
||||
}
|
||||
return keepSearching;
|
||||
}
|
||||
|
||||
bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType, bool* accurateResult) {
|
||||
RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard,
|
||||
visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, surfaceNormal, intersectedObject, false };
|
||||
element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||
distance = FLT_MAX;
|
||||
|
||||
bool requireLock = lockType == Octree::Lock;
|
||||
|
@ -776,7 +777,7 @@ bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& d
|
|||
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
||||
}
|
||||
|
||||
return args.found;
|
||||
return args.entityID;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1678,14 +1679,17 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void EntityTree::fixupNeedsParentFixups() {
|
||||
PROFILE_RANGE(simulation_physics, "FixupParents");
|
||||
MovingEntitiesOperator moveOperator;
|
||||
QVector<EntityItemWeakPointer> entitiesToFixup;
|
||||
{
|
||||
QWriteLocker locker(&_needsParentFixupLock);
|
||||
entitiesToFixup = _needsParentFixup;
|
||||
_needsParentFixup.clear();
|
||||
}
|
||||
|
||||
QWriteLocker locker(&_needsParentFixupLock);
|
||||
|
||||
QMutableVectorIterator<EntityItemWeakPointer> iter(_needsParentFixup);
|
||||
QMutableVectorIterator<EntityItemWeakPointer> iter(entitiesToFixup);
|
||||
while (iter.hasNext()) {
|
||||
EntityItemWeakPointer entityWP = iter.next();
|
||||
EntityItemPointer entity = entityWP.lock();
|
||||
|
@ -1748,6 +1752,12 @@ void EntityTree::fixupNeedsParentFixups() {
|
|||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||
recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker locker(&_needsParentFixupLock);
|
||||
// add back the entities that did not get fixup
|
||||
_needsParentFixup.append(entitiesToFixup);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {
|
||||
|
|
|
@ -97,11 +97,11 @@ public:
|
|||
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& node, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject = NULL,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
virtual bool rootElementHasData() const override { return true; }
|
||||
|
|
|
@ -588,57 +588,60 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
return false;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
void** intersectedObject, bool precisionPicking) {
|
||||
QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
keepSearching = true; // assume that we will continue searching after this.
|
||||
|
||||
EntityItemID result;
|
||||
float distanceToElementCube = std::numeric_limits<float>::max();
|
||||
float distanceToElementDetails = distance;
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
QVariantMap localExtraInfo;
|
||||
|
||||
// if the ray doesn't intersect with our cube, we can stop searching!
|
||||
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) {
|
||||
keepSearching = false; // no point in continuing to search
|
||||
return false; // we did not intersect
|
||||
return result; // we did not intersect
|
||||
}
|
||||
|
||||
// by default, we only allow intersections with leaves with content
|
||||
if (!canRayIntersect()) {
|
||||
return false; // we don't intersect with non-leaves, and we keep searching
|
||||
return result; // we don't intersect with non-leaves, and we keep searching
|
||||
}
|
||||
|
||||
// if the distance to the element cube is not less than the current best distance, then it's not possible
|
||||
// for any details inside the cube to be closer so we don't need to consider them.
|
||||
if (_cube.contains(origin) || distanceToElementCube < distance) {
|
||||
|
||||
if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
|
||||
EntityItemID entityID = findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
|
||||
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
|
||||
intersectedObject, precisionPicking, distanceToElementCube)) {
|
||||
|
||||
localExtraInfo, precisionPicking, distanceToElementCube);
|
||||
if (!entityID.isNull()) {
|
||||
if (distanceToElementDetails < distance) {
|
||||
distance = distanceToElementDetails;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
return true;
|
||||
extraInfo = localExtraInfo;
|
||||
result = entityID;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
|
||||
EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
|
||||
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
|
||||
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube) {
|
||||
|
||||
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
|
||||
int entityNumber = 0;
|
||||
bool somethingIntersected = false;
|
||||
EntityItemID entityID;
|
||||
forEachEntity([&](EntityItemPointer entity) {
|
||||
if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|
||||
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|
||||
|
@ -655,6 +658,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
float localDistance;
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
QVariantMap localExtraInfo;
|
||||
|
||||
// if the ray doesn't intersect with our cube, we can stop searching!
|
||||
if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) {
|
||||
|
@ -684,14 +688,14 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
// now ask the entity if we actually intersect
|
||||
if (entity->supportsDetailedRayIntersection()) {
|
||||
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
|
||||
localFace, localSurfaceNormal, intersectedObject, precisionPicking)) {
|
||||
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
|
||||
|
||||
if (localDistance < distance) {
|
||||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
*intersectedObject = (void*)entity.get();
|
||||
somethingIntersected = true;
|
||||
extraInfo = localExtraInfo;
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -701,15 +705,14 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f));
|
||||
*intersectedObject = (void*)entity.get();
|
||||
somethingIntersected = true;
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entityNumber++;
|
||||
});
|
||||
return somethingIntersected;
|
||||
return entityID;
|
||||
}
|
||||
|
||||
// TODO: change this to use better bounding shape for entity than sphere
|
||||
|
|
|
@ -146,16 +146,16 @@ public:
|
|||
virtual bool deleteApproved() const override { return !hasEntities(); }
|
||||
|
||||
virtual bool canRayIntersect() const override { return hasEntities(); }
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& node, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false,
|
||||
void** intersectedObject = NULL, bool precisionPicking = false);
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
||||
virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
|
||||
QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube);
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const override;
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ void LightEntityItem::resetLightPropertiesChanged() {
|
|||
bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
|
||||
// TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state
|
||||
// this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could
|
||||
|
|
|
@ -88,7 +88,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
private:
|
||||
// properties of a light
|
||||
|
|
|
@ -63,7 +63,7 @@ class LineEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject,
|
||||
QVariantMap& extraInfo,
|
||||
bool precisionPicking) const override { return false; }
|
||||
bool pointsChanged() const { return _pointsChanged; }
|
||||
void resetPointsChanged();
|
||||
|
|
|
@ -96,7 +96,7 @@ class PolyLineEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override { return false; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
// disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain
|
||||
virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious!
|
||||
|
|
|
@ -47,7 +47,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override { return false; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
|
|
@ -223,7 +223,7 @@ bool ShapeEntityItem::supportsDetailedRayIntersection() const {
|
|||
bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// determine the ray in the frame of the entity transformed from a unit sphere
|
||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
|
|
|
@ -94,7 +94,7 @@ public:
|
|||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
void debugDump() const override;
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
|||
bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
static const QString DEFAULT_TEXT;
|
||||
void setText(const QString& value);
|
||||
|
|
|
@ -108,7 +108,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
|
|||
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setSourceUrl(const QString& value);
|
||||
QString getSourceUrl() const;
|
||||
|
|
|
@ -298,7 +298,7 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
|
|||
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
|
||||
return _zonesArePickable;
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
|
|
@ -272,6 +272,11 @@ glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParen
|
|||
const FBXModel& model = models.value(nodeID);
|
||||
globalTransform = glm::translate(model.translation) * model.preTransform * glm::mat4_cast(model.preRotation *
|
||||
model.rotation * model.postRotation) * model.postTransform * globalTransform;
|
||||
if (model.hasGeometricOffset) {
|
||||
glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(model.geometricScaling, model.geometricRotation, model.geometricTranslation);
|
||||
globalTransform = globalTransform * geometricOffset;
|
||||
}
|
||||
|
||||
if (mixamoHack) {
|
||||
// there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform
|
||||
return globalTransform;
|
||||
|
@ -323,12 +328,12 @@ public:
|
|||
};
|
||||
|
||||
void appendModelIDs(const QString& parentID, const QMultiMap<QString, QString>& connectionChildMap,
|
||||
QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs) {
|
||||
QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs, bool isRootNode = false) {
|
||||
if (remainingModels.contains(parentID)) {
|
||||
modelIDs.append(parentID);
|
||||
remainingModels.remove(parentID);
|
||||
}
|
||||
int parentIndex = modelIDs.size() - 1;
|
||||
int parentIndex = isRootNode ? -1 : modelIDs.size() - 1;
|
||||
foreach (const QString& childID, connectionChildMap.values(parentID)) {
|
||||
if (remainingModels.contains(childID)) {
|
||||
FBXModel& model = models[childID];
|
||||
|
@ -1478,7 +1483,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
}
|
||||
QString topID = getTopModelID(_connectionParentMap, models, first, url);
|
||||
appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs);
|
||||
appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true);
|
||||
}
|
||||
|
||||
// figure the number of animation frames from the curves
|
||||
|
@ -1533,7 +1538,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
joint.transform = geometry.offset * glm::translate(joint.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
joint.inverseDefaultRotation = glm::inverse(combinedRotation);
|
||||
joint.distanceToParent = 0.0f;
|
||||
joint.distanceToParent = 0.0f;
|
||||
|
||||
} else {
|
||||
const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex);
|
||||
|
@ -1891,6 +1896,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
|
||||
geometry.meshes.append(extracted.mesh);
|
||||
int meshIndex = geometry.meshes.size() - 1;
|
||||
if (extracted.mesh._mesh) {
|
||||
extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex);
|
||||
}
|
||||
meshIDsToMeshIndices.insert(it.key(), meshIndex);
|
||||
}
|
||||
|
||||
|
@ -1959,7 +1967,19 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
for (const auto& mesh : geometry.meshes) {
|
||||
auto name = geometry.getModelNameOfMesh(i++);
|
||||
if (!name.isEmpty()) {
|
||||
if (mesh._mesh) {
|
||||
mesh._mesh->displayName += "#" + name;
|
||||
} else {
|
||||
qDebug() << "modelName but no mesh._mesh" << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return geometryPtr;
|
||||
}
|
||||
|
||||
|
@ -1975,7 +1995,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri
|
|||
reader._loadLightmaps = loadLightmaps;
|
||||
reader._lightmapLevel = lightmapLevel;
|
||||
|
||||
qDebug() << "Reading FBX: " << url;
|
||||
qCDebug(modelformat) << "Reading FBX: " << url;
|
||||
|
||||
return reader.extractFBXGeometry(mapping, url);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,8 @@ public:
|
|||
|
||||
static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr);
|
||||
|
||||
QString displayName;
|
||||
|
||||
protected:
|
||||
|
||||
gpu::Stream::FormatPointer _vertexFormat;
|
||||
|
|
|
@ -340,14 +340,6 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
|
|||
return resource;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qCDebug(networking) << "Fetching asynchronously:" << url;
|
||||
QMetaObject::invokeMethod(this, "getResource",
|
||||
Q_ARG(QUrl, url), Q_ARG(QUrl, fallback));
|
||||
// Cannot use extra parameter as it might be freed before the invocation
|
||||
return QSharedPointer<Resource>();
|
||||
}
|
||||
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
return getResource(fallback, QUrl());
|
||||
}
|
||||
|
@ -358,6 +350,7 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
|
|||
extra);
|
||||
resource->setSelf(resource);
|
||||
resource->setCache(this);
|
||||
resource->moveToThread(qApp->thread());
|
||||
connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);
|
||||
{
|
||||
QWriteLocker locker(&_resourcesLock);
|
||||
|
|
|
@ -115,6 +115,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans());
|
||||
#else
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
|
@ -151,6 +152,7 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||
#else
|
||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
|
|
|
@ -99,7 +99,6 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie
|
|||
|
||||
// Diffuse from ambient
|
||||
diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), lowNormalCurvature.xyz).xyz;
|
||||
diffuse /= 3.1415926;
|
||||
specular = vec3(0.0);
|
||||
}
|
||||
<@endif@>
|
||||
|
|
|
@ -425,8 +425,8 @@ void Model::initJointStates() {
|
|||
}
|
||||
|
||||
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) {
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
bool pickAgainstTriangles, bool allowBackface) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
|
||||
|
@ -457,6 +457,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
QMutexLocker locker(&_mutex);
|
||||
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
Triangle bestModelTriangle;
|
||||
Triangle bestWorldTriangle;
|
||||
int bestSubMeshIndex = 0;
|
||||
|
||||
int subMeshIndex = 0;
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
|
@ -474,8 +478,8 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
for (auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
||||
float triangleSetDistance = 0.0f;
|
||||
BoxFace triangleSetFace;
|
||||
glm::vec3 triangleSetNormal;
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) {
|
||||
Triangle triangleSetTriangle;
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
|
||||
|
||||
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
|
||||
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
|
||||
|
@ -485,8 +489,11 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
bestDistance = worldDistance;
|
||||
intersectedSomething = true;
|
||||
face = triangleSetFace;
|
||||
surfaceNormal = glm::vec3(meshToWorldMatrix * glm::vec4(triangleSetNormal, 0.0f));
|
||||
extraInfo = geometry.getModelNameOfMesh(subMeshIndex);
|
||||
bestModelTriangle = triangleSetTriangle;
|
||||
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
||||
extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint);
|
||||
extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint);
|
||||
bestSubMeshIndex = subMeshIndex;
|
||||
}
|
||||
}
|
||||
subMeshIndex++;
|
||||
|
@ -494,9 +501,24 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
|
||||
if (intersectedSomething) {
|
||||
distance = bestDistance;
|
||||
surfaceNormal = bestWorldTriangle.getNormal();
|
||||
if (pickAgainstTriangles) {
|
||||
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
||||
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
||||
extraInfo["subMeshTriangleWorld"] = QVariantMap{
|
||||
{ "v0", vec3toVariant(bestWorldTriangle.v0) },
|
||||
{ "v1", vec3toVariant(bestWorldTriangle.v1) },
|
||||
{ "v2", vec3toVariant(bestWorldTriangle.v2) },
|
||||
};
|
||||
extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal());
|
||||
extraInfo["subMeshTriangle"] = QVariantMap{
|
||||
{ "v0", vec3toVariant(bestModelTriangle.v0) },
|
||||
{ "v1", vec3toVariant(bestModelTriangle.v1) },
|
||||
{ "v2", vec3toVariant(bestModelTriangle.v2) },
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
|
|
|
@ -162,8 +162,8 @@ public:
|
|||
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
|
||||
|
||||
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
|
||||
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
@ -264,26 +264,34 @@ public:
|
|||
_scale.x = p.scale().x;
|
||||
_scale.y = p.scale().y;
|
||||
_scale.z = p.scale().z;
|
||||
_scale.w = 0.0f;
|
||||
_dq = DualQuaternion(p.rot(), p.trans());
|
||||
}
|
||||
TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) {
|
||||
_scale.x = scale.x;
|
||||
_scale.y = scale.y;
|
||||
_scale.z = scale.z;
|
||||
_scale.w = 0.0f;
|
||||
_dq = DualQuaternion(rot, trans);
|
||||
}
|
||||
TransformDualQuaternion(const Transform& transform) {
|
||||
_scale = glm::vec4(transform.getScale(), 0.0f);
|
||||
_scale.w = 0.0f;
|
||||
_dq = DualQuaternion(transform.getRotation(), transform.getTranslation());
|
||||
}
|
||||
glm::vec3 getScale() const { return glm::vec3(_scale); }
|
||||
glm::quat getRotation() const { return _dq.getRotation(); }
|
||||
glm::vec3 getTranslation() const { return _dq.getTranslation(); }
|
||||
glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); };
|
||||
|
||||
void setCauterizationParameters(float cauterizationAmount, const glm::vec3& cauterizedPosition) {
|
||||
_scale.w = cauterizationAmount;
|
||||
_cauterizedPosition = glm::vec4(cauterizedPosition, 1.0f);
|
||||
}
|
||||
protected:
|
||||
glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f };
|
||||
DualQuaternion _dq;
|
||||
glm::vec4 _padding;
|
||||
glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -58,17 +58,19 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) {
|
|||
void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 scale = clusterMatrix[0];
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
vec4 cauterizedPos = clusterMatrix[3];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
|
@ -79,6 +81,7 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio
|
|||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
cAccum += cauterizedPos * clusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
|
@ -88,25 +91,37 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio
|
|||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
|
||||
// sAccum.w indicates the amount of cauterization for this vertex.
|
||||
// 0 indicates no cauterization and 1 indicates full cauterization.
|
||||
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
|
||||
const float CAUTERIZATION_THRESHOLD = 0.1;
|
||||
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
|
||||
skinnedPosition = cAccum;
|
||||
} else {
|
||||
sAccum.w = 1.0;
|
||||
skinnedPosition = m * (sAccum * inPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal,
|
||||
out vec4 skinnedPosition, out vec3 skinnedNormal) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 scale = clusterMatrix[0];
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
vec4 cauterizedPos = clusterMatrix[3];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
|
@ -117,6 +132,7 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP
|
|||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
cAccum += cauterizedPos * clusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
|
@ -126,7 +142,18 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP
|
|||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
|
||||
// sAccum.w indicates the amount of cauterization for this vertex.
|
||||
// 0 indicates no cauterization and 1 indicates full cauterization.
|
||||
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
|
||||
const float CAUTERIZATION_THRESHOLD = 0.1;
|
||||
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
|
||||
skinnedPosition = cAccum;
|
||||
} else {
|
||||
sAccum.w = 1.0;
|
||||
skinnedPosition = m * (sAccum * inPosition);
|
||||
}
|
||||
|
||||
skinnedNormal = vec3(m * vec4(inNormal, 0));
|
||||
}
|
||||
|
||||
|
@ -134,18 +161,20 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 sAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 cAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 scale = clusterMatrix[0];
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
vec4 cauterizedPos = clusterMatrix[3];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
|
@ -156,6 +185,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
cAccum += cauterizedPos * clusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
|
@ -165,7 +195,18 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
|
||||
// sAccum.w indicates the amount of cauterization for this vertex.
|
||||
// 0 indicates no cauterization and 1 indicates full cauterization.
|
||||
// TODO: make this cauterization smoother or implement full dual-quaternion scale support.
|
||||
const float CAUTERIZATION_THRESHOLD = 0.1;
|
||||
if (sAccum.w > CAUTERIZATION_THRESHOLD) {
|
||||
skinnedPosition = cAccum;
|
||||
} else {
|
||||
sAccum.w = 1.0;
|
||||
skinnedPosition = m * (sAccum * inPosition);
|
||||
}
|
||||
|
||||
skinnedNormal = vec3(m * vec4(inNormal, 0));
|
||||
skinnedTangent = vec3(m * vec4(inTangent, 0));
|
||||
}
|
||||
|
|
|
@ -70,36 +70,40 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program
|
|||
|
||||
void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state,
|
||||
BatchSetter batchSetter, ItemSetter itemSetter) {
|
||||
|
||||
ShapeKey key{ filter._flags };
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
|
||||
// don't call makeProgram on shaders that are already made.
|
||||
if (program->getUniformBuffers().empty()) {
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), Slot::BUFFER::LIGHTING_MODEL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringMap"), Slot::MAP::SCATTERING));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), Slot::BUFFER::KEY_LIGHT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
|
||||
|
||||
if (key.isTranslucent()) {
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
|
||||
if (key.isTranslucent()) {
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("clusterGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("clusterContentBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("frustumGridBuffer"), Slot::BUFFER::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT));
|
||||
}
|
||||
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
}
|
||||
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
auto locations = std::make_shared<Locations>();
|
||||
|
||||
locations->albedoTextureUnit = program->getTextures().findLocation("albedoMap");
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
#include <QDebug>
|
||||
#include <GLMHelpers.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "Mat4.h"
|
||||
|
@ -32,6 +34,14 @@ glm::mat4 Mat4::createFromColumns(const glm::vec4& col0, const glm::vec4& col1,
|
|||
return glm::mat4(col0, col1, col2, col3);
|
||||
}
|
||||
|
||||
glm::mat4 Mat4::createFromArray(const QVector<float>& floats) const {
|
||||
if (floats.size() != 16 && floats.size() != 9) {
|
||||
context()->throwError("createFromVector requires 16 floats for mat4 (or 9 if providing a mat3)");
|
||||
return glm::mat4();
|
||||
}
|
||||
return floats.size() == 9 ? glm::mat4(glm::make_mat3(floats.constData())) : glm::make_mat4(floats.constData());
|
||||
}
|
||||
|
||||
glm::vec3 Mat4::extractTranslation(const glm::mat4& m) const {
|
||||
return ::extractTranslation(m);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtScript/QScriptable>
|
||||
#include <QVector>
|
||||
#include <glm/glm.hpp>
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
/// Scriptable Mat4 object. Used exclusively in the JavaScript API
|
||||
class Mat4 : public QObject, protected QScriptable {
|
||||
|
@ -28,6 +31,7 @@ public slots:
|
|||
glm::mat4 createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const;
|
||||
glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const;
|
||||
glm::mat4 createFromColumns(const glm::vec4& col0, const glm::vec4& col1, const glm::vec4& col2, const glm::vec4& col3) const;
|
||||
glm::mat4 createFromArray(const QVector<float>& floats) const;
|
||||
|
||||
glm::vec3 extractTranslation(const glm::mat4& m) const;
|
||||
glm::quat extractRotation(const glm::mat4& m) const;
|
||||
|
|
|
@ -150,7 +150,7 @@ signals:
|
|||
|
||||
/**jsdoc
|
||||
* Notifies scripts that a user has disconnected from the domain
|
||||
* @function Users.avatar.avatarDisconnected
|
||||
* @function Users.avatarDisconnected
|
||||
* @param {nodeID} NodeID The session ID of the avatar that has disconnected
|
||||
*/
|
||||
void avatarDisconnected(const QUuid& nodeID);
|
||||
|
|
|
@ -12,4 +12,4 @@ if (ANDROID)
|
|||
endif()
|
||||
|
||||
target_zlib()
|
||||
target_nsight()
|
||||
target_nsight()
|
||||
|
|
|
@ -292,6 +292,14 @@ glm::vec3 Triangle::getNormal() const {
|
|||
return glm::normalize(glm::cross(edge1, edge2));
|
||||
}
|
||||
|
||||
Triangle Triangle::operator*(const glm::mat4& transform) const {
|
||||
return {
|
||||
glm::vec3(transform * glm::vec4(v0, 1.0f)),
|
||||
glm::vec3(transform * glm::vec4(v1, 1.0f)),
|
||||
glm::vec3(transform * glm::vec4(v2, 1.0f))
|
||||
};
|
||||
}
|
||||
|
||||
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) {
|
||||
glm::vec3 firstSide = v0 - v1;
|
||||
|
|
|
@ -104,6 +104,7 @@ public:
|
|||
glm::vec3 v1;
|
||||
glm::vec3 v2;
|
||||
glm::vec3 getNormal() const;
|
||||
Triangle operator*(const glm::mat4& transform) const;
|
||||
};
|
||||
|
||||
inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
|
|
|
@ -37,10 +37,10 @@
|
|||
QString TEMP_DIR_FORMAT { "%1-%2-%3" };
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
|
||||
#if defined(Q_OS_OSX)
|
||||
static bool USE_SOURCE_TREE_RESOURCES = true;
|
||||
#else
|
||||
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,8 +48,8 @@ static bool USE_SOURCE_TREE_RESOURCES() {
|
|||
result = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DEV_BUILD
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "SettingHelpers.h"
|
||||
#include "SettingManager.h"
|
||||
#include "SharedLogging.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
namespace Setting {
|
||||
static QSharedPointer<Manager> globalManager;
|
||||
|
@ -32,14 +33,34 @@ namespace Setting {
|
|||
// tell the private instance to clean itself up on its thread
|
||||
DependencyManager::destroy<Manager>();
|
||||
|
||||
//
|
||||
globalManager.reset();
|
||||
|
||||
|
||||
// quit the settings manager thread and wait on it to make sure it's gone
|
||||
settingsManagerThread->quit();
|
||||
settingsManagerThread->wait();
|
||||
}
|
||||
|
||||
|
||||
void setupPrivateInstance() {
|
||||
// Ensure Setting::init has already ran and qApp exists
|
||||
if (qApp && globalManager) {
|
||||
// Let's set up the settings Private instance on its own thread
|
||||
QThread* thread = new QThread();
|
||||
Q_CHECK_PTR(thread);
|
||||
thread->setObjectName("Settings Thread");
|
||||
|
||||
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
|
||||
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
|
||||
globalManager->moveToThread(thread);
|
||||
thread->start();
|
||||
qCDebug(shared) << "Settings thread started.";
|
||||
|
||||
// Register cleanupPrivateInstance to run inside QCoreApplication's destructor.
|
||||
qAddPostRoutine(cleanupPrivateInstance);
|
||||
}
|
||||
}
|
||||
FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance)
|
||||
|
||||
// Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand,
|
||||
void init() {
|
||||
// Set settings format
|
||||
|
@ -59,23 +80,9 @@ namespace Setting {
|
|||
qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename;
|
||||
}
|
||||
|
||||
|
||||
// Let's set up the settings Private instance on its own thread
|
||||
QThread* thread = new QThread();
|
||||
Q_CHECK_PTR(thread);
|
||||
thread->setObjectName("Settings Thread");
|
||||
|
||||
globalManager = DependencyManager::set<Manager>();
|
||||
|
||||
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
|
||||
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
|
||||
globalManager->moveToThread(thread);
|
||||
thread->start();
|
||||
qCDebug(shared) << "Settings thread started.";
|
||||
|
||||
// Register cleanupPrivateInstance to run inside QCoreApplication's destructor.
|
||||
qAddPostRoutine(cleanupPrivateInstance);
|
||||
setupPrivateInstance();
|
||||
}
|
||||
|
||||
void Interface::init() {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
|
@ -62,6 +63,43 @@ extern "C" FILE * __cdecl __iob_func(void) {
|
|||
#include "OctalCode.h"
|
||||
#include "SharedLogging.h"
|
||||
|
||||
static std::unordered_map<std::string, QVariant> stagedGlobalInstances;
|
||||
|
||||
|
||||
std::mutex& globalInstancesMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
static void commitGlobalInstances() {
|
||||
std::unique_lock<std::mutex> lock(globalInstancesMutex());
|
||||
for (const auto& it : stagedGlobalInstances) {
|
||||
qApp->setProperty(it.first.c_str(), it.second);
|
||||
}
|
||||
stagedGlobalInstances.clear();
|
||||
}
|
||||
FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances)
|
||||
|
||||
QVariant getGlobalInstance(const char* propertyName) {
|
||||
if (qApp) {
|
||||
return qApp->property(propertyName);
|
||||
} else {
|
||||
auto it = stagedGlobalInstances.find(propertyName);
|
||||
if (it != stagedGlobalInstances.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void setGlobalInstance(const char* propertyName, const QVariant& variant) {
|
||||
if (qApp) {
|
||||
qApp->setProperty(propertyName, variant);
|
||||
} else {
|
||||
stagedGlobalInstances[propertyName] = variant;
|
||||
}
|
||||
}
|
||||
|
||||
static qint64 usecTimestampNowAdjust = 0; // in usec
|
||||
void usecTimestampNowForceClockSkew(qint64 clockSkew) {
|
||||
::usecTimestampNowAdjust = clockSkew;
|
||||
|
|
|
@ -25,6 +25,22 @@
|
|||
#include <QtCore/QCoreApplication>
|
||||
#include <QUuid>
|
||||
|
||||
// Workaround for https://bugreports.qt.io/browse/QTBUG-54479
|
||||
// Wrap target function inside another function that holds
|
||||
// a unique string identifier and uses it to ensure it only runs once
|
||||
// by storing a state within the qApp
|
||||
// We cannot used std::call_once with a static once_flag because
|
||||
// this is used in shared libraries that are linked by several DLLs
|
||||
// (ie. plugins), meaning the static will be useless in that case
|
||||
#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \
|
||||
static void AFUNC ## _fixed() { \
|
||||
const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \
|
||||
if (!qApp->property(propertyName.c_str()).toBool()) { \
|
||||
AFUNC(); \
|
||||
qApp->setProperty(propertyName.c_str(), QVariant(true)); \
|
||||
} \
|
||||
} \
|
||||
Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed)
|
||||
|
||||
// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows
|
||||
// the value to be reset when the sessionID changes.
|
||||
|
@ -52,6 +68,10 @@ bool destroyGlobalInstance() {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::mutex& globalInstancesMutex();
|
||||
QVariant getGlobalInstance(const char* propertyName);
|
||||
void setGlobalInstance(const char* propertyName, const QVariant& variant);
|
||||
|
||||
// Provides efficient access to a named global type. By storing the value
|
||||
// in the QApplication by name we can implement the singleton pattern and
|
||||
// have the single instance function across DLL boundaries.
|
||||
|
@ -60,9 +80,9 @@ T* globalInstance(const char* propertyName, Args&&... args) {
|
|||
static T* resultInstance { nullptr };
|
||||
static std::mutex mutex;
|
||||
if (!resultInstance) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
std::unique_lock<std::mutex> lock(globalInstancesMutex());
|
||||
if (!resultInstance) {
|
||||
auto variant = qApp->property(propertyName);
|
||||
auto variant = getGlobalInstance(propertyName);
|
||||
if (variant.isNull()) {
|
||||
std::unique_ptr<T>& instancePtr = globalInstancePointer<T>();
|
||||
if (!instancePtr.get()) {
|
||||
|
@ -72,7 +92,7 @@ T* globalInstance(const char* propertyName, Args&&... args) {
|
|||
}
|
||||
void* voidInstance = &(*instancePtr);
|
||||
variant = QVariant::fromValue(voidInstance);
|
||||
qApp->setProperty(propertyName, variant);
|
||||
setGlobalInstance(propertyName, variant);
|
||||
}
|
||||
void* returnedVoidInstance = variant.value<void*>();
|
||||
resultInstance = static_cast<T*>(returnedVoidInstance);
|
||||
|
|
|
@ -31,7 +31,7 @@ void TriangleSet::clear() {
|
|||
}
|
||||
|
||||
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) {
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
||||
|
||||
// reset our distance to be the max possible, lower level tests will store best distance here
|
||||
distance = std::numeric_limits<float>::max();
|
||||
|
@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3&
|
|||
}
|
||||
|
||||
int trianglesTouched = 0;
|
||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface);
|
||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, triangle, precision, trianglesTouched, allowBackface);
|
||||
|
||||
#if WANT_DEBUGGING
|
||||
if (precision) {
|
||||
|
@ -95,11 +95,12 @@ void TriangleSet::balanceOctree() {
|
|||
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
||||
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
||||
bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
float boxDistance = distance;
|
||||
float bestDistance = distance;
|
||||
glm::vec3 surfaceNormal;
|
||||
|
||||
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
|
||||
|
||||
|
@ -112,14 +113,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec
|
|||
|
||||
if (precision) {
|
||||
for (const auto& triangleIndex : _triangleIndices) {
|
||||
const auto& triangle = _allTriangles[triangleIndex];
|
||||
const auto& thisTriangle = _allTriangles[triangleIndex];
|
||||
float thisTriangleDistance;
|
||||
trianglesTouched++;
|
||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) {
|
||||
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
|
||||
if (thisTriangleDistance < bestDistance) {
|
||||
bestDistance = thisTriangleDistance;
|
||||
intersectedSomething = true;
|
||||
surfaceNormal = triangle.getNormal();
|
||||
triangle = thisTriangle;
|
||||
distance = bestDistance;
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +205,8 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
|
|||
}
|
||||
|
||||
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||
bool allowBackface) {
|
||||
|
||||
if (_population < 1) {
|
||||
return false; // no triangles below here, so we can't intersect
|
||||
|
@ -212,6 +214,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
|
|||
|
||||
float bestLocalDistance = distance;
|
||||
BoxFace bestLocalFace;
|
||||
Triangle bestLocalTriangle;
|
||||
glm::vec3 bestLocalNormal;
|
||||
bool intersects = false;
|
||||
|
||||
|
@ -229,7 +232,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
|
|||
|
||||
float childDistance = distance;
|
||||
BoxFace childFace;
|
||||
glm::vec3 childNormal;
|
||||
Triangle childTriangle;
|
||||
|
||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||
if (_depth < MAX_DEPTH) {
|
||||
|
@ -237,22 +240,22 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
|
|||
// check each child, if there's an intersection, it will return some distance that we need
|
||||
// to compare against the other results, because there might be multiple intersections and
|
||||
// we will always choose the best (shortest) intersection
|
||||
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
||||
if (childDistance < bestLocalDistance) {
|
||||
bestLocalDistance = childDistance;
|
||||
bestLocalFace = childFace;
|
||||
bestLocalNormal = childNormal;
|
||||
bestLocalTriangle = childTriangle;
|
||||
intersects = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// also check our local triangle set
|
||||
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) {
|
||||
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) {
|
||||
if (childDistance < bestLocalDistance) {
|
||||
bestLocalDistance = childDistance;
|
||||
bestLocalFace = childFace;
|
||||
bestLocalNormal = childNormal;
|
||||
bestLocalTriangle = childTriangle;
|
||||
intersects = true;
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +263,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
|
|||
if (intersects) {
|
||||
distance = bestLocalDistance;
|
||||
face = bestLocalFace;
|
||||
surfaceNormal = bestLocalNormal;
|
||||
triangle = bestLocalTriangle;
|
||||
}
|
||||
return intersects;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ class TriangleSet {
|
|||
void clear();
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||
bool allowBackface = false);
|
||||
|
||||
const AABox& getBounds() const { return _bounds; }
|
||||
|
||||
|
@ -38,7 +39,8 @@ class TriangleSet {
|
|||
|
||||
// checks our internal list of triangles
|
||||
bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
||||
bool allowBackface = false);
|
||||
|
||||
std::vector<Triangle>& _allTriangles;
|
||||
std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
|
||||
|
@ -60,7 +62,7 @@ public:
|
|||
void insert(const Triangle& t);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false);
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false);
|
||||
|
||||
void balanceOctree();
|
||||
|
||||
|
@ -72,7 +74,7 @@ public:
|
|||
// intersection occurs, the distance and surface normal will be provided.
|
||||
// note: this might side-effect internal structures
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched);
|
||||
|
||||
// Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to
|
||||
// determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a
|
||||
|
|
|
@ -274,7 +274,7 @@ function actionStartEvent(event) {
|
|||
var result = Overlays.findRayIntersection(event.actionRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
|
||||
var panelName = result.extraInfo;
|
||||
var panelName = result.extraInfo.subMeshName + '';
|
||||
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
|
@ -338,7 +338,7 @@ function handleLookAt(pickRay) {
|
|||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
var panelName = result.extraInfo;
|
||||
var panelName = result.extraInfo.subMeshName + '';
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
|
|
|
@ -272,7 +272,7 @@ function actionStartEvent(event) {
|
|||
var result = Overlays.findRayIntersection(event.actionRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
|
||||
var panelName = result.extraInfo;
|
||||
var panelName = result.extraInfo.subMeshName + '';
|
||||
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
|
@ -321,7 +321,7 @@ function handleLookAt(pickRay) {
|
|||
// check if we hit a panel and if we should jump there
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
if (result.intersects && result.overlayID == panelWall) {
|
||||
var panelName = result.extraInfo;
|
||||
var panelName = result.extraInfo.subMeshName + '';
|
||||
var panelStringIndex = panelName.indexOf("Panel");
|
||||
if (panelStringIndex != -1) {
|
||||
var panelIndex = parseInt(panelName.slice(5));
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
});
|
||||
}
|
||||
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise)
|
||||
url = METAVERSE_BASE + '/api/v1/users?'
|
||||
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
|
||||
if (domain) {
|
||||
url += 'status=' + domain.slice(1, -1); // without curly braces
|
||||
} else {
|
||||
|
|
|
@ -469,7 +469,7 @@ var toolBar = (function () {
|
|||
|
||||
// tablet version of new-model dialog
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
tablet.pushOntoStack("NewModelDialog.qml");
|
||||
tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml");
|
||||
});
|
||||
|
||||
addButton("newCubeButton", "cube-01.svg", function () {
|
||||
|
@ -1344,7 +1344,7 @@ function recursiveDelete(entities, childrenList, deletedIDs) {
|
|||
var entityID = entities[i];
|
||||
var children = Entities.getChildrenIDs(entityID);
|
||||
var grandchildrenList = [];
|
||||
recursiveDelete(children, grandchildrenList);
|
||||
recursiveDelete(children, grandchildrenList, deletedIDs);
|
||||
var initialProperties = Entities.getEntityProperties(entityID);
|
||||
childrenList.push({
|
||||
entityID: entityID,
|
||||
|
|
|
@ -361,7 +361,7 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
|
|||
});
|
||||
}
|
||||
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
|
||||
url = METAVERSE_BASE + '/api/v1/users?'
|
||||
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
|
||||
if (domain) {
|
||||
url += 'status=' + domain.slice(1, -1); // without curly braces
|
||||
} else {
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
button.editProperties({isActive: shouldActivateButton});
|
||||
wireEventBridge(true);
|
||||
messagesWaiting(false);
|
||||
tablet.sendToQml({ method: 'refreshFeeds' })
|
||||
tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() })
|
||||
|
||||
} else {
|
||||
shouldActivateButton = false;
|
||||
|
|
|
@ -33,8 +33,6 @@ private:
|
|||
Qt59TestApp::Qt59TestApp(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv)
|
||||
{
|
||||
|
||||
Setting::init();
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); });
|
||||
DependencyManager::set<AddressManager>();
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
add_subdirectory(scribe)
|
||||
set_target_properties(scribe PROPERTIES FOLDER "Tools")
|
||||
|
||||
find_npm()
|
||||
if (NPM_EXECUTABLE)
|
||||
add_subdirectory(jsdoc)
|
||||
set_target_properties(jsdoc PROPERTIES FOLDER "Tools")
|
||||
endif()
|
||||
|
||||
if (BUILD_TOOLS)
|
||||
add_subdirectory(udt-test)
|
||||
set_target_properties(udt-test PROPERTIES FOLDER "Tools")
|
||||
|
@ -27,4 +33,3 @@ if (BUILD_TOOLS)
|
|||
add_subdirectory(auto-tester)
|
||||
set_target_properties(auto-tester PROPERTIES FOLDER "Tools")
|
||||
endif()
|
||||
|
||||
|
|
|
@ -97,7 +97,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
|
|||
_password = pieces[1];
|
||||
}
|
||||
|
||||
Setting::init();
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
|
||||
DependencyManager::set<AccountManager>([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); });
|
||||
|
|
|
@ -25,6 +25,8 @@ int main(int argc, char * argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
ACClientApp app(argc, argv);
|
||||
|
||||
return app.exec();
|
||||
|
|
|
@ -135,7 +135,6 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) :
|
|||
_domainServerAddress = domainURL.toString();
|
||||
}
|
||||
|
||||
Setting::init();
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
|
||||
DependencyManager::set<StatTracker>();
|
||||
|
|
|
@ -25,6 +25,8 @@ int main(int argc, char * argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
ATPClientApp app(argc, argv);
|
||||
|
||||
return app.exec();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue