Merge branch 'master' of https://github.com/highfidelity/hifi into hdr

This commit is contained in:
samcake 2016-08-25 12:36:01 -07:00
commit 9669234f9c
134 changed files with 6719 additions and 1683 deletions
BUILD.mdCMakeLists.txt
cmake
domain-server/src
interface
libraries
plugins/openvr/src
scripts

View file

@ -18,6 +18,7 @@
* [oglplus](http://oglplus.org/) ~> 0.63
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
* [QuaZip](http://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
* [soxr](http://soxr.sourceforge.net) ~> 0.1.1
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3

View file

@ -204,6 +204,7 @@ set_property(DIRECTORY PROPERTY EP_PREFIX ${EXTERNAL_PROJECT_PREFIX})
setup_externals_binary_dir()
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
option(GET_QUAZIP "Get QuaZip library automatically as external project" 1)
if (WIN32)

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

@ -0,0 +1,55 @@
set(EXTERNAL_NAME quazip)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
cmake_policy(SET CMP0046 OLD)
include(ExternalProject)
if (WIN32)
# windows shell does not like backslashes expanded on the command line,
# so convert all backslashes in the QT path to forward slashes
string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
elseif ($ENV{QT_CMAKE_PREFIX_PATH})
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
endif ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.7.2.zip
URL_MD5 2955176048a31262c09259ca8d309d19
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
add_dependencies(quazip zlib)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES
FOLDER "hidden/externals"
INSTALL_NAME_DIR ${INSTALL_DIR}/lib
BUILD_WITH_INSTALL_RPATH True)
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories")
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories")
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL")
if (APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
elseif (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library")
endif ()
include(SelectLibraryConfigurations)
select_library_configurations(${EXTERNAL_NAME_UPPER})
# Force selected libraries into the cache
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries")
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries")

View file

@ -0,0 +1,16 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Leonardo Murillo on 2015/11/20
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_QUAZIP)
add_dependency_external_projects(quazip)
find_package(QuaZip REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
if (WIN32)
add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
endif ()
endmacro()

View file

@ -0,0 +1,29 @@
#
# FindQuaZip.h
# StackManagerQt/cmake/modules
#
# Created by Mohammed Nafees.
# Copyright (c) 2014 High Fidelity. All rights reserved.
#
# QUAZIP_FOUND - QuaZip library was found
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
# QUAZIP_LIBRARIES - List of QuaZip libraries
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("quazip")
if (WIN32)
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
elseif (APPLE)
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
else ()
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES quazip HINTS ${QUAZIP_SEARCH_DIRS})
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_INCLUDE_DIRS)
mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_SEARCH_DIRS)

View file

@ -118,8 +118,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setupNodeListAndAssignments();
if (_type == MetaverseDomain) {
// if we have a metaverse domain, we'll need an access token to heartbeat handle auto-networking
if (_type != NonMetaverse) {
// if we have a metaverse domain, we'll use an access token for API calls
resetAccountManagerAccessToken();
}
@ -469,8 +469,8 @@ bool DomainServer::resetAccountManagerAccessToken() {
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
qDebug() << "Set an access token via the web interface, in your user or master config"
qWarning() << "No access token is present. Some operations that use the metaverse API will fail.";
qDebug() << "Set an access token via the web interface, in your user config"
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
// clear any existing access token from AccountManager
@ -480,7 +480,7 @@ bool DomainServer::resetAccountManagerAccessToken() {
}
} else {
qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present"
<< " in the user or master config.";
<< " in the user config.";
}
// give this access token to the AccountManager

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path d="M25.9,20.6c0.29,0.15,0.47,0.38,0.48,0.72c0,0.7,0.1,2.92,0.08,3.62c1,0.3,3.61,1.19,4.56,1.45
c0.15,0.01,0.27,0.03,0.38,0.09c0.2,0.1,0.33,0.3,0.36,0.58c0.01,0.11,0.02,0.22,0.02,0.33c-0.03,1.5,0.01,1.08-0.05,2.58
c-0.06,1.55-0.78,2.72-2.21,3.34c-1.12,0.49-2.01,0.57-2.92,0.1c-0.25-0.13-0.5-0.3-0.76-0.51c-1.46-1.21-2.95-2.39-4.42-3.59
c-0.37-0.3-0.48-0.75-0.29-1.15c0.19-0.39,0.63-0.61,1.05-0.49c0.06,0.02,0.12,0.04,0.18,0.07c0.1,0.05,0.19,0.11,0.27,0.18
c0.64,0.51,1,1.02,1.63,1.53c0,0,0.01,0,0.01,0.01c0.01,0.01,0.04,0.01,0.09,0.04c0-0.12,0.01-0.23,0.01-0.33
c0.06-2.53,0.12-5.06,0.18-7.59c0.01-0.36,0.06-0.69,0.37-0.92c0.28-0.2,0.58-0.25,0.89-0.12C25.85,20.57,25.88,20.58,25.9,20.6
M26.64,19.16c-0.06-0.03-0.12-0.06-0.18-0.09c-0.83-0.36-1.74-0.25-2.48,0.3c-1,0.73-1.03,1.83-1.04,2.19
c-0.04,1.54-0.07,3.11-0.11,4.64c-0.07-0.02-0.13-0.05-0.2-0.07c-1.17-0.34-2.41,0.22-2.96,1.33c-0.26,0.53-0.32,1.14-0.18,1.71
c0.14,0.55,0.45,1.03,0.91,1.4c0.49,0.4,0.98,0.8,1.46,1.18c0.97,0.78,1.97,1.59,2.94,2.4c0.35,0.29,0.7,0.52,1.05,0.71
c1.72,0.88,3.28,0.39,4.3-0.06c1.96-0.86,3.09-2.55,3.18-4.76c0.03-0.89,0.04-1.13,0.04-1.47c0-0.23,0-0.51,0.01-1.13
c0-0.18,0-0.37-0.03-0.56c-0.1-0.82-0.54-1.49-1.23-1.84c-0.25-0.13-0.53-0.21-0.82-0.25c-0.56-0.16-1.57-0.49-2.48-0.79
c-0.27-0.09-0.53-0.17-0.78-0.25c-0.01-0.34-0.02-0.7-0.03-1.06C28.02,22.12,28,21.58,28,21.31
C27.99,20.39,27.49,19.59,26.64,19.16L26.64,19.16z"/>
</g>
<path d="M33.4,17.76l-2.87-2.64l-0.13,1.65c-1.42-0.61-3.13-0.95-4.95-0.95c-1.91,0-3.66,0.37-5.06,1.04l-0.23-1.76l-2.73,2.79
l3.35,2l-0.22-1.66c1.27-0.71,3.01-1.11,4.89-1.11c1.8,0,3.52,0.37,4.84,1.03l-0.14,1.79L33.4,17.76z"/>
<g>
<path class="st0" d="M25.64,19.86c0.29,0.15,0.47,0.38,0.48,0.72c0,0.7,0.1,2.92,0.08,3.62c1,0.3,3.61,1.19,4.56,1.45
c0.16,0.04,0.28,0.08,0.39,0.13c0.2,0.1,0.31,0.26,0.35,0.54c0.01,0.11,0.02,0.22,0.02,0.33c-0.03,1.5,0.01,1.08-0.05,2.58
c-0.06,1.55-0.78,2.72-2.21,3.34c-1.12,0.49-2.01,0.57-2.92,0.1c-0.25-0.13-0.5-0.3-0.76-0.51c-1.46-1.21-2.95-2.39-4.42-3.59
c-0.37-0.3-0.48-0.75-0.29-1.15c0.19-0.39,0.63-0.61,1.05-0.49c0.06,0.02,0.12,0.04,0.18,0.07c0.1,0.05,0.19,0.11,0.27,0.18
c0.64,0.51,1,1.02,1.63,1.53c0,0,0.01,0,0.01,0.01c0.01,0.01,0.04,0.01,0.09,0.04c0-0.12,0.01-0.23,0.01-0.33
c0.06-2.53,0.12-5.06,0.18-7.59c0.01-0.36,0.06-0.69,0.37-0.92c0.28-0.2,0.58-0.25,0.89-0.12C25.59,19.83,25.62,19.84,25.64,19.86
M26.38,18.42c-0.06-0.03-0.12-0.06-0.18-0.09c-0.83-0.36-1.74-0.25-2.48,0.3c-1,0.73-1.03,1.83-1.04,2.19
c-0.04,1.54-0.07,3.11-0.11,4.64c-0.07-0.02-0.13-0.05-0.2-0.07c-1.17-0.34-2.41,0.22-2.96,1.33c-0.26,0.53-0.32,1.14-0.18,1.71
c0.14,0.55,0.45,1.03,0.91,1.4c0.49,0.4,0.98,0.8,1.46,1.18c0.97,0.78,1.97,1.59,2.94,2.4c0.35,0.29,0.7,0.52,1.05,0.71
c1.72,0.88,3.28,0.39,4.3-0.06c1.96-0.86,3.09-2.55,3.18-4.76c0.03-0.89,0.04-1.13,0.04-1.47c0-0.23,0-0.51,0.01-1.13
c0-0.18,0-0.37-0.03-0.56c-0.1-0.82-0.45-1.41-1.13-1.76c-0.25-0.13-0.61-0.24-0.91-0.32c-0.56-0.16-1.57-0.49-2.48-0.79
c-0.27-0.09-0.53-0.17-0.78-0.25c-0.01-0.34-0.02-0.7-0.03-1.06c-0.02-0.57-0.03-1.11-0.04-1.38
C27.73,19.65,27.23,18.85,26.38,18.42L26.38,18.42z"/>
</g>
<path class="st0" d="M33.14,17.02l-2.87-2.64l-0.13,1.65c-1.42-0.61-3.13-0.95-4.95-0.95c-1.91,0-3.66,0.37-5.06,1.04l-0.23-1.76
l-2.73,2.79l3.35,2l-0.22-1.66c1.27-0.71,3.01-1.11,4.89-1.11c1.8,0,3.52,0.37,4.84,1.03l-0.14,1.79L33.14,17.02z"/>
</svg>

After

(image error) Size: 3.7 KiB

View file

@ -1,81 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="1440"
height="200"
data-icon="map-marker"
data-container-transform="translate(24)"
viewBox="0 0 1440 200"
id="svg4136"
inkscape:version="0.91 r13725"
sodipodi:docname="address-bar.svg">
<metadata
id="metadata4144">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4142" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1536"
inkscape:window-height="687"
id="namedview4140"
showgrid="false"
inkscape:zoom="0.61319416"
inkscape:cx="670.06567"
inkscape:cy="52.468468"
inkscape:window-x="105"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg4136" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none;stroke-linejoin:round;stroke-opacity:1"
id="rect4141"
width="1280"
height="140"
x="160"
y="30"
rx="16.025024"
ry="17.019567" />
<rect
style="fill:#dadada;fill-opacity:1;stroke:#cbcbcb;stroke-width:0.35830048;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="rect4135"
width="328.72031"
height="139.64169"
x="150.33546"
y="30.179144"
rx="18.876532"
ry="20.609974" />
<circle
style="fill:#b8b8b8;fill-opacity:1;stroke:none;stroke-opacity:1"
id="path4146"
cx="100"
cy="100"
r="100" />
<path
d="m 100,36.000005 c -22.1,0 -40,17.9 -40,39.999995 0,30 40,88 40,88 0,0 40,-58 40,-88 0,-22.099995 -17.9,-39.999995 -40,-39.999995 z m 0,22 c 9.9,0 18,8.099995 18,17.999995 0,9.9 -8.1,18 -18,18 -9.9,0 -18,-8.1 -18,-18 0,-9.9 8.1,-17.999995 18,-17.999995 z"
id="path4138"
inkscape:connector-curvature="0" />
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1"
id="svg4136" inkscape:version="0.91 r13725" sodipodi:docname="address-bar.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1440 200"
style="enable-background:new 0 0 1440 200;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1E1E1E;}
.st1{fill:#E6E7E8;}
.st2{fill:#FFFFFF;}
</style>
<path class="st0" d="M1428.61,172H11.46c-6.27,0-11.39-5.13-11.39-11.39V49.58c0-6.27,5.13-11.39,11.39-11.39h1417.15
c6.27,0,11.39,5.13,11.39,11.39v111.03C1440,166.87,1434.87,172,1428.61,172z"/>
<path class="st1" d="M1428.61,165.81H11.46c-6.27,0-11.39-5.13-11.39-11.39V43.39c0-6.27,5.13-11.39,11.39-11.39h1417.15
c6.27,0,11.39,5.13,11.39,11.39v111.03C1440,160.68,1434.87,165.81,1428.61,165.81z"/>
<path class="st2" d="M1133.24,165.81H417.95c-4.47,0-8.12-3.65-8.12-8.12V40.11c0-4.47,3.65-8.12,8.12-8.12h715.28
c4.47,0,8.12,3.65,8.12,8.12v117.57C1141.36,162.15,1137.7,165.81,1133.24,165.81z"/>
</svg>

Before

(image error) Size: 2.5 KiB

After

(image error) Size: 1.4 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M33.72,85.08l-9.15-9.15l-0.74-0.74l0.74-0.74l9.35-9.35c0.59-0.59,0.59-1.56,0-2.15
c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45L19.52,75.19l12.04,12.04c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45C34.31,86.64,34.31,85.67,33.72,85.08z"/>
<path class="st1" d="M33.72,33.45l-9.15-9.15l-0.74-0.74l0.74-0.74l9.35-9.35c0.59-0.59,0.59-1.56,0-2.15
c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45L19.52,23.56L31.56,35.6c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45C34.31,35.01,34.31,34.04,33.72,33.45z"/>
<path class="st2" d="M17.99,124.82l12.78,12.78c1,1,2.63,1,3.63,0c1-1,1-2.63,0-3.63l-9.15-9.15l9.35-9.35c1-1,1-2.63,0-3.63
c-1-1-2.63-1-3.63,0L17.99,124.82z"/>
<path class="st3" d="M32.79,112.13c0.41,0,0.79,0.16,1.08,0.45c0.59,0.59,0.59,1.56,0,2.15l-9.35,9.35l-0.74,0.74l0.74,0.74
l9.15,9.15c0.59,0.59,0.59,1.56,0,2.15c-0.29,0.29-0.67,0.45-1.08,0.45c-0.41,0-0.79-0.16-1.08-0.45l-12.04-12.04l12.24-12.24
C32,112.29,32.38,112.13,32.79,112.13 M32.79,111.08c-0.66,0-1.31,0.25-1.82,0.75l-12.98,12.98l12.78,12.78
c0.5,0.5,1.16,0.75,1.82,0.75c0.66,0,1.31-0.25,1.82-0.75c1-1,1-2.63,0-3.63l-9.15-9.15l9.35-9.35c1-1,1-2.63,0-3.63
C34.1,111.34,33.44,111.08,32.79,111.08L32.79,111.08z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

(image error) Size: 1.7 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M21.12,62.95c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45c-0.59,0.59-0.59,1.56,0,2.15
l9.35,9.35l0.74,0.74l-0.74,0.74l-9.15,9.15c-0.59,0.59-0.59,1.56,0,2.15c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45l12.04-12.04L21.12,62.95z"/>
<path class="st1" d="M21.12,11.32c-0.29-0.29-0.67-0.45-1.08-0.45c-0.41,0-0.79,0.16-1.08,0.45c-0.59,0.59-0.59,1.56,0,2.15
l9.35,9.35l0.74,0.74l-0.74,0.74l-9.15,9.15c-0.59,0.59-0.59,1.56,0,2.15c0.29,0.29,0.67,0.45,1.08,0.45
c0.41,0,0.79-0.16,1.08-0.45l12.04-12.04L21.12,11.32z"/>
<path class="st2" d="M34.9,124.82L22.11,137.6c-1,1-2.63,1-3.63,0c-1-1-1-2.63,0-3.63l9.15-9.15l-9.35-9.35c-1-1-1-2.63,0-3.63
c1-1,2.63-1,3.63,0L34.9,124.82z"/>
<path class="st3" d="M20.1,112.13c0.41,0,0.79,0.16,1.08,0.45l12.24,12.24l-12.04,12.04c-0.29,0.29-0.67,0.45-1.08,0.45
c-0.41,0-0.79-0.16-1.08-0.45c-0.59-0.59-0.59-1.56,0-2.15l9.15-9.15l0.74-0.74l-0.74-0.74l-9.35-9.35c-0.59-0.59-0.59-1.56,0-2.15
C19.31,112.29,19.69,112.13,20.1,112.13 M20.1,111.08c-0.66,0-1.31,0.25-1.82,0.75c-1,1-1,2.63,0,3.63l9.35,9.35l-9.15,9.15
c-1,1-1,2.63,0,3.63c0.5,0.5,1.16,0.75,1.82,0.75s1.31-0.25,1.82-0.75l12.78-12.78l-12.98-12.98
C21.41,111.34,20.76,111.08,20.1,111.08L20.1,111.08z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

(image error) Size: 1.7 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<g>
<path class="st0" d="M36.75,71.26c-0.05-0.31-0.22-0.57-0.47-0.76l-10.72-7.73l-8.21,5.71l0,0l0,0l-2.98,1.99
c-0.26,0.17-0.43,0.43-0.5,0.74c-0.06,0.3,0,0.61,0.17,0.87c0.22,0.32,0.58,0.52,0.97,0.52c0.23,0,0.45-0.07,0.64-0.19l2.99-1.99
l6.62-4.61l0.28-0.2l0.28,0.2l9.11,6.57c0.2,0.14,0.43,0.22,0.68,0.22c0.37,0,0.73-0.18,0.94-0.48
C36.73,71.88,36.8,71.57,36.75,71.26z"/>
<path class="st0" d="M23.59,79.62c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V79.62z"/>
</g>
<g>
<path class="st1" d="M36.75,19.64c-0.05-0.31-0.22-0.57-0.47-0.76l-10.72-7.73l-8.21,5.71l0,0l0,0l-2.98,1.99
c-0.26,0.17-0.43,0.43-0.5,0.74c-0.06,0.3,0,0.61,0.17,0.87c0.22,0.32,0.58,0.52,0.97,0.52c0.23,0,0.45-0.07,0.64-0.19l2.99-1.99
l6.62-4.61l0.28-0.2l0.28,0.2l9.11,6.57c0.2,0.14,0.43,0.22,0.68,0.22c0.37,0,0.73-0.18,0.94-0.48
C36.73,20.25,36.8,19.94,36.75,19.64z"/>
<path class="st1" d="M23.59,27.99c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V27.99z"/>
</g>
<g>
<path class="st2" d="M15,122.61c-0.53,0-1.05-0.26-1.37-0.73c-0.5-0.76-0.3-1.78,0.46-2.28l2.98-1.99l8.5-5.91l11,7.93
c0.74,0.53,0.9,1.56,0.37,2.3c-0.53,0.74-1.56,0.9-2.3,0.37l-9.11-6.57l-6.63,4.61l-3,1.99C15.63,122.52,15.31,122.61,15,122.61z"
/>
<path class="st2" d="M35.6,122.69c-0.36,0-0.71-0.11-1.01-0.33l-9.07-6.53l-6.58,4.58l-3,2c-0.28,0.19-0.61,0.29-0.95,0.29
c-0.58,0-1.12-0.29-1.44-0.77c-0.53-0.79-0.31-1.87,0.48-2.39l2.98-1.99l8.54-5.94l11.05,7.96c0.37,0.27,0.62,0.67,0.69,1.12
c0.07,0.46-0.03,0.91-0.3,1.29c-0.27,0.37-0.67,0.62-1.12,0.69C35.79,122.68,35.7,122.69,35.6,122.69z M25.53,115.63l9.16,6.6
c0.34,0.24,0.75,0.34,1.17,0.28s0.77-0.29,1.02-0.63c0.24-0.34,0.34-0.75,0.28-1.17c-0.07-0.41-0.29-0.77-0.63-1.02l-10.96-7.9
l-8.45,5.88l-2.98,1.99c-0.72,0.48-0.91,1.45-0.44,2.17c0.29,0.44,0.78,0.7,1.3,0.7c0.31,0,0.61-0.09,0.87-0.26l3-1.99
L25.53,115.63z"/>
</g>
<path class="st3" d="M36.62,119.56l-11.05-7.96l-8.54,5.94l-2.98,1.99c-0.79,0.53-1.01,1.6-0.48,2.39
c0.32,0.48,0.86,0.77,1.44,0.77c0.34,0,0.67-0.1,0.95-0.29l3-2l6.58-4.58l9.07,6.53c0.3,0.21,0.64,0.33,1.01,0.33
c0.55,0,1.08-0.27,1.4-0.72C37.56,121.2,37.39,120.12,36.62,119.56z M17.39,118.07l8.17-5.68l10.68,7.7
c0.23,0.17,0.39,0.42,0.43,0.7c0.05,0.28-0.02,0.57-0.19,0.8c-0.2,0.28-0.53,0.45-0.88,0.45c-0.23,0-0.45-0.07-0.63-0.2l-9.44-6.8
l-6.95,4.83l-2.99,1.99c-0.18,0.12-0.38,0.18-0.6,0.18c-0.36,0-0.7-0.18-0.9-0.48c-0.16-0.24-0.22-0.53-0.16-0.81
c0.06-0.28,0.22-0.53,0.46-0.69L17.39,118.07z"/>
<path class="st2" d="M23.59,129.14c0-1.13,0.97-2.03,2.1-2.03c1.13,0,2.1,0.93,2.1,2.06v6.06h7.58v-8.97l-3.28-2.41l-6.46-4.68
l-6.88,4.64l-3.23,2.1v9.32h8.07V129.14z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

(image error) Size: 3.2 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<path class="st0" d="M25.71,59.91c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,63.83,30.54,59.91,25.71,59.91z M25.71,72.53c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,70.52,28.19,72.53,25.71,72.53z"/>
<path class="st0" d="M36.43,86.1H14.28c-0.49,0-0.9,0.39-0.9,0.89c0,0.49,0.4,0.89,0.9,0.89h22.15c0.49,0,0.9-0.39,0.9-0.89
C37.32,86.49,36.92,86.1,36.43,86.1z"/>
<path class="st1" d="M25.71,8.28c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,12.2,30.54,8.28,25.71,8.28z M25.71,20.91c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,18.89,28.19,20.91,25.71,20.91z"/>
<path class="st1" d="M36.43,34.47H14.28c-0.49,0-0.9,0.39-0.9,0.89s0.4,0.89,0.9,0.89h22.15c0.49,0,0.9-0.39,0.9-0.89
S36.92,34.47,36.43,34.47z"/>
<path class="st2" d="M25.71,109.66c-4.83,0-8.75,3.92-8.75,8.75c0,4.83,8.75,16.5,8.75,16.5s8.75-11.67,8.75-16.5
C34.46,113.57,30.54,109.66,25.71,109.66z M25.71,122.28c-2.48,0-4.5-2.01-4.5-4.5c0-2.48,2.01-4.5,4.5-4.5s4.5,2.01,4.5,4.5
C30.2,120.27,28.19,122.28,25.71,122.28z"/>
<path class="st2" d="M36.43,138.33H14.28c-0.85,0-1.54-0.69-1.54-1.54s0.69-1.54,1.54-1.54h22.15c0.85,0,1.54,0.69,1.54,1.54
S37.28,138.33,36.43,138.33z"/>
<path class="st3" d="M36.43,135.89c0.49,0,0.9,0.4,0.9,0.9c0,0.49-0.4,0.9-0.9,0.9H14.28c-0.49,0-0.9-0.4-0.9-0.9
c0-0.49,0.4-0.9,0.9-0.9H36.43 M36.43,135.25H14.28c-0.85,0-1.54,0.69-1.54,1.54c0,0.85,0.69,1.54,1.54,1.54h22.15
c0.85,0,1.54-0.69,1.54-1.54C37.97,135.94,37.28,135.25,36.43,135.25L36.43,135.25z"/>
</g>
<g id="Layer_2">
</g>
</svg>

After

(image error) Size: 2 KiB

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{fill:#414042;}
.st1{fill:#CCCCCC;}
.st2{fill:#1398BB;}
.st3{fill:#31D8FF;}
</style>
<g id="Layer_1">
<circle class="st0" cx="26.54" cy="69.8" r="4.44"/>
<path class="st0" d="M34.87,82.89c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42c-2.68,0-4.85,2.09-4.85,4.77
v1.88H34.87z"/>
<path class="st0" d="M44.17,67.05c0-3.97-3.22-7.19-7.19-7.19H16.67c-3.97,0-7.19,3.22-7.19,7.19v14.18c0,3.97,3.22,7.19,7.19,7.19
h20.31c3.97,0,7.19-3.22,7.19-7.19V67.05z M42.39,81.32c0,3.03-2.46,5.49-5.49,5.49H16.58c-3.03,0-5.49-2.46-5.49-5.49v-14.2
c0-3.03,2.46-5.49,5.49-5.49h20.33c3.03,0,5.49,2.46,5.49,5.49V81.32z"/>
<circle class="st1" cx="26.54" cy="18.17" r="4.44"/>
<path class="st1" d="M34.87,31.26c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42c-2.68,0-4.85,2.09-4.85,4.77
v1.88H34.87z"/>
<path class="st1" d="M44.17,15.42c0-3.97-3.22-7.19-7.19-7.19H16.67c-3.97,0-7.19,3.22-7.19,7.19V29.6c0,3.97,3.22,7.19,7.19,7.19
h20.31c3.97,0,7.19-3.22,7.19-7.19V15.42z M42.39,29.69c0,3.03-2.46,5.49-5.49,5.49H16.58c-3.03,0-5.49-2.46-5.49-5.49V15.5
c0-3.03,2.46-5.49,5.49-5.49h20.33c3.03,0,5.49,2.46,5.49,5.49V29.69z"/>
<circle class="st2" cx="26.52" cy="119.45" r="4.44"/>
<path class="st2" d="M34.85,132.54c0.01-0.01,0.02-0.02,0.03-0.03v-1.86c0-2.68-2.33-4.77-5-4.77h-6.42
c-2.68,0-4.85,2.09-4.85,4.77v1.88H34.85z"/>
<g>
<path class="st2" d="M36.9,138.76H16.6c-4.32,0-7.83-3.51-7.83-7.83v-14.16c0-4.32,3.51-7.83,7.83-7.83h20.3
c4.32,0,7.83,3.51,7.83,7.83v14.16C44.73,135.25,41.22,138.76,36.9,138.76z M16.6,111.93c-2.67,0-4.84,2.17-4.84,4.84v14.16
c0,2.67,2.17,4.84,4.84,4.84h20.3c2.67,0,4.84-2.17,4.84-4.84v-14.16c0-2.67-2.17-4.84-4.84-4.84H16.6z"/>
<path class="st3" d="M36.9,109.58c3.96,0,7.19,3.22,7.19,7.19v14.16c0,3.96-3.22,7.19-7.19,7.19H16.6c-3.96,0-7.19-3.22-7.19-7.19
v-14.16c0-3.96,3.22-7.19,7.19-7.19H36.9 M16.6,136.42h20.3c3.02,0,5.49-2.46,5.49-5.49v-14.16c0-3.02-2.46-5.49-5.49-5.49H16.6
c-3.02,0-5.49,2.46-5.49,5.49v14.16C11.11,133.95,13.57,136.42,16.6,136.42 M36.9,108.93H16.6c-4.32,0-7.83,3.51-7.83,7.83v14.16
c0,4.32,3.51,7.83,7.83,7.83h20.3c4.32,0,7.83-3.51,7.83-7.83v-14.16C44.73,112.45,41.22,108.93,36.9,108.93L36.9,108.93z
M16.6,135.77c-2.67,0-4.84-2.17-4.84-4.84v-14.16c0-2.67,2.17-4.84,4.84-4.84h20.3c2.67,0,4.84,2.17,4.84,4.84v14.16
c0,2.67-2.17,4.84-4.84,4.84H16.6L16.6,135.77z"/>
</g>
</g>
<g id="Layer_2">
</g>
</svg>

After

(image error) Size: 2.7 KiB

View file

@ -14,6 +14,8 @@ import "controls"
import "styles"
import "windows"
import "hifi"
import "hifi/toolbars"
import "controls-uit" as HifiControls
Window {
id: root
@ -45,50 +47,80 @@ Window {
anchors.centerIn = parent;
}
function resetAfterTeleport() {
storyCardFrame.shown = root.shown = false;
}
function goCard(card) {
addressLine.text = card.userStory.name;
if (addressBarDialog.useFeed) {
storyCardHTML.url = addressBarDialog.metaverseServerUrl + "/user_stories/" + card.storyId;
storyCardFrame.shown = true;
return;
}
addressLine.text = card.hifiUrl;
toggleOrGo(true);
}
property var allDomains: [];
property var suggestionChoices: [];
property var domainsBaseUrl: null;
property var allPlaces: [];
property var allStories: [];
property int cardWidth: 200;
property int cardHeight: 152;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
AddressBarDialog {
id: addressBarDialog
implicitWidth: backgroundImage.width
implicitHeight: backgroundImage.height
// The buttons have their button state changed on hover, so we have to manually fix them up here
onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0;
onForwardEnabledChanged: forwardArrow.buttonState = addressBarDialog.forwardEnabled ? 1 : 0;
onUseFeedChanged: updateFeedState();
onReceivedHifiSchemeURL: resetAfterTeleport();
Row {
ListModel { id: suggestions }
ListView {
id: scroll
width: backgroundImage.width;
height: cardHeight;
spacing: hifi.layout.spacing;
clip: true;
anchors {
bottom: backgroundImage.top;
bottomMargin: 2 * hifi.layout.spacing;
right: backgroundImage.right;
rightMargin: -104; // FIXME
horizontalCenter: backgroundImage.horizontalCenter
}
spacing: hifi.layout.spacing;
Card {
id: s0;
model: suggestions;
orientation: ListView.Horizontal;
delegate: Card {
width: cardWidth;
height: cardHeight;
goFunction: goCard
goFunction: goCard;
userName: model.username;
placeName: model.place_name;
hifiUrl: model.place_name + model.path;
imageUrl: model.image_url;
thumbnail: model.thumbnail_url;
action: model.action;
timestamp: model.created_at;
onlineUsers: model.online_users;
storyId: model.metaverseId;
hoverThunk: function () { ListView.view.currentIndex = index; }
unhoverThunk: function () { ListView.view.currentIndex = -1; }
}
Card {
id: s1;
width: cardWidth;
height: cardHeight;
goFunction: goCard
}
Card {
id: s2;
width: cardWidth;
height: cardHeight;
goFunction: goCard
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
highlight: Rectangle { color: "transparent"; border.width: 4; border.color: "#1DB5ED"; z: 1; }
leftMargin: 50; // Start the first item over by about the same amount as the last item peeks through on the other side.
rightMargin: 50;
}
Image { // Just a visual indicator that the user can swipe the cards over to see more.
source: "../images/Swipe-Icon-single.svg"
width: 50;
visible: suggestions.count > 3;
anchors {
right: scroll.right;
verticalCenter: scroll.verticalCenter;
}
}
Image {
id: backgroundImage
source: "../images/address-bar.svg"
@ -97,64 +129,43 @@ Window {
property int inputAreaHeight: 56.0 * root.scale // Height of the background's input area
property int inputAreaStep: (height - inputAreaHeight) / 2
Image {
ToolbarButton {
id: homeButton
source: "../images/home-button.svg"
width: 29
height: 26
imageURL: "../images/home.svg"
buttonState: 1
defaultState: 1
hoverState: 2
onClicked: addressBarDialog.loadHome();
anchors {
left: parent.left
leftMargin: parent.height + 2 * hifi.layout.spacing
leftMargin: homeButton.width / 2
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadHome()
}
}
}
Image {
id: backArrow
source: addressBarDialog.backEnabled ? "../images/left-arrow.svg" : "../images/left-arrow-disabled.svg"
width: 22
height: 26
ToolbarButton {
id: backArrow;
imageURL: "../images/backward.svg";
hoverState: addressBarDialog.backEnabled ? 2 : 0;
defaultState: addressBarDialog.backEnabled ? 1 : 0;
buttonState: addressBarDialog.backEnabled ? 1 : 0;
onClicked: addressBarDialog.loadBack();
anchors {
left: homeButton.right
leftMargin: 2 * hifi.layout.spacing
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadBack()
}
}
}
Image {
id: forwardArrow
source: addressBarDialog.forwardEnabled ? "../images/right-arrow.svg" : "../images/right-arrow-disabled.svg"
width: 22
height: 26
ToolbarButton {
id: forwardArrow;
imageURL: "../images/forward.svg";
hoverState: addressBarDialog.forwardEnabled ? 2 : 0;
defaultState: addressBarDialog.forwardEnabled ? 1 : 0;
buttonState: addressBarDialog.forwardEnabled ? 1 : 0;
onClicked: addressBarDialog.loadForward();
anchors {
left: backArrow.right
leftMargin: 2 * hifi.layout.spacing
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
addressBarDialog.loadForward()
}
}
}
// FIXME replace with TextField
@ -162,20 +173,80 @@ Window {
id: addressLine
focus: true
anchors {
fill: parent
leftMargin: parent.height + parent.height + hifi.layout.spacing * 7
rightMargin: hifi.layout.spacing * 2
top: parent.top
bottom: parent.bottom
left: forwardArrow.right
right: placesButton.left
leftMargin: forwardArrow.width
rightMargin: placesButton.width
topMargin: parent.inputAreaStep + hifi.layout.spacing
bottomMargin: parent.inputAreaStep + hifi.layout.spacing
}
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
helperText: "Go to: place, @user, /path, network address"
helperPixelSize: font.pixelSize * 0.75
helperItalic: true
onTextChanged: filterChoicesByText()
}
// These two are radio buttons.
ToolbarButton {
id: placesButton
imageURL: "../images/places.svg"
buttonState: 1
defaultState: addressBarDialog.useFeed ? 0 : 1;
hoverState: addressBarDialog.useFeed ? 2 : -1;
onClicked: addressBarDialog.useFeed ? toggleFeed() : identity()
anchors {
right: feedButton.left;
bottom: addressLine.bottom;
}
}
ToolbarButton {
id: feedButton;
imageURL: "../images/snap-feed.svg";
buttonState: 0
defaultState: addressBarDialog.useFeed ? 1 : 0;
hoverState: addressBarDialog.useFeed ? -1 : 2;
onClicked: addressBarDialog.useFeed ? identity() : toggleFeed();
anchors {
right: parent.right;
bottom: addressLine.bottom;
rightMargin: feedButton.width / 2
}
}
}
Window {
width: 938;
height: 625;
scale: 0.8 // Reset scale of Window to 1.0 (counteract address bar's scale value of 1.25)
HifiControls.WebView {
anchors.fill: parent;
id: storyCardHTML;
}
id: storyCardFrame;
shown: false;
destroyOnCloseButton: false;
pinnable: false;
anchors {
verticalCenter: backgroundImage.verticalCenter;
horizontalCenter: scroll.horizontalCenter;
}
}
}
function toggleFeed() {
addressBarDialog.useFeed = !addressBarDialog.useFeed;
updateFeedState();
}
function updateFeedState() {
placesButton.buttonState = addressBarDialog.useFeed ? 0 : 1;
feedButton.buttonState = addressBarDialog.useFeed ? 1 : 0;
filterChoicesByText();
}
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
// TODO: make available to other .qml.
var request = new XMLHttpRequest();
@ -200,133 +271,217 @@ Window {
request.open("GET", url, true);
request.send();
}
// call iterator(element, icb) once for each element of array, and then cb(error) when icb(error) has been called by each iterator.
// short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
function asyncEach(array, iterator, cb) {
var count = array.length;
function icb(error) {
if (!--count || error) {
count = -1; // don't cb multiple times (e.g., if error)
cb(error);
}
}
function asyncMap(array, iterator, cb) {
// call iterator(element, icb) once for each element of array, and then cb(error, mappedResult)
// when icb(error, mappedElement) has been called by each iterator.
// Calls to iterator are overlapped and may call icb in any order, but the mappedResults are collected in the same
// order as the elements of the array.
// Short-circuits if error. Note that iterator MUST be an asynchronous function. (Use setTimeout if necessary.)
var count = array.length, results = [];
if (!count) {
return cb();
return cb(null, results);
}
array.forEach(function (element) {
iterator(element, icb);
array.forEach(function (element, index) {
if (count < 0) { // don't keep iterating after we short-circuit
return;
}
iterator(element, function (error, mapped) {
results[index] = mapped;
if (error || !--count) {
count = 0; // don't cb multiple times if error
cb(error, results);
}
});
});
}
// Example:
/*asyncMap([0, 1, 2, 3, 4, 5, 6], function (elt, icb) {
console.log('called', elt);
setTimeout(function () {
console.log('answering', elt);
icb(null, elt);
}, Math.random() * 1000);
}, console.log); */
function identity(x) {
return x;
}
function addPictureToDomain(domainInfo, cb) { // asynchronously add thumbnail and lobby to domainInfo, if available, and cb(error)
// This requests data for all the names at once, and just uses the first one to come back.
// We might change this to check one at a time, which would be less requests and more latency.
asyncEach([domainInfo.name].concat(domainInfo.names || null).filter(identity), function (name, icb) {
var url = "https://metaverse.highfidelity.com/api/v1/places/" + name;
getRequest(url, function (error, json) {
var previews = !error && json.data.place.previews;
if (previews) {
if (!domainInfo.thumbnail) { // just grab the first one
domainInfo.thumbnail = previews.thumbnail;
}
if (!domainInfo.lobby) {
domainInfo.lobby = previews.lobby;
}
}
icb(error);
});
}, cb);
function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey
if (!error && (data.status === 'success')) {
return;
}
if (!error) { // Create a message from the data
error = data.status + ': ' + data.error;
}
if (typeof(error) === 'string') { // Make a proper Error object
error = new Error(error);
}
error.message += ' in ' + url; // Include the url.
cb(error);
return true;
}
function getDomains(options, cb) { // cb(error, arrayOfData)
if (!options.page) {
options.page = 1;
}
if (!domainsBaseUrl) {
var domainsOptions = [
'open', // published hours handle now
'active', // has at least one person connected. FIXME: really want any place that is verified accessible.
// FIXME: really want places I'm allowed in, not just open ones.
'restriction=open', // Not by whitelist, etc. FIXME: If logged in, add hifi to the restriction options, in order to include places that require login.
// FIXME add maturity
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
'sort_by=users',
'sort_order=desc',
];
domainsBaseUrl = "https://metaverse.highfidelity.com/api/v1/domains/all?" + domainsOptions.join('&');
}
var url = domainsBaseUrl + "&page=" + options.page + "&users=" + options.minUsers + "-" + options.maxUsers;
getRequest(url, function (error, json) {
if (!error && (json.status !== 'success')) {
error = new Error("Bad response: " + JSON.stringify(json));
}
if (error) {
error.message += ' for ' + url;
return cb(error);
}
var domains = json.data.domains;
if (json.current_page < json.total_pages) {
options.page++;
return getDomains(options, function (error, others) {
cb(error, domains.concat(others));
});
}
cb(null, domains);
});
}
function filterChoicesByText() {
function fill1(target, data) {
if (!data) {
target.visible = false;
function getPlace(placeData, cb) { // cb(error, side-effected-placeData), after adding path, thumbnails, and description
var url = metaverseBase + 'places/' + placeData.place_name;
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
console.log('suggestion:', JSON.stringify(data));
target.userStory = data;
target.image.source = data.lobby || target.defaultPicture;
target.placeText = data.name;
target.usersText = data.online_users + ((data.online_users === 1) ? ' user' : ' users');
target.visible = true;
}
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity);
var filtered = !words.length ? suggestionChoices : allDomains.filter(function (domain) {
var text = domain.names.concat(domain.tags).join(' ');
if (domain.description) {
text += domain.description;
var place = data.data.place, previews = place.previews;
placeData.path = place.path;
if (previews && previews.thumbnail) {
placeData.thumbnail_url = previews.thumbnail;
}
text = text.toUpperCase();
return words.every(function (word) {
return text.indexOf(word) >= 0;
if (place.description) {
placeData.description = place.description;
placeData.searchText += ' ' + place.description.toUpperCase();
}
cb(error, placeData);
});
}
function makeModelData(data, optionalPlaceName) { // create a new obj from data
// ListModel elements will only ever have those properties that are defined by the first obj that is added.
// So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story.
var name = optionalPlaceName || data.place_name,
tags = data.tags || [data.action, data.username],
description = data.description || "",
thumbnail_url = data.thumbnail_url || "",
image_url = thumbnail_url;
if (data.details) {
try {
image_url = JSON.parse(data.details).image_url || thumbnail_url;
} catch (e) {
console.log(name, "has bad details", data.details);
}
}
return {
place_name: name,
username: data.username || "",
path: data.path || "",
created_at: data.created_at || "",
action: data.action || "",
thumbnail_url: thumbnail_url,
image_url: image_url,
metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
tags: tags,
description: description,
online_users: data.online_users || 0,
searchText: [name].concat(tags, description || []).join(' ').toUpperCase()
}
}
function mapDomainPlaces(domain, cb) { // cb(error, arrayOfDomainPlaceData)
function addPlace(name, icb) {
getPlace(makeModelData(domain, name), icb);
}
// IWBNI we could get these results in order with most-recent-entered first.
// In any case, we don't really need to preserve the domain.names order in the results.
asyncMap(domain.names || [], addPlace, cb);
}
function suggestable(place) {
if (addressBarDialog.useFeed) {
return true;
}
return (place.place_name !== AddressManager.hostname) // Not our entry, but do show other entry points to current domain.
&& place.thumbnail_url
&& place.online_users // at least one present means it's actually online
&& place.online_users <= 20;
}
function getDomainPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
// Each page of results is processed completely before we start on the next page.
// For each page of domains, we process each domain in parallel, and for each domain, process each place name in parallel.
// This gives us minimum latency within the page, but we do preserve the order within the page by using asyncMap and
// only appending the collected results.
var params = [
'open', // published hours handle now
// TBD: should determine if place is actually running?
'restriction=open', // Not by whitelist, etc. TBD: If logged in, add hifi to the restriction options, in order to include places that require login?
// TBD: add maturity?
'protocol=' + encodeURIComponent(AddressManager.protocolVersion()),
'sort_by=users',
'sort_order=desc',
'page=' + pageNumber
];
var url = metaverseBase + 'domains/all?' + params.join('&');
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
asyncMap(data.data.domains, mapDomainPlaces, function (error, pageResults) {
if (error) {
return cb(error);
}
// pageResults is now [ [ placeDataOneForDomainOne, placeDataTwoForDomainOne, ...], [ placeDataTwoForDomainTwo...] ]
pageResults.forEach(function (domainResults) {
allPlaces = allPlaces.concat(domainResults);
if (!addressLine.text && !addressBarDialog.useFeed) { // Don't add if the user is already filtering
domainResults.forEach(function (place) {
if (suggestable(place)) {
suggestions.append(place);
}
});
}
});
if (data.current_page < data.total_pages) {
return getDomainPage(pageNumber + 1, cb);
}
cb();
});
});
fill1(s0, filtered[0]);
fill1(s1, filtered[1]);
fill1(s2, filtered[2]);
}
function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model
var url = metaverseBase + 'user_stories?page=' + pageNumber;
getRequest(url, function (error, data) {
if (handleError(url, error, data, cb)) {
return;
}
var stories = data.user_stories.map(function (story) { // explicit single-argument function
return makeModelData(story);
});
allStories = allStories.concat(stories);
if (!addressLine.text && addressBarDialog.useFeed) { // Don't add if the user is already filtering
stories.forEach(function (story) {
suggestions.append(story);
});
}
if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now
return getUserStoryPage(pageNumber + 1, cb);
}
cb();
});
}
function filterChoicesByText() {
suggestions.clear();
var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity),
data = addressBarDialog.useFeed ? allStories : allPlaces;
function matches(place) {
if (!words.length) {
return suggestable(place);
}
return words.every(function (word) {
return place.searchText.indexOf(word) >= 0;
});
}
data.forEach(function (place) {
if (matches(place)) {
suggestions.append(place);
}
});
}
function fillDestinations() {
allDomains = suggestionChoices = [];
getDomains({minUsers: 0, maxUsers: 20}, function (error, domains) {
if (error) {
console.log('domain query failed:', error);
return filterChoicesByText();
}
var here = AddressManager.hostname; // don't show where we are now.
allDomains = domains.filter(function (domain) { return domain.name !== here; });
// Whittle down suggestions to those that have at least one user, and try to get pictures.
suggestionChoices = allDomains.filter(function (domain) { return domain.online_users; });
asyncEach(domains, addPictureToDomain, function (error) {
if (error) {
console.log('place picture query failed:', error);
}
// Whittle down more by requiring a picture.
suggestionChoices = suggestionChoices.filter(function (domain) { return domain.lobby; });
filterChoicesByText();
});
allPlaces = [];
allStories = [];
suggestions.clear();
getDomainPage(1, function (error) {
console.log('domain query', error || 'ok', allPlaces.length);
});
getUserStoryPage(1, function (error) {
console.log('user stories query', error || 'ok', allStories.length);
});
}

View file

@ -218,16 +218,22 @@ ScrollingWindow {
onIconChanged: {
console.log("New icon: " + icon)
}
onNewViewRequested:{
onNewViewRequested: {
var component = Qt.createComponent("Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
}
}
Component.onCompleted: {
desktop.initWebviewProfileHandlers(webview.profile)
}
//profile: desktop.browserProfile
}
} // item
Keys.onPressed: {
switch(event.key) {
case Qt.Key_L:

View file

@ -0,0 +1,103 @@
//
// MarketplaceComboBox.qml
//
// Created by Elisa Lupin-Jimenez on 3 Aug 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import QtWebEngine 1.1
import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "controls"
import "controls-uit" as Controls
import "styles"
import "styles-uit"
Rectangle {
HifiConstants { id: hifi }
id: marketplaceComboBox
anchors.fill: parent
color: hifi.colors.baseGrayShadow
property var currentUrl: "https://metaverse.highfidelity.com/marketplace"
Controls.WebView {
id: webview
url: currentUrl
anchors.top: switchMarketView.bottom
width: parent.width
height: parent.height - 40
focus: true
Timer {
id: zipTimer
running: false
repeat: false
interval: 1500
property var handler;
onTriggered: handler();
}
property var autoCancel: 'var element = $("a.btn.cancel");
element.click();'
onNewViewRequested: {
var component = Qt.createComponent("Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView);
if (File.isZippedFbx(desktop.currentUrl)) {
zipTimer.handler = function() {
newWindow.destroy();
runJavaScript(autoCancel);
}
zipTimer.start();
}
}
property var simpleDownload: 'var element = $("a.download-file");
element.removeClass("download-file");
element.removeAttr("download");'
onLinkHovered: {
desktop.currentUrl = hoveredUrl;
// add an error message for non-fbx files
if (File.isZippedFbx(desktop.currentUrl)) {
runJavaScript(simpleDownload, function(){console.log("ran the JS");});
}
}
}
Controls.ComboBox {
id: switchMarketView
anchors.top: parent.top
anchors.right: parent.right
colorScheme: hifi.colorSchemes.dark
width: 200
height: 40
visible: true
model: ["Marketplace", "Clara.io"]
onCurrentIndexChanged: {
if (currentIndex === 0) { webview.url = "https://metaverse.highfidelity.com/marketplace"; }
if (currentIndex === 1) { webview.url = "https://clara.io/library"; }
}
}
Controls.Label {
id: switchMarketLabel
anchors.verticalCenter: switchMarketView.verticalCenter
anchors.right: switchMarketView.left
color: hifi.colors.white
text: "Explore interesting content from: "
}
}

View file

@ -1,13 +1,16 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Controls 1.4
import QtWebChannel 1.0
import QtWebEngine 1.1
import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "windows" as Windows
import "controls"
import "controls-uit" as Controls
import "styles"
import "styles-uit"
Windows.Window {
id: root
@ -23,6 +26,8 @@ Windows.Window {
property var eventBridge;
property var component;
property var dynamicContent;
onSourceChanged: {
if (dynamicContent) {
dynamicContent.destroy();

View file

@ -1,10 +0,0 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
WebEngineView {
id: root
anchors.fill: parent
objectName: "webview"
url: "about:blank"
}

View file

@ -21,6 +21,8 @@ Original.Button {
width: 120
height: hifi.dimensions.controlLineHeight
HifiConstants { id: hifi }
style: ButtonStyle {
background: Rectangle {

View file

@ -18,6 +18,7 @@ import "." as VrControls
FocusScope {
id: root
HifiConstants { id: hifi }
property alias model: comboBox.model;
property alias comboBox: comboBox

View file

@ -13,6 +13,7 @@ import QtQuick 2.5
import "../styles-uit"
RalewaySemiBold {
HifiConstants { id: hifi }
property int colorScheme: hifi.colorSchemes.light
size: hifi.fontSizes.inputLabel

View file

@ -25,6 +25,8 @@ WebEngineView {
});
}
// FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6
Timer {
id: urlReplacementTimer
@ -59,11 +61,6 @@ WebEngineView {
}
}
onNewViewRequested:{
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
// See https://bugreports.qt.io/browse/QTBUG-49521

View file

@ -12,6 +12,8 @@ Original.TextInput {
verticalAlignment: Original.TextInput.AlignVCenter
font.family: hifi.fonts.fontFamily
font.pixelSize: hifi.fonts.pixelSize
property int helperPixelSize: font.pixelSize
property bool helperItalic: false
/*
Original.Rectangle {
@ -23,7 +25,8 @@ Original.TextInput {
*/
Text {
anchors.fill: parent
font.pixelSize: parent.font.pixelSize
font.pixelSize: helperPixelSize
font.italic: helperItalic
font.family: parent.font.family
verticalAlignment: parent.verticalAlignment
horizontalAlignment: parent.horizontalAlignment

View file

@ -55,9 +55,11 @@ WebEngineView {
}
onNewViewRequested:{
if (desktop) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
request.openIn(newWindow.webView);
}
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6

View file

@ -17,28 +17,69 @@ import QtGraphicalEffects 1.0
import "../styles-uit"
Rectangle {
property string userName: "";
property string placeName: "";
property string action: "";
property string timestamp: "";
property string hifiUrl: "";
property string thumbnail: defaultThumbnail;
property string imageUrl: "";
property var goFunction: null;
property var userStory: null;
property alias image: lobby;
property alias placeText: place.text;
property alias usersText: users.text;
property string storyId: "";
property string timePhrase: pastTime(timestamp);
property string actionPhrase: makeActionPhrase(action);
property int onlineUsers: 0;
property bool isUserStory: userName && !onlineUsers;
property int textPadding: 20;
property int textSize: 24;
property string defaultPicture: "../../images/default-domain.gif";
property int textSizeSmall: 18;
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
HifiConstants { id: hifi }
function pastTime(timestamp) { // Answer a descriptive string
timestamp = new Date(timestamp);
var then = timestamp.getTime(),
now = Date.now(),
since = now - then,
ONE_MINUTE = 1000 * 60,
ONE_HOUR = ONE_MINUTE * 60,
hours = since / ONE_HOUR,
minutes = (hours % 1) * 60;
if (hours > 24) {
return timestamp.toDateString();
}
if (hours > 1) {
return Math.floor(hours).toString() + ' hr ' + Math.floor(minutes) + ' min ago';
}
if (minutes >= 2) {
return Math.floor(minutes).toString() + ' min ago';
}
return 'about a minute ago';
}
function makeActionPhrase(actionLabel) {
switch (actionLabel) {
case "snapshot":
return "took a snapshot";
default:
return "unknown"
}
}
Image {
id: lobby;
width: parent.width;
height: parent.height;
source: defaultPicture;
source: thumbnail || defaultThumbnail;
fillMode: Image.PreserveAspectCrop;
// source gets filled in later
anchors.verticalCenter: parent.verticalCenter;
anchors.left: parent.left;
onStatusChanged: {
if (status == Image.Error) {
console.log("source: " + source + ": failed to load " + JSON.stringify(userStory));
source = defaultPicture;
console.log("source: " + source + ": failed to load " + hifiUrl);
source = defaultThumbnail;
}
}
}
@ -69,6 +110,7 @@ Rectangle {
}
RalewaySemiBold {
id: place;
text: isUserStory ? "" : placeName;
color: hifi.colors.white;
size: textSize;
anchors {
@ -79,7 +121,8 @@ Rectangle {
}
RalewayRegular {
id: users;
size: textSize;
text: isUserStory ? timePhrase : (onlineUsers + ((onlineUsers === 1) ? ' person' : ' people'));
size: textSizeSmall;
color: hifi.colors.white;
anchors {
bottom: parent.bottom;
@ -87,10 +130,18 @@ Rectangle {
margins: textPadding;
}
}
// These two can be supplied to provide hover behavior.
// For example, AddressBarDialog provides functions that set the current list view item
// to that which is being hovered over.
property var hoverThunk: function () { };
property var unhoverThunk: function () { };
MouseArea {
id: zmouseArea;
anchors.fill: parent;
acceptedButtons: Qt.LeftButton;
onClicked: goFunction(parent);
hoverEnabled: true;
onEntered: hoverThunk();
onExited: unhoverThunk();
}
}

View file

@ -20,6 +20,8 @@ OriginalDesktop.Desktop {
onEntered: ApplicationCompositor.reticleOverDesktop = true
onExited: ApplicationCompositor.reticleOverDesktop = false
acceptedButtons: Qt.NoButton
}
// The tool window, one instance
@ -71,6 +73,39 @@ OriginalDesktop.Desktop {
});
}
// Accept a download through the webview
property bool webViewProfileSetup: false
property string currentUrl: ""
property string adaptedPath: ""
property string tempDir: ""
function initWebviewProfileHandlers(profile) {
console.log("The webview url in desktop is: " + currentUrl);
if (webViewProfileSetup) return;
webViewProfileSetup = true;
profile.downloadRequested.connect(function(download){
console.log("Download start: " + download.state);
adaptedPath = File.convertUrlToPath(currentUrl);
tempDir = File.getTempDir();
console.log("Temp dir created: " + tempDir);
download.path = tempDir + "/" + adaptedPath;
console.log("Path where object should download: " + download.path);
download.accept();
if (download.state === WebEngineDownloadItem.DownloadInterrupted) {
console.log("download failed to complete");
}
})
profile.downloadFinished.connect(function(download){
if (download.state === WebEngineDownloadItem.DownloadCompleted) {
File.runUnzip(download.path, currentUrl);
} else {
console.log("The download was corrupted, state: " + download.state);
}
})
}
// Create or fetch a toolbar with the given name
function getToolbar(name) {
var result = toolbars[name];

View file

@ -57,6 +57,7 @@
#include <display-plugins/DisplayPlugin.h>
#include <EntityScriptingInterface.h>
#include <ErrorDialog.h>
#include <FileScriptingInterface.h>
#include <Finally.h>
#include <FramebufferCache.h>
#include <gpu/Batch.h>
@ -1067,70 +1068,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
[this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) {
if (_keyboardFocusedItem != entityItemID) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
if (EntityTypes::Web == properties.getType() && !properties.getLocked() && properties.getVisible()) {
auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID);
RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get());
if (webEntity) {
webEntity->setProxyWindow(_window->windowHandle());
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->pluginFocusOutEvent();
}
_keyboardFocusedItem = entityItemID;
_lastAcceptedKeyPress = usecTimestampNow();
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
_keyboardFocusHighlight = new Cube3DOverlay();
_keyboardFocusHighlight->setAlpha(1.0f);
_keyboardFocusHighlight->setBorderSize(1.0f);
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
_keyboardFocusHighlight->setIsSolid(false);
_keyboardFocusHighlight->setPulseMin(0.5);
_keyboardFocusHighlight->setPulseMax(1.0);
_keyboardFocusHighlight->setColorPulse(1.0);
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
_keyboardFocusHighlight->setDrawInFront(true);
}
_keyboardFocusHighlight->setRotation(webEntity->getRotation());
_keyboardFocusHighlight->setPosition(webEntity->getPosition());
_keyboardFocusHighlight->setDimensions(webEntity->getDimensions() * 1.05f);
_keyboardFocusHighlight->setVisible(true);
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
}
}
if (_keyboardFocusedItem == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
}
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
setKeyboardFocusEntity(entityItemID);
});
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity,
[=](const EntityItemID& entityItemID) {
if (entityItemID == _keyboardFocusedItem) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
if (entityItemID == _keyboardFocusedItem.get()) {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
}
});
// If the user clicks somewhere where there is NO entity at all, we will release focus
connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity,
[=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=]() {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
});
connect(this, &Application::aboutToQuit, [=]() {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
});
// Add periodic checks to send user activity data
@ -1403,11 +1357,13 @@ void Application::cleanupBeforeQuit() {
// FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete:
_applicationStateDevice.reset();
if (_keyboardFocusHighlightID > 0) {
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
_keyboardFocusHighlightID = -1;
{
if (_keyboardFocusHighlightID > 0) {
getOverlays().deleteOverlay(_keyboardFocusHighlightID);
_keyboardFocusHighlightID = -1;
}
_keyboardFocusHighlight = nullptr;
}
_keyboardFocusHighlight = nullptr;
auto nodeList = DependencyManager::get<NodeList>();
@ -1623,6 +1579,9 @@ void Application::initializeUi() {
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
FileScriptingInterface* fileDownload = new FileScriptingInterface(engine);
rootContext->setContextProperty("File", fileDownload);
connect(fileDownload, &FileScriptingInterface::unzipSuccess, this, &Application::showAssetServerWidget);
rootContext->setContextProperty("MyAvatar", getMyAvatar());
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
@ -2069,7 +2028,6 @@ bool Application::importJSONFromURL(const QString& urlString) {
}
bool Application::importSVOFromURL(const QString& urlString) {
emit svoImportRequested(urlString);
return true;
}
@ -2122,16 +2080,16 @@ bool Application::event(QEvent* event) {
return true;
}
if (!_keyboardFocusedItem.isInvalidID()) {
switch (event->type()) {
{
if (!_keyboardFocusedItem.get().isInvalidID()) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(_keyboardFocusedItem);
RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get());
if (webEntity && webEntity->getEventHandler()) {
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
if (entity && entity->getEventHandler()) {
event->setAccepted(false);
QCoreApplication::sendEvent(webEntity->getEventHandler(), event);
QCoreApplication::sendEvent(entity->getEventHandler(), event);
if (event->isAccepted()) {
_lastAcceptedKeyPress = usecTimestampNow();
return true;
@ -2142,6 +2100,7 @@ bool Application::event(QEvent* event) {
default:
break;
}
}
}
@ -2193,13 +2152,15 @@ bool Application::event(QEvent* event) {
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
QUrl url = fileEvent->url();
if (!url.isEmpty()) {
QString urlString = url.toString();
if (canAcceptURL(urlString)) {
return acceptURL(urlString);
}
}
@ -2340,7 +2301,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (isOption && !isShifted && !isMeta) {
Menu::getInstance()->triggerOption(MenuOption::ScriptEditor);
} else if (!isOption && !isShifted && isMeta) {
takeSnapshot();
takeSnapshot(true);
}
break;
@ -2691,7 +2652,10 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
event->screenPos(), button,
buttons, event->modifiers());
getEntities()->mouseMoveEvent(&mappedEvent);
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y()))) {
getEntities()->mouseMoveEvent(&mappedEvent);
}
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -2981,14 +2945,6 @@ void Application::idle(float nsecsElapsed) {
_simCounter.increment();
PerformanceTimer perfTimer("idle");
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
if (!_keyboardFocusedItem.isInvalidID()) {
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
_keyboardFocusedItem = UNKNOWN_ENTITY_ID;
}
}
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
@ -3002,6 +2958,27 @@ void Application::idle(float nsecsElapsed) {
static const float BIGGEST_DELTA_TIME_SECS = 0.25f;
update(glm::clamp(secondsSinceLastUpdate, 0.0f, BIGGEST_DELTA_TIME_SECS));
}
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
{
if (!_keyboardFocusedItem.get().isInvalidID()) {
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
} else {
// update position of highlight overlay
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
if (entity && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setRotation(entity->getRotation());
_keyboardFocusHighlight->setPosition(entity->getPosition());
}
}
}
}
{
PerformanceTimer perfTimer("pluginIdle");
PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()");
@ -3586,6 +3563,54 @@ void Application::rotationModeChanged() const {
}
}
QUuid Application::getKeyboardFocusEntity() const {
return _keyboardFocusedItem.get();
}
void Application::setKeyboardFocusEntity(QUuid id) {
EntityItemID entityItemID(id);
setKeyboardFocusEntity(entityItemID);
}
void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (_keyboardFocusedItem.get() != entityItemID) {
_keyboardFocusedItem.set(UNKNOWN_ENTITY_ID);
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
if (!properties.getLocked() && properties.getVisible()) {
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
if (entity && entity->wantsKeyboardFocus()) {
entity->setProxyWindow(_window->windowHandle());
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->pluginFocusOutEvent();
}
_keyboardFocusedItem.set(entityItemID);
_lastAcceptedKeyPress = usecTimestampNow();
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
_keyboardFocusHighlight = new Cube3DOverlay();
_keyboardFocusHighlight->setAlpha(1.0f);
_keyboardFocusHighlight->setBorderSize(1.0f);
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
_keyboardFocusHighlight->setIsSolid(false);
_keyboardFocusHighlight->setPulseMin(0.5);
_keyboardFocusHighlight->setPulseMax(1.0);
_keyboardFocusHighlight->setColorPulse(1.0);
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
_keyboardFocusHighlight->setDrawInFront(false);
}
_keyboardFocusHighlight->setRotation(entity->getRotation());
_keyboardFocusHighlight->setPosition(entity->getPosition());
_keyboardFocusHighlight->setDimensions(entity->getDimensions() * 1.05f);
_keyboardFocusHighlight->setVisible(true);
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
}
}
if (_keyboardFocusedItem.get() == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
}
}
void Application::updateDialogs(float deltaTime) const {
PerformanceTimer perfTimer("updateDialogs");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
@ -4306,7 +4331,7 @@ namespace render {
}
case model::SunSkyStage::SKY_BOX: {
auto skybox = skyStage->getSkybox();
if (skybox) {
if (!skybox->empty()) {
PerformanceTimer perfTimer("skybox");
skybox->render(batch, args->getViewFrustum());
break;
@ -4893,6 +4918,7 @@ bool Application::canAcceptURL(const QString& urlString) const {
bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
if (urlString.startsWith(HIFI_URL_SCHEME)) {
// this is a hifi URL - have the AddressManager handle it
emit receivedHifiSchemeURL(urlString);
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
Qt::AutoConnection, Q_ARG(const QString&, urlString));
return true;
@ -4941,7 +4967,7 @@ bool Application::askToSetAvatarUrl(const QString& url) {
modelLicense = simpleWordWrap(modelLicense, MAX_CHARACTERS_PER_LINE);
agreeToLicence = QMessageBox::Yes == OffscreenUi::question("Avatar Usage License",
modelLicense + "\nDo you argee to these terms?",
modelLicense + "\nDo you agree to these terms?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
}
@ -5000,7 +5026,6 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
}
bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@ -5098,11 +5123,11 @@ void Application::toggleRunningScriptsWidget() const {
//}
}
void Application::showAssetServerWidget(QString filePath) {
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
return;
}
static const QUrl url { "AssetServer.qml" };
auto startUpload = [=](QQmlContext* context, QObject* newObject){
@ -5169,15 +5194,24 @@ void Application::toggleLogDialog() {
}
}
void Application::takeSnapshot() {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
void Application::takeSnapshot(bool notify, float aspectRatio) {
postLambdaEvent([notify, aspectRatio, this] {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav");
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot());
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path);
emit DependencyManager::get<WindowScriptingInterface>()->snapshotTaken(path, notify);
});
}
void Application::shareSnapshot(const QString& path) {
postLambdaEvent([path] {
// not much to do here, everything is done in snapshot code...
Snapshot::uploadSnapshot(path);
});
}
float Application::getRenderResolutionScale() const {
@ -5642,3 +5676,48 @@ bool Application::makeRenderingContextCurrent() {
bool Application::isForeground() const {
return _isForeground && !_window->isMinimized();
}
void Application::sendMousePressOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->mousePressOnEntity(entityItemID, event);
}
void Application::sendMouseMoveOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->mouseMoveOnEntity(entityItemID, event);
}
void Application::sendMouseReleaseOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->mouseReleaseOnEntity(entityItemID, event);
}
void Application::sendClickDownOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->clickDownOnEntity(entityItemID, event);
}
void Application::sendHoldingClickOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->holdingClickOnEntity(entityItemID, event);
}
void Application::sendClickReleaseOnEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->clickReleaseOnEntity(entityItemID, event);
}
void Application::sendHoverEnterEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->hoverEnterEntity(entityItemID, event);
}
void Application::sendHoverOverEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->hoverOverEntity(entityItemID, event);
}
void Application::sendHoverLeaveEntity(QUuid id, PointerEvent event) {
EntityItemID entityItemID(id);
emit getEntities()->hoverLeaveEntity(entityItemID, event);
}

View file

@ -43,6 +43,7 @@
#include <ViewFrustum.h>
#include <AbstractUriHandler.h>
#include <shared/RateCounter.h>
#include <ThreadSafeValueCache.h>
#include "avatar/MyAvatar.h"
#include "Bookmarks.h"
@ -249,11 +250,26 @@ public:
float getAvatarSimrate() const { return _avatarSimCounter.rate(); }
float getAverageSimsPerSecond() const { return _simCounter.rate(); }
void takeSnapshot(bool notify, float aspectRatio = 0.0f);
void shareSnapshot(const QString& filename);
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; }
Q_INVOKABLE void sendMousePressOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendMouseMoveOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendMouseReleaseOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendClickDownOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoldingClickOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendClickReleaseOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverEnterEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverOverEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverLeaveEntity(QUuid id, PointerEvent event);
signals:
void svoImportRequested(const QString& url);
@ -263,6 +279,7 @@ signals:
void activeDisplayPluginChanged();
void uploadRequest(QString path);
void receivedHifiSchemeURL(const QString& url);
public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z);
@ -319,6 +336,10 @@ public slots:
static void runTests();
QUuid getKeyboardFocusEntity() const; // thread-safe
void setKeyboardFocusEntity(QUuid id);
void setKeyboardFocusEntity(EntityItemID entityItemID);
private slots:
void showDesktop();
void clearDomainOctreeDetails();
@ -379,8 +400,6 @@ private:
int sendNackPackets();
void takeSnapshot();
MyAvatar* getMyAvatar() const;
void checkSkeleton() const;
@ -531,7 +550,7 @@ private:
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
EntityItemID _keyboardFocusedItem;
ThreadSafeValueCache<EntityItemID> _keyboardFocusedItem;
quint64 _lastAcceptedKeyPress = 0;
bool _isForeground = true; // starts out assumed to be in foreground
bool _inPaint = false;

View file

@ -27,10 +27,6 @@ void ConnectionMonitor::init() {
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::disconnectedFromDomain);
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::connectedToDomain);
// Connect to AddressManager::hostChanged
auto addressManager = DependencyManager::get<AddressManager>();
connect(addressManager.data(), &AddressManager::hostChanged, this, &ConnectionMonitor::hostChanged);
_timer.setSingleShot(true);
_timer.setInterval(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
_timer.start();
@ -46,7 +42,3 @@ void ConnectionMonitor::disconnectedFromDomain() {
void ConnectionMonitor::connectedToDomain(const QString& name) {
_timer.stop();
}
void ConnectionMonitor::hostChanged(const QString& name) {
_timer.start();
}

View file

@ -25,7 +25,6 @@ public:
private slots:
void disconnectedFromDomain();
void connectedToDomain(const QString& name);
void hostChanged(const QString& name);
private:
QTimer _timer;

View file

@ -59,6 +59,8 @@ const float DISPLAYNAME_ALPHA = 1.0f;
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534;
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
return ItemKey::Builder::opaqueShape();
@ -851,15 +853,33 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
}
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return glmExtractRotation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::quat rotation;
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
return Quaternions::Y_180 * rotation;
}
}
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
bool success;
Transform avatarTransform;
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
return extractTranslation(invAvatarMat * sensorToWorldMatrix);
} else {
glm::vec3 translation;
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
return Quaternions::Y_180 * translation;
}
}
int Avatar::getJointIndex(const QString& name) const {

View file

@ -107,7 +107,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdSensorOrientation(),
_hmdSensorPosition(),
_bodySensorMatrix(),
_sensorToWorldMatrix(),
_goToPending(false),
_goToPosition(),
_goToOrientation(),
@ -511,13 +510,9 @@ void MyAvatar::simulate(float deltaTime) {
updateAvatarEntities();
}
// thread-safe
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
// As far as I know no HMD system supports a play area of a kilometer in radius.
// As far as I know no HMD system supports a play area of a kilometer in radius.
static const float MAX_HMD_ORIGIN_DISTANCE = 1000.0f;
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
@ -526,7 +521,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorMatrix = hmdSensorMatrix;
auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
if (newHmdSensorPosition != _hmdSensorPosition &&
if (newHmdSensorPosition != _hmdSensorPosition &&
glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
// Ignore unreasonable HMD sensor data

View file

@ -79,8 +79,6 @@ class MyAvatar : public Avatar {
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
@ -110,9 +108,6 @@ public:
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
Q_INVOKABLE QVariant getOrientationVar() const;
@ -415,6 +410,10 @@ private:
bool _useSnapTurn { true };
bool _clearOverlayWhenMoving { true };
// working copy of sensorToWorldMatrix.
// See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix;
// cache of the current HMD sensor position and orientation
// in sensor space.
glm::mat4 _hmdSensorMatrix;
@ -427,10 +426,6 @@ private:
// in sensor space.
glm::mat4 _bodySensorMatrix;
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
struct FollowHelper {
FollowHelper();

View file

@ -26,6 +26,11 @@ bool AccountScriptingInterface::isLoggedIn() {
return accountManager->isLoggedIn();
}
bool AccountScriptingInterface::checkAndSignalForAccessToken() {
auto accountManager = DependencyManager::get<AccountManager>();
return accountManager->checkAndSignalForAccessToken();
}
QString AccountScriptingInterface::getUsername() {
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {

View file

@ -26,6 +26,7 @@ public slots:
static AccountScriptingInterface* getInstance();
QString getUsername();
bool isLoggedIn();
bool checkAndSignalForAccessToken();
};
#endif // hifi_AccountScriptingInterface_h

View file

@ -17,6 +17,8 @@
#include "Application.h"
#include "MainWindow.h"
#include <display-plugins/CompositorHelper.h>
#include <DependencyManager.h>
#include <OffscreenUi.h>
int DesktopScriptingInterface::getWidth() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
@ -31,3 +33,11 @@ void DesktopScriptingInterface::setOverlayAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}
void DesktopScriptingInterface::show(const QString& path, const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection, Q_ARG(QString, path), Q_ARG(QString, title));
return;
}
DependencyManager::get<OffscreenUi>()->show(path, title);
}

View file

@ -23,6 +23,7 @@ class DesktopScriptingInterface : public QObject, public Dependency {
public:
Q_INVOKABLE void setOverlayAlpha(float alpha);
Q_INVOKABLE void show(const QString& path, const QString& title);
int getWidth();
int getHeight();

View file

@ -26,3 +26,8 @@ void DialogsManagerScriptingInterface::toggleAddressBar() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"toggleAddressBar", Qt::QueuedConnection);
}
void DialogsManagerScriptingInterface::showFeed() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"showFeed", Qt::QueuedConnection);
}

View file

@ -18,6 +18,7 @@ class DialogsManagerScriptingInterface : public QObject {
Q_OBJECT
public:
DialogsManagerScriptingInterface();
Q_INVOKABLE void showFeed();
public slots:
void toggleAddressBar();

View file

@ -96,7 +96,7 @@ void WindowScriptingInterface::alert(const QString& message) {
/// \param const QString& message message to display
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message)));
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No)));
}
/// Display a prompt with a text box
@ -203,3 +203,11 @@ void WindowScriptingInterface::copyToClipboard(const QString& text) {
qDebug() << "Copying";
QApplication::clipboard()->setText(text);
}
void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) {
qApp->takeSnapshot(notify, aspectRatio);
}
void WindowScriptingInterface::shareSnapshot(const QString& path) {
qApp->shareSnapshot(path);
}

View file

@ -55,12 +55,15 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void showAssetServer(const QString& upload = "");
void copyToClipboard(const QString& text);
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
void shareSnapshot(const QString& path);
signals:
void domainChanged(const QString& domainHostname);
void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reasonMessage, int reasonCode);
void snapshotTaken(const QString& path);
void snapshotTaken(const QString& path, bool notify);
void snapshotShared(const QString& error);
private slots:
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);

View file

@ -38,6 +38,8 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
});
_backEnabled = !(DependencyManager::get<AddressManager>()->getBackStack().isEmpty());
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed);
connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL);
}
void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) {

View file

@ -14,21 +14,29 @@
#define hifi_AddressBarDialog_h
#include <OffscreenQmlDialog.h>
#include <NetworkingConstants.h>
class AddressBarDialog : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL
Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged)
Q_PROPERTY(bool forwardEnabled READ forwardEnabled NOTIFY forwardEnabledChanged)
Q_PROPERTY(bool useFeed READ useFeed WRITE setUseFeed NOTIFY useFeedChanged)
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl)
public:
AddressBarDialog(QQuickItem* parent = nullptr);
bool backEnabled() { return _backEnabled; }
bool forwardEnabled() { return _forwardEnabled; }
bool useFeed() { return _useFeed; }
void setUseFeed(bool useFeed) { if (_useFeed != useFeed) { _useFeed = useFeed; emit useFeedChanged(); } }
QString metaverseServerUrl() { return NetworkingConstants::METAVERSE_SERVER_URL.toString(); }
signals:
void backEnabledChanged();
void forwardEnabledChanged();
void useFeedChanged();
void receivedHifiSchemeURL(const QString& url);
protected:
void displayAddressOfflineMessage();
@ -42,6 +50,7 @@ protected:
bool _backEnabled;
bool _forwardEnabled;
bool _useFeed { false };
};
#endif

View file

@ -54,6 +54,11 @@ void DialogsManager::showAddressBar() {
AddressBarDialog::show();
}
void DialogsManager::showFeed() {
AddressBarDialog::show();
emit setUseFeed(true);
}
void DialogsManager::toggleDiskCacheEditor() {
maybeCreateDialog(_diskCacheEditor);
_diskCacheEditor->toggle();

View file

@ -45,6 +45,7 @@ public:
public slots:
void toggleAddressBar();
void showAddressBar();
void showFeed();
void toggleDiskCacheEditor();
void toggleLoginDialog();
void showLoginDialog();
@ -63,6 +64,7 @@ public slots:
signals:
void addressBarToggled();
void addressBarShown(bool visible);
void setUseFeed(bool useFeed);
private slots:
void hmdToolsClosed();

View file

@ -32,6 +32,7 @@
#include "Application.h"
#include "Snapshot.h"
#include "SnapshotUploader.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
@ -141,3 +142,34 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) {
return imageTempFile;
}
}
void Snapshot::uploadSnapshot(const QString& filename) {
const QString SNAPSHOT_UPLOAD_URL = "/api/v1/snapshots";
static SnapshotUploader uploader;
QFile* file = new QFile(filename);
Q_ASSERT(file->exists());
file->open(QIODevice::ReadOnly);
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"image\"; filename=\"" + file->fileName() + "\""));
imagePart.setBodyDevice(file);
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
multiPart->append(imagePart);
auto accountManager = DependencyManager::get<AccountManager>();
JSONCallbackParameters callbackParams(&uploader, "uploadSuccess", &uploader, "uploadFailure");
accountManager->sendRequest(SNAPSHOT_UPLOAD_URL,
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation,
callbackParams,
nullptr,
multiPart);
}

View file

@ -40,6 +40,7 @@ public:
static Setting::Handle<QString> snapshotsLocation;
static Setting::Handle<bool> hasSetSnapshotsLocation;
static void uploadSnapshot(const QString& filename);
private:
static QFile* savedFileForSnapshot(QImage & image, bool isTemporary);
};

View file

@ -0,0 +1,75 @@
//
// SnapshotUploader.cpp
// interface/src/ui
//
// Created by Howard Stearns on 8/22/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 <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <AddressManager.h>
#include "scripting/WindowScriptingInterface.h"
#include "SnapshotUploader.h"
void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
const QString STORY_UPLOAD_URL = "/api/v1/user_stories";
static SnapshotUploader uploader;
// parse the reply for the thumbnail_url
QByteArray contents = reply.readAll();
QJsonParseError jsonError;
auto doc = QJsonDocument::fromJson(contents, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
auto dataObject = doc.object().value("data").toObject();
QString thumbnailUrl = dataObject.value("thumbnail_url").toString();
QString imageUrl = dataObject.value("image_url").toString();
auto addressManager = DependencyManager::get<AddressManager>();
QString placeName = addressManager->getPlaceName();
if (placeName.isEmpty()) {
placeName = addressManager->getHost();
}
QString currentPath = addressManager->currentPath(true);
// create json post data
QJsonObject rootObject;
QJsonObject userStoryObject;
QJsonObject detailsObject;
detailsObject.insert("image_url", imageUrl);
QString pickledDetails = QJsonDocument(detailsObject).toJson();
userStoryObject.insert("details", pickledDetails);
userStoryObject.insert("thumbnail_url", thumbnailUrl);
userStoryObject.insert("place_name", placeName);
userStoryObject.insert("path", currentPath);
userStoryObject.insert("action", "snapshot");
rootObject.insert("user_story", userStoryObject);
auto accountManager = DependencyManager::get<AccountManager>();
JSONCallbackParameters callbackParams(&uploader, "createStorySuccess", &uploader, "createStoryFailure");
accountManager->sendRequest(STORY_UPLOAD_URL,
AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation,
callbackParams,
QJsonDocument(rootObject).toJson());
}
else {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(contents);
}
}
void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(reply.readAll());
}
void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(QString());
}
void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(reply.readAll());
}

View file

@ -0,0 +1,26 @@
//
// SnapshotUploader.h
// interface/src/ui
//
// Created by Howard Stearns on 8/22/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
//
#ifndef hifi_SnapshotUploader_h
#define hifi_SnapshotUploader_h
#include <QObject>
#include <QtNetwork/QNetworkReply>
class SnapshotUploader : public QObject {
Q_OBJECT
public slots:
void uploadSuccess(QNetworkReply& reply);
void uploadFailure(QNetworkReply& reply);
void createStorySuccess(QNetworkReply& reply);
void createStoryFailure(QNetworkReply& reply);
};
#endif // hifi_SnapshotUploader_h

View file

@ -74,8 +74,8 @@ void Web3DOverlay::render(RenderArgs* args) {
if (!_webSurface) {
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
_webSurface->load("WebView.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->resize(QSize(_resolution.x, _resolution.y));

View file

@ -53,15 +53,18 @@ namespace AvatarDataPacket {
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
PACKED_BEGIN struct Header {
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
float position[3]; // skeletal model's position
float globalPosition[3]; // avatar's position
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
float lookAtPosition[3]; // world space position that eyes are focusing on.
float audioLoudness; // current loundess of microphone
uint8_t sensorToWorldQuat[6]; // 6 byte compressed quaternion part of sensor to world matrix
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
uint8_t flags;
} PACKED_END;
const size_t HEADER_SIZE = 49;
const size_t HEADER_SIZE = 69;
// only present if HAS_REFERENTIAL flag is set in header.flags
PACKED_BEGIN struct ParentInfo {
@ -93,6 +96,9 @@ namespace AvatarDataPacket {
*/
}
static const int TRANSLATION_COMPRESSION_RADIX = 12;
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
AvatarData::AvatarData() :
@ -210,6 +216,14 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
header->lookAtPosition[2] = _headData->_lookAtPosition.z;
header->audioLoudness = _headData->_audioLoudness;
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
glm::vec3 scale = extractScale(sensorToWorldMatrix);
packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState);
// hand state
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
@ -346,8 +360,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
*destinationBuffer++ = validity;
}
const int TRANSLATION_COMPRESSION_RADIX = 12;
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i ++) {
@ -500,6 +512,15 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
_headData->_audioLoudness = audioLoudness;
glm::quat sensorToWorldQuat;
unpackOrientationQuatFromSixBytes(header->sensorToWorldQuat, sensorToWorldQuat);
float sensorToWorldScale;
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&header->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
glm::vec3 sensorToWorldTrans(header->sensorToWorldTrans[0], header->sensorToWorldTrans[1], header->sensorToWorldTrans[2]);
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
{ // bitFlags and face data
uint8_t bitItems = header->flags;
@ -616,7 +637,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// each joint translation component is stored in 6 bytes.
const int COMPRESSED_TRANSLATION_SIZE = 6;
PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE);
const int TRANSLATION_COMPRESSION_RADIX = 12;
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
@ -1718,6 +1738,11 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
return result;
}
// thread-safe
glm::mat4 AvatarData::getSensorToWorldMatrix() const {
return _sensorToWorldMatrixCache.get();
}
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);

View file

@ -54,6 +54,7 @@ typedef unsigned long long quint64;
#include <SpatiallyNestable.h>
#include <NumericalConstants.h>
#include <Packed.h>
#include <ThreadSafeValueCache.h>
#include "AABox.h"
#include "HeadData.h"
@ -171,6 +172,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
public:
static const QString FRAME_NAME;
@ -351,6 +354,9 @@ public:
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();
@ -425,6 +431,9 @@ protected:
bool _avatarEntityDataLocallyEdited { false };
bool _avatarEntityDataChanged { false };
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
static QUrl _defaultFullAvatarModelUrl;

View file

@ -31,6 +31,6 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
}
}
QImage NullDisplayPlugin::getScreenshot() const {
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
return QImage();
}

View file

@ -18,7 +18,7 @@ public:
glm::uvec2 getRecommendedRenderSize() const override;
bool hasFocus() const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
QImage getScreenshot() const override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
private:
static const QString NAME;
};

View file

@ -659,12 +659,26 @@ void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const {
_container->makeRenderingContextCurrent();
}
QImage OpenGLDisplayPlugin::getScreenshot() const {
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
auto size = _compositeFramebuffer->getSize();
if (isHmd()) {
size.x /= 2;
}
auto bestSize = size;
uvec2 corner(0);
if (aspectRatio != 0.0f) { // Pick out the largest piece of the center that produces the requested width/height aspectRatio
if (ceil(size.y * aspectRatio) < size.x) {
bestSize.x = round(size.y * aspectRatio);
} else {
bestSize.y = round(size.x / aspectRatio);
}
corner.x = round((size.x - bestSize.x) / 2.0f);
corner.y = round((size.y - bestSize.y) / 2.0f);
}
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
QImage screenshot(size.x, size.y, QImage::Format_ARGB32);
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
withMainThreadContext([&] {
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(uvec2(0), size), screenshot);
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
});
return screenshot.mirrored(false, true);
}

View file

@ -54,7 +54,7 @@ public:
return getSurfaceSize();
}
QImage getScreenshot() const override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
float presentRate() const override;

View file

@ -48,7 +48,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
OctreeRenderer(),
_wantScripts(wantScripts),
_entitiesScriptEngine(NULL),
_lastMouseEventValid(false),
_lastPointerEventValid(false),
_viewState(viewState),
_scriptingServices(scriptingServices),
_displayModelBounds(false),
@ -195,9 +195,9 @@ void EntityTreeRenderer::update() {
// Even if we're not moving the mouse, if we started clicking on an entity and we have
// not yet released the hold then this is still considered a holdingClickOnEntity event
// and we want to simulate this message here as well as in mouse move
if (_lastMouseEventValid && !_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastMouseEvent);
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastMouseEvent);
if (_lastPointerEventValid && !_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, _lastPointerEvent);
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", _lastPointerEvent);
}
}
@ -599,18 +599,10 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons
}
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface,
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event){
entityScriptingInterface->mousePressOnEntity(intersection.entityID, MouseEvent(*event));
});
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface,
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
entityScriptingInterface->mouseMoveOnEntity(intersection.entityID, MouseEvent(*event));
});
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface,
[=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
entityScriptingInterface->mouseReleaseOnEntity(intersection.entityID, MouseEvent(*event));
});
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity);
connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity);
@ -627,6 +619,59 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
connect(DependencyManager::get<SceneScriptingInterface>().data(), &SceneScriptingInterface::shouldRenderEntitiesChanged, this, &EntityTreeRenderer::updateEntityRenderStatus, Qt::QueuedConnection);
}
static glm::vec2 projectOntoEntityXYPlane(EntityItemPointer entity, const PickRay& pickRay, const RayToEntityIntersectionResult& rayPickResult) {
if (entity) {
glm::vec3 entityPosition = entity->getPosition();
glm::quat entityRotation = entity->getRotation();
glm::vec3 entityDimensions = entity->getDimensions();
glm::vec3 entityRegistrationPoint = entity->getRegistrationPoint();
// project the intersection point onto the local xy plane of the object.
float distance;
glm::vec3 planePosition = entityPosition;
glm::vec3 planeNormal = entityRotation * Vectors::UNIT_Z;
glm::vec3 rayDirection = pickRay.direction;
glm::vec3 rayStart = pickRay.origin;
glm::vec3 p;
if (rayPlaneIntersection(planePosition, planeNormal, rayStart, rayDirection, distance)) {
p = rayStart + rayDirection * distance;
} else {
p = rayPickResult.intersection;
}
glm::vec3 localP = glm::inverse(entityRotation) * (p - entityPosition);
glm::vec3 normalizedP = (localP / entityDimensions) + entityRegistrationPoint;
return glm::vec2(normalizedP.x * entityDimensions.x,
(1.0f - normalizedP.y) * entityDimensions.y); // flip y-axis
} else {
return glm::vec2();
}
}
static uint32_t toPointerButtons(const QMouseEvent& event) {
uint32_t buttons = 0;
buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0;
buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0;
buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0;
return buttons;
}
static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
switch (event.button()) {
case Qt::LeftButton:
return PointerEvent::PrimaryButton;
case Qt::RightButton:
return PointerEvent::SecondaryButton;
case Qt::MiddleButton:
return PointerEvent::TertiaryButton;
default:
return PointerEvent::NoButtons;
}
}
static const uint32_t MOUSE_POINTER_ID = 0;
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
// If we don't have a tree, or we're in the process of shutting down, then don't
// process these events.
@ -645,24 +690,32 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
QUrl url = QUrl(urlString, QUrl::StrictMode);
if (url.isValid() && !url.isEmpty()){
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
}
emit mousePressOnEntity(rayPickResult, event);
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit mousePressOnEntity(rayPickResult.entityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", pointerEvent);
}
_currentClickingOnEntityID = rayPickResult.entityID;
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
emit clickDownOnEntity(_currentClickingOnEntityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", pointerEvent);
}
_lastPointerEvent = pointerEvent;
_lastPointerEventValid = true;
} else {
emit mousePressOffEntity(rayPickResult, event);
emit mousePressOffEntity();
}
_lastMouseEvent = MouseEvent(*event);
_lastMouseEventValid = true;
}
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
@ -671,31 +724,48 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
if (!_tree || _shuttingDown) {
return;
}
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
PickRay ray = _viewState->computePickRay(event->x(), event->y());
bool precisionPicking = !_dontDoPrecisionPicking;
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking);
if (rayPickResult.intersects) {
//qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
emit mouseReleaseOnEntity(rayPickResult, event);
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit mouseReleaseOnEntity(rayPickResult.entityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", pointerEvent);
}
_lastPointerEvent = pointerEvent;
_lastPointerEventValid = true;
}
// 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 clickOn event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
auto entity = getTree()->findEntityByID(_currentClickingOnEntityID);
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit clickReleaseOnEntity(_currentClickingOnEntityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", pointerEvent);
}
}
// makes it the unknown ID, we just released so we can't be clicking on anything
_currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
_lastMouseEvent = MouseEvent(*event);
_lastMouseEventValid = true;
}
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
@ -712,19 +782,35 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
if (rayPickResult.intersects) {
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit mouseMoveOnEntity(rayPickResult.entityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", pointerEvent);
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", pointerEvent);
}
// handle the hover logic...
// 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) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event));
auto entity = getTree()->findEntityByID(_currentHoverOverEntityID);
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", pointerEvent);
}
}
@ -732,28 +818,39 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
// this is true if the _currentHoverOverEntityID is known or unknown
if (rayPickResult.entityID != _currentHoverOverEntityID) {
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", pointerEvent);
}
}
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
// we should send our hover over event
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event));
emit hoverOverEntity(rayPickResult.entityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", pointerEvent);
}
// remember what we're hovering over
_currentHoverOverEntityID = rayPickResult.entityID;
_lastPointerEvent = pointerEvent;
_lastPointerEventValid = true;
} else {
// handle the hover logic...
// 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()) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event));
auto entity = getTree()->findEntityByID(_currentHoverOverEntityID);
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", pointerEvent);
}
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID
}
@ -762,13 +859,19 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
// Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have
// not yet released the hold then this is still considered a holdingClickOnEntity event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event));
auto entity = getTree()->findEntityByID(_currentClickingOnEntityID);
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
toPointerButton(*event), toPointerButtons(*event));
emit holdingClickOnEntity(_currentClickingOnEntityID, pointerEvent);
if (_entitiesScriptEngine) {
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event));
_entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", pointerEvent);
}
}
_lastMouseEvent = MouseEvent(*event);
_lastMouseEventValid = true;
}
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {

View file

@ -18,7 +18,8 @@
#include <AbstractAudioInterface.h>
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
#include <EntityTree.h>
#include <MouseEvent.h>
#include <QMouseEvent>
#include <PointerEvent.h>
#include <OctreeRenderer.h>
#include <ScriptCache.h>
#include <TextureCache.h>
@ -98,18 +99,18 @@ public:
std::shared_ptr<ZoneEntityItem> myAvatarZone() { return _bestZone; }
signals:
void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
void mouseMoveOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
void mouseReleaseOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mousePressOffEntity();
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
@ -174,8 +175,8 @@ private:
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision);
bool _lastMouseEventValid;
MouseEvent _lastMouseEvent;
bool _lastPointerEventValid;
PointerEvent _lastPointerEvent;
AbstractViewStateInterface* _viewState;
AbstractScriptingServicesInterface* _scriptingServices;
bool _displayModelBounds;

View file

@ -595,6 +595,9 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
}
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
const uint32_t TRIANGLE_STRIDE = 3;
const uint32_t QUAD_STRIDE = 4;
ShapeType type = getShapeType();
glm::vec3 dimensions = getDimensions();
if (type == SHAPE_TYPE_COMPOUND) {
@ -611,8 +614,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
// to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case.
const uint32_t TRIANGLE_STRIDE = 3;
const uint32_t QUAD_STRIDE = 4;
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) {
@ -621,7 +622,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// run through all the triangles and (uniquely) add each point to the hull
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
assert(numIndices % TRIANGLE_STRIDE == 0);
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices % TRIANGLE_STRIDE == 0);
numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) {
glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]];
glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]];
@ -639,7 +643,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// run through all the quads and (uniquely) add each point to the hull
numIndices = (uint32_t)meshPart.quadIndices.size();
assert(numIndices % QUAD_STRIDE == 0);
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices % QUAD_STRIDE == 0);
numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) {
glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]];
glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]];
@ -735,6 +742,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
int32_t meshCount = 0;
int32_t pointListIndex = 0;
for (auto& mesh : meshes) {
if (!mesh) {
continue;
}
const gpu::BufferView& vertices = mesh->getVertexBuffer();
const gpu::BufferView& indices = mesh->getIndexBuffer();
const gpu::BufferView& parts = mesh->getPartBuffer();
@ -768,24 +778,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements()));
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
while (partItr != parts.cend<const model::Mesh::Part>()) {
auto numIndices = partItr->_numIndices;
if (partItr->_topology == model::Mesh::TRIANGLES) {
assert(partItr->_numIndices % 3 == 0);
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices % TRIANGLE_STRIDE == 0);
numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + partItr->_numIndices;
auto indexEnd = indexItr + numIndices;
while (indexItr != indexEnd) {
triangleIndices.push_back(*indexItr + meshIndexOffset);
++indexItr;
}
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
assert(partItr->_numIndices > 2);
uint32_t approxNumIndices = 3 * partItr->_numIndices;
// TODO: resurrect assert after we start sanitizing FBXMesh higher up
//assert(numIndices > 2);
uint32_t approxNumIndices = TRIANGLE_STRIDE * numIndices;
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
// we underestimated the final size of triangleIndices so we pre-emptively expand it
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
}
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + (partItr->_numIndices - 2);
auto indexEnd = indexItr + (numIndices - 2);
// first triangle uses the first three indices
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
@ -819,18 +835,24 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
while (partItr != parts.cend<const model::Mesh::Part>()) {
// collect unique list of indices for this part
std::set<int32_t> uniqueIndices;
auto numIndices = partItr->_numIndices;
if (partItr->_topology == model::Mesh::TRIANGLES) {
assert(partItr->_numIndices % 3 == 0);
// TODO: assert rather than workaround after we start sanitizing FBXMesh higher up
//assert(numIndices% TRIANGLE_STRIDE == 0);
numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + partItr->_numIndices;
auto indexEnd = indexItr + numIndices;
while (indexItr != indexEnd) {
uniqueIndices.insert(*indexItr);
++indexItr;
}
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
assert(partItr->_numIndices > 2);
// TODO: resurrect assert after we start sanitizing FBXMesh higher up
//assert(numIndices > TRIANGLE_STRIDE - 1);
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + (partItr->_numIndices - 2);
auto indexEnd = indexItr + (numIndices - 2);
// first triangle uses the first three indices
uniqueIndices.insert(*(indexItr++));
@ -842,11 +864,11 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
while (indexItr != indexEnd) {
if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) {
if (triangleCount % 2 == 0) {
// even triangles use first two indices in order
// EVEN triangles use first two indices in order
uniqueIndices.insert(*(indexItr - 2));
uniqueIndices.insert(*(indexItr - 1));
} else {
// odd triangles swap order of first two indices
// ODD triangles swap order of first two indices
uniqueIndices.insert(*(indexItr - 1));
uniqueIndices.insert(*(indexItr - 2));
}

View file

@ -11,6 +11,7 @@
#include <QMouseEvent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQmlContext>
#include <QOpenGLContext>
#include <glm/gtx/quaternion.hpp>
@ -26,7 +27,6 @@
#include "EntityTreeRenderer.h"
const float DPI = 30.47f;
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount { 0 };
// Don't allow more than 100 concurrent web views
@ -34,6 +34,8 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
static int MAX_WINDOW_SIZE = 4096;
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
entity->setProperties(properties);
@ -63,106 +65,56 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
QSurface * currentSurface = currentContext->surface();
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
_webSurface->load("WebView.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
// Restore the original GL context
currentContext->makeCurrent(currentSurface);
auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event) {
// Ignore mouse interaction if we're locked
if (this->getLocked()) {
return;
}
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonPress) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
_lastPress = toGlm(mouseEvent->pos());
}
}
if (intersection.entityID == getID()) {
if (event->button() == Qt::MouseButton::RightButton) {
if (event->type() == QEvent::MouseButtonRelease) {
const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);
ivec2 dist = glm::abs(toGlm(mouseEvent->pos()) - _lastPress);
if (!glm::any(glm::greaterThan(dist, ivec2(1)))) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
QMetaObject::invokeMethod(_webSurface->getRootItem(), "goBack");
});
}
_lastPress = ivec2(INT_MIN);
}
return;
}
// FIXME doesn't work... double click events not received
if (event->type() == QEvent::MouseButtonDblClick) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
if (event->button() == Qt::MouseButton::MiddleButton) {
if (event->type() == QEvent::MouseButtonRelease) {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
});
}
return;
}
// Map the intersection point to an actual offscreen pixel
glm::vec3 point = intersection.intersection;
glm::vec3 dimensions = getDimensions();
point -= getPosition();
point = glm::inverse(getRotation()) * point;
point /= dimensions;
point += 0.5f;
point.y = 1.0f - point.y;
point *= dimensions * (METERS_TO_INCHES * DPI);
if (event->button() == Qt::MouseButton::LeftButton) {
if (event->type() == QEvent::MouseButtonPress) {
this->_pressed = true;
this->_lastMove = ivec2((int)point.x, (int)point.y);
} else if (event->type() == QEvent::MouseButtonRelease) {
this->_pressed = false;
}
}
if (event->type() == QEvent::MouseMove) {
this->_lastMove = ivec2((int)point.x, (int)point.y);
}
// Forward the mouse event.
QMouseEvent mappedEvent(event->type(),
QPoint((int)point.x, (int)point.y),
event->screenPos(), event->button(),
event->buttons(), event->modifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
auto forwardPointerEvent = [=](const EntityItemID& entityItemID, const PointerEvent& event) {
if (entityItemID == getID()) {
handlePointerEvent(event);
}
};
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent);
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent);
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent);
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
_mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardPointerEvent);
_mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardPointerEvent);
_mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardPointerEvent);
_hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const PointerEvent& event) {
if (this->_pressed && this->getID() == entityItemID) {
// If the user mouses off the entity while the button is down, simulate a mouse release
QMouseEvent mappedEvent(QEvent::MouseButtonRelease,
QPoint(_lastMove.x, _lastMove.y),
Qt::MouseButton::LeftButton,
Qt::MouseButtons(), Qt::KeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
// If the user mouses off the entity while the button is down, simulate a touch end.
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(Qt::TouchPointReleased);
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
point.setPos(windowPoint);
QList<QTouchEvent::TouchPoint> touchPoints;
touchPoints.push_back(point);
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints);
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
}
});
return true;
}
glm::vec2 RenderableWebEntityItem::getWindowSize() const {
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * _dpi;
// ensure no side is never larger then MAX_WINDOW_SIZE
float max = (dims.x > dims.y) ? dims.x : dims.y;
if (max > MAX_WINDOW_SIZE) {
dims *= MAX_WINDOW_SIZE / max;
}
return dims;
}
void RenderableWebEntityItem::render(RenderArgs* args) {
checkFading();
@ -188,12 +140,13 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
}
_lastRenderTime = usecTimestampNow();
glm::vec2 dims = glm::vec2(getDimensions());
dims *= METERS_TO_INCHES * DPI;
glm::vec2 windowSize = getWindowSize();
// The offscreen surface is idempotent for resizes (bails early
// if it's a no-op), so it's safe to just call resize every frame
// without worrying about excessive overhead.
_webSurface->resize(QSize(dims.x, dims.y));
_webSurface->resize(QSize(windowSize.x, windowSize.y));
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
Q_ASSERT(getType() == EntityTypes::Web);
@ -242,6 +195,69 @@ QObject* RenderableWebEntityItem::getEventHandler() {
return _webSurface->getEventHandler();
}
void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
// Ignore mouse interaction if we're locked
if (getLocked() || !_webSurface) {
return;
}
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
if (event.getType() == PointerEvent::Move) {
// Forward a mouse move event to webSurface
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
}
{
// Forward a touch update event to webSurface
if (event.getType() == PointerEvent::Press) {
this->_pressed = true;
} else if (event.getType() == PointerEvent::Release) {
this->_pressed = false;
}
QEvent::Type type;
Qt::TouchPointState touchPointState;
switch (event.getType()) {
case PointerEvent::Press:
type = QEvent::TouchBegin;
touchPointState = Qt::TouchPointPressed;
break;
case PointerEvent::Release:
type = QEvent::TouchEnd;
touchPointState = Qt::TouchPointReleased;
break;
case PointerEvent::Move:
default:
type = QEvent::TouchUpdate;
touchPointState = Qt::TouchPointMoved;
break;
}
QTouchEvent::TouchPoint point;
point.setId(event.getID());
point.setState(touchPointState);
point.setPos(windowPoint);
point.setScreenPos(windowPoint);
QList<QTouchEvent::TouchPoint> touchPoints;
touchPoints.push_back(point);
QTouchEvent* touchEvent = new QTouchEvent(type);
touchEvent->setWindow(nullptr);
touchEvent->setDevice(nullptr);
touchEvent->setTarget(nullptr);
touchEvent->setTouchPoints(touchPoints);
touchEvent->setTouchPointStates(touchPointState);
_lastTouchEvent = *touchEvent;
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
}
}
void RenderableWebEntityItem::destroyWebSurface() {
if (_webSurface) {
--_currentWebCount;

View file

@ -10,6 +10,9 @@
#define hifi_RenderableWebEntityItem_h
#include <QSharedPointer>
#include <QMouseEvent>
#include <QTouchEvent>
#include <PointerEvent.h>
#include <WebEntityItem.h>
@ -28,9 +31,13 @@ public:
virtual void render(RenderArgs* args) override;
virtual void setSourceUrl(const QString& value) override;
void setProxyWindow(QWindow* proxyWindow);
QObject* getEventHandler();
virtual bool wantsHandControllerPointerEvents() const override { return true; }
virtual bool wantsKeyboardFocus() const override { return true; }
virtual void setProxyWindow(QWindow* proxyWindow) override;
virtual QObject* getEventHandler() override;
void handlePointerEvent(const PointerEvent& event);
void update(const quint64& now) override;
bool needsToCallUpdate() const override { return _webSurface != nullptr; }
@ -40,13 +47,14 @@ public:
private:
bool buildWebSurface(EntityTreeRenderer* renderer);
void destroyWebSurface();
glm::vec2 getWindowSize() const;
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;
uint32_t _texture{ 0 };
ivec2 _lastPress{ INT_MIN };
bool _pressed{ false };
ivec2 _lastMove{ INT_MIN };
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
uint64_t _lastRenderTime{ 0 };
QMetaObject::Connection _mousePressConnection;

View file

@ -17,6 +17,8 @@
#include <glm/glm.hpp>
#include <QtGui/QWindow>
#include <AnimationCache.h> // for Animation, AnimationCache, and AnimationPointer classes
#include <Octree.h> // for EncodeBitstreamParams class
#include <OctreeElement.h> // for OctreeElement::AppendState
@ -436,6 +438,11 @@ public:
static std::function<bool()> getEntitiesShouldFadeFunction() { return _entitiesShouldFadeFunction; }
virtual bool isTransparent() { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
virtual bool wantsHandControllerPointerEvents() const { return false; }
virtual bool wantsKeyboardFocus() const { return false; }
virtual void setProxyWindow(QWindow* proxyWindow) {}
virtual QObject* getEventHandler() { return nullptr; }
protected:
void setSimulated(bool simulated) { _simulated = simulated; }

View file

@ -335,6 +335,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape);
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
changedProperties += _animation.getChangedProperties();
changedProperties += _keyLight.getChangedProperties();
@ -504,6 +505,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Web only
if (_type == EntityTypes::Web) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
}
// PolyVoxel only
@ -726,6 +728,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
_lastEdited = usecTimestampNow();
}
@ -903,6 +907,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
// FIXME - these are not yet handled
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
@ -1065,6 +1071,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
if (properties.getType() == EntityTypes::Web) {
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
}
if (properties.getType() == EntityTypes::Text) {
@ -1364,6 +1371,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Web) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
}
if (properties.getType() == EntityTypes::Text) {
@ -1642,6 +1650,8 @@ void EntityItemProperties::markAllChanged() {
_clientOnlyChanged = true;
_owningAvatarIDChanged = true;
_dpiChanged = true;
}
// The minimum bounding box for the entity.
@ -1977,6 +1987,10 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "ghostingAllowed";
}
if (dpiChanged()) {
out += "dpi";
}
if (shapeChanged()) {
out += "shape";
}

View file

@ -215,6 +215,8 @@ public:
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
static QString getBackgroundModeString(BackgroundMode mode);

View file

@ -73,4 +73,6 @@ const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30;
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -176,6 +176,7 @@ enum EntityPropertyList {
PROP_OWNING_AVATAR_ID, // doesn't go over wire
PROP_SHAPE,
PROP_DPI,
PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts
PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts

View file

@ -1230,6 +1230,65 @@ QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare
return result;
}
QUuid EntityScriptingInterface::getKeyboardFocusEntity() const {
QUuid result;
QMetaObject::invokeMethod(qApp, "getKeyboardFocusEntity", Qt::DirectConnection, Q_RETURN_ARG(QUuid, result));
return result;
}
void EntityScriptingInterface::setKeyboardFocusEntity(QUuid id) {
QMetaObject::invokeMethod(qApp, "setKeyboardFocusEntity", Qt::QueuedConnection, Q_ARG(QUuid, id));
}
void EntityScriptingInterface::sendMousePressOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendMousePressOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendMouseMoveOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendMouseMoveOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendMouseReleaseOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendMouseReleaseOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendClickDownOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendClickDownOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendHoldingClickOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendHoldingClickOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendClickReleaseOnEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendClickReleaseOnEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendHoverEnterEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendHoverEnterEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendHoverOverEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendHoverOverEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
void EntityScriptingInterface::sendHoverLeaveEntity(QUuid id, PointerEvent event) {
QMetaObject::invokeMethod(qApp, "sendHoverLeaveEntity", Qt::QueuedConnection, Q_ARG(QUuid, id), Q_ARG(PointerEvent, event));
}
bool EntityScriptingInterface::wantsHandControllerPointerEvents(QUuid id) {
bool result = false;
if (_entityTree) {
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(id));
if (entity) {
result = entity->wantsHandControllerPointerEvents();
}
});
}
return result;
}
float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) {
return std::abs(mass * (newVelocity - oldVelocity));
}

View file

@ -23,6 +23,7 @@
#include <Octree.h>
#include <OctreeScriptingInterface.h>
#include <RegisteredMetaTypes.h>
#include <PointerEvent.h>
#include "PolyVoxEntityItem.h"
#include "LineEntityItem.h"
@ -34,7 +35,6 @@
#include "EntityItemProperties.h"
class EntityTree;
class MouseEvent;
class RayToEntityIntersectionResult {
public:
@ -62,6 +62,8 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
public:
EntityScriptingInterface(bool bidOnSimulationOwnership);
@ -186,6 +188,23 @@ public slots:
Q_INVOKABLE QVector<QUuid> getChildrenIDs(const QUuid& parentID);
Q_INVOKABLE QVector<QUuid> getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex);
Q_INVOKABLE QUuid getKeyboardFocusEntity() const;
Q_INVOKABLE void setKeyboardFocusEntity(QUuid id);
Q_INVOKABLE void sendMousePressOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendMouseMoveOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendMouseReleaseOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendClickDownOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoldingClickOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendClickReleaseOnEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverEnterEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverOverEntity(QUuid id, PointerEvent event);
Q_INVOKABLE void sendHoverLeaveEntity(QUuid id, PointerEvent event);
Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id);
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
@ -193,17 +212,17 @@ signals:
void canRezChanged(bool canRez);
void canRezTmpChanged(bool canRez);
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);

View file

@ -11,6 +11,7 @@
#include <glm/gtx/transform.hpp>
#include <QDebug>
#include <QJsonDocument>
#include <ByteCountCoding.h>
#include <GeometryUtil.h>
@ -30,6 +31,7 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Web;
_dpi = ENTITY_ITEM_DEFAULT_DPI;
}
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
@ -42,6 +44,7 @@ void WebEntityItem::setDimensions(const glm::vec3& value) {
EntityItemProperties WebEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
return properties;
}
@ -50,6 +53,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
if (somethingChanged) {
bool wantDebug = false;
@ -61,7 +65,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
}
setLastEdited(properties._lastEdited);
}
return somethingChanged;
}
@ -74,6 +78,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
return bytesRead;
}
@ -83,6 +88,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;
requestedProperties += PROP_DPI;
return requestedProperties;
}
@ -91,30 +97,44 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
}
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 {
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
void** intersectedObject, bool precisionPicking) const {
glm::vec3 dimensions = getDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
glm::quat rotation = getRotation();
glm::vec3 position = getPosition() + rotation *
(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
// FIXME - should set face and surfaceNormal
return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance);
glm::vec3 position = getPosition() + rotation * (dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
surfaceNormal = rotation * Vectors::UNIT_Z;
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
return true;
} else {
return false;
}
}
void WebEntityItem::setSourceUrl(const QString& value) {
void WebEntityItem::setSourceUrl(const QString& value) {
if (_sourceUrl != value) {
_sourceUrl = value;
}
}
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
void WebEntityItem::setDPI(uint16_t value) {
_dpi = value;
}
uint16_t WebEntityItem::getDPI() const {
return _dpi;
}

View file

@ -54,8 +54,14 @@ public:
virtual void setSourceUrl(const QString& value);
const QString& getSourceUrl() const;
virtual bool wantsHandControllerPointerEvents() const override { return true; }
void setDPI(uint16_t value);
uint16_t getDPI() const;
protected:
QString _sourceUrl;
uint16_t _dpi;
};
#endif // hifi_WebEntityItem_h

View file

@ -25,6 +25,8 @@
#include <NumericalConstants.h>
#include <Finally.h>
#include <PathUtils.h>
#include <AbstractUriHandler.h>
#include <AccountManager.h>
#include <NetworkAccessManager.h>
#include "OffscreenGLCanvas.h"
@ -32,6 +34,39 @@
#include "GLHelpers.h"
QString fixupHifiUrl(const QString& urlString) {
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
QUrl url(urlString);
QUrlQuery query(url);
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
auto accountManager = DependencyManager::get<AccountManager>();
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
url.setQuery(query.query());
return url.toString();
}
return urlString;
}
class UrlHandler : public QObject {
Q_OBJECT
public:
Q_INVOKABLE bool canHandleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->canAcceptURL(url);
}
Q_INVOKABLE bool handleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->acceptURL(url);
}
// FIXME hack for authentication, remove when we migrate to Qt 5.6
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
return fixupHifiUrl(originalUrl);
}
};
// Time between receiving a request to render the offscreen UI actually triggering
// the render. Could possibly be increased depending on the framerate we expect to
// achieve.
@ -131,7 +166,7 @@ private:
QMyQuickRenderControl* _renderControl{ nullptr };
FramebufferPtr _fbo;
RenderbufferPtr _depthStencil;
TextureRecycler _textures;
TextureRecycler _textures { true };
GLTextureEscrow _escrow;
uint64_t _lastRenderTime{ 0 };
@ -364,6 +399,8 @@ void OffscreenQmlRenderThread::render() {
glGetError();
}
Context::Bound(oglplus::Texture::Target::_2D, *texture).GenerateMipmap();
// FIXME probably unecessary
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
_quickWindow->resetOpenGLState();
@ -438,6 +475,9 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &OffscreenQmlSurface::onAboutToQuit);
_updateTimer.setInterval(MIN_TIMER_MS);
_updateTimer.start();
auto rootContext = getRootContext();
rootContext->setContextProperty("urlHandler", new UrlHandler());
}
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
@ -786,3 +826,5 @@ void OffscreenQmlSurface::setFocusText(bool newFocusText) {
emit focusTextChanged(_focusText);
}
}
#include "OffscreenQmlSurface.moc"

View file

@ -509,16 +509,28 @@ TexturePtr TextureRecycler::getNextTexture() {
using namespace oglplus;
if (_readyTextures.empty()) {
TexturePtr newTexture(new Texture());
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(
0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr
);
if (_useMipmaps) {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::LinearMipmapLinear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Anisotropy(8.0f)
.LODBias(-0.2f)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
} else {
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
.MinFilter(TextureMinFilter::Linear)
.MagFilter(TextureMagFilter::Linear)
.WrapS(TextureWrap::ClampToEdge)
.WrapT(TextureWrap::ClampToEdge)
.Image2D(0, PixelDataInternalFormat::RGBA8,
_size.x, _size.y,
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
}
GLuint texId = GetName(*newTexture);
_allTextures[texId] = TexInfo{ newTexture, _size };
_readyTextures.push(newTexture);

View file

@ -190,6 +190,7 @@ using BasicFramebufferWrapperPtr = std::shared_ptr<BasicFramebufferWrapper>;
class TextureRecycler {
public:
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
void setSize(const uvec2& size);
void clear();
TexturePtr getNextTexture();
@ -212,4 +213,5 @@ private:
Map _allTextures;
Queue _readyTextures;
uvec2 _size{ 1920, 1080 };
bool _useMipmaps;
};

View file

@ -224,6 +224,8 @@ public:
bool operator==(const Iterator<T>& iterator) const { return (_ptr == iterator.getConstPtr()); }
bool operator!=(const Iterator<T>& iterator) const { return (_ptr != iterator.getConstPtr()); }
bool operator<(const Iterator<T>& iterator) const { return (_ptr < iterator.getConstPtr()); }
bool operator>(const Iterator<T>& iterator) const { return (_ptr > iterator.getConstPtr()); }
void movePtr(const Index& movement) {
auto byteptr = ((Byte*)_ptr);

View file

@ -676,6 +676,10 @@ void sphericalHarmonicsEvaluateDirection(float * result, int order, const glm::
}
bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<glm::vec3> & output, const uint order) {
int width = cubeTexture.getWidth();
if(width != cubeTexture.getHeight()) {
return false;
}
const uint sqOrder = order*order;
// allocate memory for calculations
@ -684,8 +688,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
std::vector<float> resultG(sqOrder);
std::vector<float> resultB(sqOrder);
int width, height;
// initialize values
float fWt = 0.0f;
for(uint i=0; i < sqOrder; i++) {
@ -696,11 +698,16 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
}
std::vector<float> shBuff(sqOrder);
std::vector<float> shBuffB(sqOrder);
// get width and height
width = height = cubeTexture.getWidth();
if(width != height) {
return false;
// We trade accuracy for speed by breaking the image into 32x32 parts
// and approximating the distance for all the pixels in each part to be
// the distance to the part's center.
int numDivisionsPerSide = 32;
if (width < numDivisionsPerSide) {
numDivisionsPerSide = width;
}
int stride = width / numDivisionsPerSide;
int halfStride = stride / 2;
// for each face of cube texture
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
@ -718,11 +725,11 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
// step between two texels for range [-1, 1]
float invWidthBy2 = 2.0f / float(width);
for(int y=0; y < width; y++) {
for(int y=halfStride; y < width-halfStride; y += stride) {
// texture coordinate V in range [-1 to 1]
const float fV = negativeBound + float(y) * invWidthBy2;
for(int x=0; x < width; x++) {
for(int x=halfStride; x < width - halfStride; x += stride) {
// texture coordinate U in range [-1 to 1]
const float fU = negativeBound + float(x) * invWidthBy2;
@ -785,32 +792,37 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir);
// index of texel in texture
uint pixOffsetIndex = (x + y * width) * numComponents;
// get color from texture and map to range [0, 1]
glm::vec3 clr(ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex]),
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 1]),
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 2]));
float red { 0.0f };
float green { 0.0f };
float blue { 0.0f };
for (int i = 0; i < stride; ++i) {
for (int j = 0; j < stride; ++j) {
int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents;
red += ColorUtils::sRGB8ToLinearFloat(data[k]);
green += ColorUtils::sRGB8ToLinearFloat(data[k + 1]);
blue += ColorUtils::sRGB8ToLinearFloat(data[k + 2]);
}
}
glm::vec3 clr(red, green, blue);
// scale color and add to previously accumulated coefficients
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.r * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order,
resultR.data(), shBuffB.data());
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.g * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order,
resultG.data(), shBuffB.data());
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.b * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order,
resultB.data(), shBuffB.data());
// red
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data());
// green
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data());
// blue
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data());
}
}
}
// final scale for coefficients
const float fNormProj = (4.0f * glm::pi<float>()) / fWt;
const float fNormProj = (4.0f * glm::pi<float>()) / (fWt * (float)(stride * stride));
sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj);
sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj);
sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj);

View file

@ -330,9 +330,22 @@ bool Geometry::areTexturesLoaded() const {
if (!_areTexturesLoaded) {
for (auto& material : _materials) {
// Check if material textures are loaded
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[](const NetworkMaterial::Textures::value_type& it) { return it.texture && !it.texture->isLoaded(); })) {
bool materialMissingTexture = std::any_of(material->_textures.cbegin(), material->_textures.cend(),
[](const NetworkMaterial::Textures::value_type& it) {
auto texture = it.texture;
if (!texture) {
return false;
}
// Failed texture downloads need to be considered as 'loaded'
// or the object will never fade in
bool finished = texture->isLoaded() || texture->isFailed();
if (!finished) {
return true;
}
return false;
});
if (materialMissingTexture) {
return false;
}

View file

@ -35,6 +35,7 @@ public:
void setCubemap(const gpu::TexturePointer& cubemap);
const gpu::TexturePointer& getCubemap() const { return _cubemap; }
virtual bool empty() { return _schemaBuffer.get<Schema>().color == vec3(0) && !_cubemap; }
virtual void clear() { setCubemap(nullptr); }
void prepare(gpu::Batch& batch, int textureSlot = SKYBOX_SKYMAP_SLOT, int bufferSlot = SKYBOX_CONSTANTS_SLOT) const;

View file

@ -38,6 +38,7 @@ class AddressManager : public QObject, public Dependency {
Q_PROPERTY(QString protocol READ getProtocol)
Q_PROPERTY(QString hostname READ getHost)
Q_PROPERTY(QString pathname READ currentPath)
Q_PROPERTY(QString placename READ getPlaceName)
public:
Q_INVOKABLE QString protocolVersion();
using PositionGetter = std::function<glm::vec3()>;

View file

@ -282,6 +282,9 @@ public:
/// Checks whether the resource has loaded.
virtual bool isLoaded() const { return _loaded; }
/// Checks whether the resource has failed to download.
virtual bool isFailed() const { return _failedToLoad; }
/// For loading resources, returns the number of bytes received.
qint64 getBytesReceived() const { return _bytesReceived; }

View file

@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS;
return VERSION_WEB_ENTITIES_SUPPORT_DPI;
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AbsoluteSixByteRotations);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SensorToWorldMat);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::AssetGetInfo:

View file

@ -186,12 +186,14 @@ const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,
SoftAttachmentSupport,
AvatarEntities,
AbsoluteSixByteRotations
AbsoluteSixByteRotations,
SensorToWorldMat
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -164,17 +164,28 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
assert(pointCollection.size() == 1); // should only have one mesh
if (pointCollection.size() < 1) {
// no lists of points to work with
return nullptr;
}
// we only use the first point collection
const ShapeInfo::PointList& pointList = pointCollection[0];
assert(pointList.size() > 2); // should have at least one triangle's worth of points
if (pointList.size() < 3) {
// not enough distinct points to make a non-degenerate triangle
return nullptr;
}
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
int32_t numIndices = triangleIndices.size();
if (numIndices < 3) {
// not enough indices to make a single triangle
return nullptr;
}
// allocate mesh buffers
btIndexedMesh mesh;
int32_t numIndices = triangleIndices.size();
const int32_t VERTICES_PER_TRIANGLE = 3;
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
if (numIndices < INT16_MAX) {
@ -328,7 +339,9 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
break;
case SHAPE_TYPE_STATIC_MESH: {
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
shape = new StaticMeshShape(dataArray);
if (dataArray) {
shape = new StaticMeshShape(dataArray);
}
}
break;
}

View file

@ -172,7 +172,7 @@ public:
}
// Fetch the most recently displayed image as a QImage
virtual QImage getScreenshot() const = 0;
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
// will query the underlying hmd api to compute the most recent head pose
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }

View file

@ -38,6 +38,7 @@ public:
void parse(const QString& userDataJson);
bool ready();
bool enabled() { return _enabled; }
void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation);
const gpu::ShaderPointer& getShader() const { return _shader; }

View file

@ -26,6 +26,10 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() {
_procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
}
bool ProceduralSkybox::empty() {
return !_procedural.enabled() && Skybox::empty();
}
void ProceduralSkybox::clear() {
// Parse and prepare a procedural with no shaders to release textures
parse(QString());

View file

@ -20,13 +20,14 @@
class ProceduralSkybox: public model::Skybox {
public:
ProceduralSkybox();
virtual ~ProceduralSkybox() {};
~ProceduralSkybox() override {};
void parse(const QString& userData) { _procedural.parse(userData); }
virtual void clear() override;
bool empty() override;
void clear() override;
virtual void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
void render(gpu::Batch& batch, const ViewFrustum& frustum) const override;
static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox);
protected:

View file

@ -533,10 +533,21 @@ void ModelMeshPartPayload::startFade() {
void ModelMeshPartPayload::render(RenderArgs* args) const {
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
if (!_model->_readyWhenAdded || !_model->_isVisible || !_hasStartedFade) {
if (!_model->_readyWhenAdded || !_model->_isVisible) {
return; // bail asap
}
// If we didn't start the fade in, check if we are ready to now....
if (!_hasStartedFade && _model->isLoaded() && _model->getGeometry()->areTexturesLoaded()) {
const_cast<ModelMeshPartPayload&>(*this).startFade();
}
// If we still didn't start the fade in, bail
if (!_hasStartedFade) {
return;
}
// When an individual mesh parts like this finishes its fade, we will mark the Model as
// having render items that need updating
bool nextIsFading = _isFading ? isStillFading() : false;

View file

@ -1,3 +1,15 @@
set(TARGET_NAME script-engine)
setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets)
target_zlib()
add_dependency_external_projects(quazip)
find_package(QuaZip REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
if (WIN32)
add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
endif ()
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics)

View file

@ -13,6 +13,7 @@
#include "KeyEvent.h"
#include "MouseEvent.h"
#include "SpatialEvent.h"
#include "PointerEvent.h"
#include "TouchEvent.h"
#include "WheelEvent.h"
@ -22,6 +23,7 @@ void registerEventTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, HFActionEvent::toScriptValue, HFActionEvent::fromScriptValue);
qScriptRegisterMetaType(engine, KeyEvent::toScriptValue, KeyEvent::fromScriptValue);
qScriptRegisterMetaType(engine, MouseEvent::toScriptValue, MouseEvent::fromScriptValue);
qScriptRegisterMetaType(engine, PointerEvent::toScriptValue, PointerEvent::fromScriptValue);
qScriptRegisterMetaType(engine, TouchEvent::toScriptValue, TouchEvent::fromScriptValue);
qScriptRegisterMetaType(engine, WheelEvent::toScriptValue, WheelEvent::fromScriptValue);
qScriptRegisterMetaType(engine, SpatialEvent::toScriptValue, SpatialEvent::fromScriptValue);

View file

@ -0,0 +1,145 @@
//
// FileScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by Elisa Lupin-Jimenez on 6/28/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
//
#include <QTemporaryDir>
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QBuffer>
#include <QTextCodec>
#include <QIODevice>
#include <QUrl>
#include <QByteArray>
#include <QString>
#include <QFileInfo>
#include <quazip5/quazip.h>
#include <quazip5/JlCompress.h>
#include "ResourceManager.h"
#include "FileScriptingInterface.h"
FileScriptingInterface::FileScriptingInterface(QObject* parent) : QObject(parent) {
// nothing for now
}
void FileScriptingInterface::runUnzip(QString path, QUrl url) {
qDebug() << "Url that was downloaded: " + url.toString();
qDebug() << "Path where download is saved: " + path;
QString fileName = "/" + path.section("/", -1);
QString tempDir = path;
tempDir.remove(fileName);
qDebug() << "Temporary directory at: " + tempDir;
if (!isTempDir(tempDir)) {
qDebug() << "Temporary directory mismatch; risk of losing files";
return;
}
QString file = unzipFile(path, tempDir);
if (file != "") {
qDebug() << "Object file to upload: " + file;
QUrl url = QUrl::fromLocalFile(file);
emit unzipSuccess(url.toString());
} else {
qDebug() << "unzip failed";
}
qDebug() << "Removing temporary directory at: " + tempDir;
QDir(tempDir).removeRecursively();
}
// fix to check that we are only referring to a temporary directory
bool FileScriptingInterface::isTempDir(QString tempDir) {
QString folderName = "/" + tempDir.section("/", -1);
QString tempContainer = tempDir;
tempContainer.remove(folderName);
QTemporaryDir test;
QString testDir = test.path();
folderName = "/" + testDir.section("/", -1);
QString testContainer = testDir;
testContainer.remove(folderName);
if (testContainer == tempContainer) return true;
return false;
}
bool FileScriptingInterface::isZippedFbx(QUrl url) {
if (url.toString().contains(".zip") && url.toString().contains("fbx")) return true;
return false;
}
// this function is not in use
QString FileScriptingInterface::getTempDir() {
QTemporaryDir dir;
dir.setAutoRemove(false);
return dir.path();
// do something to delete this temp dir later
}
QString FileScriptingInterface::convertUrlToPath(QUrl url) {
QString newUrl;
QString oldUrl = url.toString();
newUrl = oldUrl.section("filename=", 1, 1);
qDebug() << "Filename should be: " + newUrl;
return newUrl;
}
// this function is not in use
void FileScriptingInterface::downloadZip(QString path, const QString link) {
QUrl url = QUrl(link);
auto request = ResourceManager::createResourceRequest(nullptr, url);
connect(request, &ResourceRequest::finished, this, [this, path]{
unzipFile(path, ""); // so intellisense isn't mad
});
request->send();
}
QString FileScriptingInterface::unzipFile(QString path, QString tempDir) {
QDir dir(path);
QString dirName = dir.path();
QString target = tempDir + "/model_repo";
QStringList list = JlCompress::extractDir(dirName, target);
qDebug() << list;
if (!list.isEmpty()) {
return list.front();
} else {
qDebug() << "Extraction failed";
return "";
}
}
// this function is not in use
void FileScriptingInterface::recursiveFileScan(QFileInfo file, QString* dirName) {
/*if (!file.isDir()) {
qDebug() << "Regular file logged: " + file.fileName();
return;
}*/
QFileInfoList files;
if (file.fileName().contains(".zip")) {
qDebug() << "Extracting archive: " + file.fileName();
JlCompress::extractDir(file.fileName());
}
files = file.dir().entryInfoList();
/*if (files.empty()) {
files = JlCompress::getFileList(file.fileName());
}*/
foreach (QFileInfo file, files) {
qDebug() << "Looking into file: " + file.fileName();
recursiveFileScan(file, dirName);
}
return;
}

View file

@ -0,0 +1,43 @@
//
// FileScriptingInterface.h
// libraries/script-engine/src
//
// Created by Elisa Lupin-Jimenez on 6/28/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
//
#ifndef hifi_FileScriptingInterface_h
#define hifi_FileScriptingInterface_h
#include <QtCore/QObject>
#include <QFileInfo>
#include <QString>
class FileScriptingInterface : public QObject {
Q_OBJECT
public:
FileScriptingInterface(QObject* parent);
public slots:
bool isZippedFbx(QUrl url);
QString convertUrlToPath(QUrl url);
void runUnzip(QString path, QUrl url);
QString getTempDir();
signals:
void unzipSuccess(QString url);
private:
bool isTempDir(QString tempDir);
QString unzipFile(QString path, QString tempDir);
void recursiveFileScan(QFileInfo file, QString* dirName);
void downloadZip(QString path, const QString link);
};
#endif // hifi_FileScriptingInterface_h

View file

@ -49,6 +49,7 @@
#include "BatchLoader.h"
#include "DataViewClass.h"
#include "EventTypes.h"
#include "FileScriptingInterface.h" // unzip project
#include "MenuItemProperties.h"
#include "ScriptAudioInjector.h"
#include "ScriptCache.h"
@ -501,6 +502,9 @@ void ScriptEngine::init() {
registerGlobalObject("Mat4", &_mat4Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
registerGlobalObject("File", new FileScriptingInterface(this));
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);
@ -723,9 +727,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
};
};
using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;
auto makeMouseHandler = [this](QString eventName) -> MouseHandler {
return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) {
using PointerHandler = std::function<void(const EntityItemID&, const PointerEvent&)>;
auto makePointerHandler = [this](QString eventName) -> PointerHandler {
return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) {
forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });
};
};
@ -741,17 +745,17 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));
connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));
connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makePointerHandler("mousePressOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makePointerHandler("mouseMoveOnEntity"));
connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makePointerHandler("mouseReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity"));
connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makePointerHandler("clickDownOnEntity"));
connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makePointerHandler("holdingClickOnEntity"));
connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makePointerHandler("clickReleaseOnEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makePointerHandler("hoverEnterEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makePointerHandler("hoverOverEntity"));
connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makePointerHandler("hoverLeaveEntity"));
connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));
}
@ -1546,7 +1550,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
}
}
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event) {
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) {
if (QThread::currentThread() != thread()) {
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
@ -1556,12 +1560,12 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
QMetaObject::invokeMethod(this, "callEntityScriptMethod",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, methodName),
Q_ARG(const MouseEvent&, event));
Q_ARG(const PointerEvent&, event));
return;
}
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::callEntityScriptMethod() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";
"entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent";
#endif
refreshFileScript(entityID);

View file

@ -30,7 +30,7 @@
#include <EntityItemID.h>
#include <EntitiesScriptEngineProvider.h>
#include "MouseEvent.h"
#include "PointerEvent.h"
#include "ArrayBufferClass.h"
#include "AssetScriptingInterface.h"
#include "AudioScriptingInterface.h"
@ -140,7 +140,7 @@ public:
Q_INVOKABLE void unloadAllEntityScripts();
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName,
const QStringList& params = QStringList()) override;
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event);
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event);
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision);
Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); }

View file

@ -113,6 +113,20 @@ int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk)
// calculate the angle between a point on a sphere that is closest to the cone.
float coneSphereAngle(const glm::vec3& coneCenter, const glm::vec3& coneDirection, const glm::vec3& sphereCenter, float sphereRadius);
inline bool rayPlaneIntersection(const glm::vec3& planePosition, const glm::vec3& planeNormal,
const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distanceOut) {
float rayDirectionDotPlaneNormal = glm::dot(rayDirection, planeNormal);
const float PARALLEL_THRESHOLD = 0.0001f;
if (fabsf(rayDirectionDotPlaneNormal) > PARALLEL_THRESHOLD) {
float rayStartDotPlaneNormal = glm::dot(planePosition - rayStart, planeNormal);
distanceOut = rayStartDotPlaneNormal / rayDirectionDotPlaneNormal;
return true;
} else {
// ray is parallel to the plane
return false;
}
}
typedef glm::vec2 LineSegment2[2];
// Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html

View file

@ -0,0 +1,163 @@
//
// PointerEvent.cpp
// script-engine/src
//
// Created by Anthony Thibault on 2016-8-11.
// 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 "PointerEvent.h"
#include <qscriptengine.h>
#include <qscriptvalue.h>
#include "RegisteredMetaTypes.h"
static bool areFlagsSet(uint32_t flags, uint32_t mask) {
return (flags & mask) != 0;
}
PointerEvent::PointerEvent() {
;
}
PointerEvent::PointerEvent(EventType type, uint32_t id,
const glm::vec2& pos2D, const glm::vec3& pos3D,
const glm::vec3& normal, const glm::vec3& direction,
Button button, uint32_t buttons) :
_type(type),
_id(id),
_pos2D(pos2D),
_pos3D(pos3D),
_normal(normal),
_direction(direction),
_button(button),
_buttons(buttons)
{
;
}
QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEvent& event) {
QScriptValue obj = engine->newObject();
switch (event._type) {
case Press:
obj.setProperty("type", "Press");
break;
case Release:
obj.setProperty("type", "Release");
break;
default:
case Move:
obj.setProperty("type", "Move");
break;
};
obj.setProperty("id", event._id);
QScriptValue pos2D = engine->newObject();
pos2D.setProperty("x", event._pos2D.x);
pos2D.setProperty("y", event._pos2D.y);
obj.setProperty("pos2D", pos2D);
QScriptValue pos3D = engine->newObject();
pos3D.setProperty("x", event._pos3D.x);
pos3D.setProperty("y", event._pos3D.y);
pos3D.setProperty("z", event._pos3D.z);
obj.setProperty("pos3D", pos3D);
QScriptValue normal = engine->newObject();
normal.setProperty("x", event._normal.x);
normal.setProperty("y", event._normal.y);
normal.setProperty("z", event._normal.z);
obj.setProperty("pos3D", normal);
QScriptValue direction = engine->newObject();
direction.setProperty("x", event._direction.x);
direction.setProperty("y", event._direction.y);
direction.setProperty("z", event._direction.z);
obj.setProperty("pos3D", direction);
switch (event._button) {
case NoButtons:
obj.setProperty("button", "None");
break;
case PrimaryButton:
obj.setProperty("button", "Primary");
break;
case SecondaryButton:
obj.setProperty("button", "Secondary");
break;
case TertiaryButton:
obj.setProperty("button", "Tertiary");
break;
}
obj.setProperty("isLeftButton", areFlagsSet(event._buttons, PrimaryButton));
obj.setProperty("isRightButton", areFlagsSet(event._buttons, SecondaryButton));
obj.setProperty("isMiddleButton", areFlagsSet(event._buttons, TertiaryButton));
obj.setProperty("isPrimaryButton", areFlagsSet(event._buttons, PrimaryButton));
obj.setProperty("isSecondaryButton", areFlagsSet(event._buttons, SecondaryButton));
obj.setProperty("isTertiaryButton", areFlagsSet(event._buttons, TertiaryButton));
return obj;
}
void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& event) {
if (object.isObject()) {
QScriptValue type = object.property("type");
QString typeStr = type.isString() ? type.toString() : "Move";
if (typeStr == "Press") {
event._type = Press;
} else if (typeStr == "Release") {
event._type = Release;
} else {
event._type = Move;
}
QScriptValue id = object.property("id");
event._id = type.isNumber() ? (uint32_t)type.toNumber() : 0;
glm::vec2 pos2D;
vec2FromScriptValue(object.property("pos2D"), event._pos2D);
glm::vec3 pos3D;
vec3FromScriptValue(object.property("pos3D"), event._pos3D);
glm::vec3 normal;
vec3FromScriptValue(object.property("normal"), event._normal);
glm::vec3 direction;
vec3FromScriptValue(object.property("direction"), event._direction);
QScriptValue button = object.property("button");
QString buttonStr = type.isString() ? type.toString() : "NoButtons";
if (buttonStr == "Primary") {
event._button = PrimaryButton;
} else if (buttonStr == "Secondary") {
event._button = SecondaryButton;
} else if (buttonStr == "Tertiary") {
event._button = TertiaryButton;
} else {
event._button = NoButtons;
}
bool primary = object.property("isPrimary").toBool() || object.property("isLeftButton").toBool();
bool secondary = object.property("isSecondary").toBool() || object.property("isRightButton").toBool();
bool tertiary = object.property("isTertiary").toBool() || object.property("isMiddleButton").toBool();
event._buttons = 0;
if (primary) {
event._buttons |= PrimaryButton;
}
if (secondary) {
event._buttons |= SecondaryButton;
}
if (tertiary) {
event._buttons |= TertiaryButton;
}
}
}

View file

@ -0,0 +1,68 @@
//
// PointerEvent.h
// script-engine/src
//
// Created by Anthony Thibault on 2016-8-11.
// 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
//
#ifndef hifi_PointerEvent_h
#define hifi_PointerEvent_h
#include <stdint.h>
#include <glm/glm.hpp>
#include <QScriptValue>
class PointerEvent {
public:
enum Button {
NoButtons = 0x0,
PrimaryButton = 0x1,
SecondaryButton = 0x2,
TertiaryButton = 0x4
};
enum EventType {
Press, // A button has just been pressed
Release, // A button has just been released
Move // The pointer has just moved
};
PointerEvent();
PointerEvent(EventType type, uint32_t id,
const glm::vec2& pos2D, const glm::vec3& pos3D,
const glm::vec3& normal, const glm::vec3& direction,
Button button, uint32_t buttons);
static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event);
static void fromScriptValue(const QScriptValue& object, PointerEvent& event);
QScriptValue toScriptValue(QScriptEngine* engine) const { return PointerEvent::toScriptValue(engine, *this); }
EventType getType() const { return _type; }
uint32_t getID() const { return _id; }
const glm::vec2& getPos2D() const { return _pos2D; }
const glm::vec3& getPos3D() const { return _pos3D; }
const glm::vec3& getNormal() const { return _normal; }
const glm::vec3& getDirection() const { return _direction; }
Button getButton() const { return _button; }
uint32_t getButtons() const { return _buttons; }
private:
EventType _type;
uint32_t _id; // used to identify the pointer. (left vs right hand, for example)
glm::vec2 _pos2D; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner
glm::vec3 _pos3D; // surface location in world coordinates (in meters)
glm::vec3 _normal; // surface normal
glm::vec3 _direction; // incoming direction of pointer ray.
Button _button { NoButtons }; // button assosiated with this event, (if type is Press, this will be the button that is pressed)
uint32_t _buttons { NoButtons }; // the current state of all the buttons.
};
Q_DECLARE_METATYPE(PointerEvent)
#endif // hifi_PointerEvent_h

View file

@ -60,39 +60,6 @@ private:
bool _navigationFocusDisabled{ false };
};
QString fixupHifiUrl(const QString& urlString) {
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
QUrl url(urlString);
QUrlQuery query(url);
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
auto accountManager = DependencyManager::get<AccountManager>();
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
url.setQuery(query.query());
return url.toString();
}
return urlString;
}
class UrlHandler : public QObject {
Q_OBJECT
public:
Q_INVOKABLE bool canHandleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->canAcceptURL(url);
}
Q_INVOKABLE bool handleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->acceptURL(url);
}
// FIXME hack for authentication, remove when we migrate to Qt 5.6
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
return fixupHifiUrl(originalUrl);
}
};
static OffscreenFlags* offscreenFlags { nullptr };
// This hack allows the QML UI to work with keys that are also bound as
@ -126,7 +93,6 @@ void OffscreenUi::create(QOpenGLContext* context) {
rootContext->setContextProperty("OffscreenUi", this);
rootContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
}

View file

@ -111,12 +111,17 @@ public:
_plugin.withNonPresentThreadLock([&] {
while (!_queue.empty()) {
auto& front = _queue.front();
auto result = glClientWaitSync(front.fence, 0, 0);
if (GL_TIMEOUT_EXPIRED == result && GL_WAIT_FAILED == result) {
if (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) {
break;
} else if (GL_CONDITION_SATISFIED == result || GL_ALREADY_SIGNALED == result) {
glDeleteSync(front.fence);
} else {
assert(false);
}
glDeleteSync(front.fence);
front.fence = 0;
_current = front;
_queue.pop();
@ -455,6 +460,16 @@ void OpenVrDisplayPlugin::compositeLayers() {
#if OPENVR_THREADED_SUBMIT
newComposite.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// https://www.opengl.org/registry/specs/ARB/sync.txt:
// > The simple flushing behavior defined by
// > SYNC_FLUSH_COMMANDS_BIT will not help when waiting for a fence
// > command issued in another context's command stream to complete.
// > Applications which block on a fence sync object must take
// > additional steps to assure that the context from which the
// > corresponding fence command was issued has flushed that command
// > to the graphics pipeline.
glFlush();
if (!newComposite.textureID) {
newComposite.textureID = getGLBackend()->getTextureID(newComposite.texture, false);
}

View file

@ -176,8 +176,7 @@ void showOpenVrKeyboard(bool show = true) {
}
}
void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
void updateFromOpenVrKeyboardInput() {
auto chars = _overlay->GetKeyboardText(textArray, 8192);
auto newText = QString(QByteArray(textArray, chars));
_keyboardFocusObject->setProperty("text", newText);
@ -187,6 +186,11 @@ void finishOpenVrKeyboardInput() {
//QInputMethodEvent event(_existingText, QList<QInputMethodEvent::Attribute>());
//event.setCommitString(newText, 0, _existingText.size());
//qApp->sendEvent(_keyboardFocusObject, &event);
}
void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
updateFromOpenVrKeyboardInput();
// Simulate an enter press on the top level window to trigger the action
if (0 == (_currentHints & Qt::ImhMultiLine)) {
qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")));
@ -267,6 +271,11 @@ void handleOpenVrEvents() {
activeHmd->AcknowledgeQuit_Exiting();
break;
case vr::VREvent_KeyboardCharInput:
// Make the focused field match the keyboard results, inclusive of combining characters and such.
updateFromOpenVrKeyboardInput();
break;
case vr::VREvent_KeyboardDone:
finishOpenVrKeyboardInput();

View file

@ -1,3 +1,6 @@
"use strict";
/* jslint vars: true, plusplus: true */
//
// defaultScripts.js
// examples
@ -8,23 +11,75 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var DEFAULT_SCRIPTS = [
"system/progress.js",
"system/away.js",
"system/users.js",
"system/mute.js",
"system/goto.js",
"system/hmd.js",
"system/marketplaces/marketplace.js",
"system/edit.js",
"system/mod.js",
"system/selectAudioDevice.js",
"system/notifications.js",
"system/controllers/handControllerGrab.js",
"system/controllers/handControllerPointer.js",
"system/controllers/squeezeHands.js",
"system/controllers/grab.js",
"system/controllers/teleport.js",
"system/controllers/toggleAdvancedMovementForHandControllers.js",
"system/dialTone.js",
"system/firstPersonHMD.js",
"system/snapshot.js"
];
Script.load("system/progress.js");
Script.load("system/away.js");
Script.load("system/users.js");
Script.load("system/mute.js");
Script.load("system/goto.js");
Script.load("system/hmd.js");
Script.load("system/marketplace.js");
Script.load("system/edit.js");
Script.load("system/mod.js");
Script.load("system/selectAudioDevice.js");
Script.load("system/notifications.js");
Script.load("system/controllers/handControllerGrab.js");
Script.load("system/controllers/handControllerPointer.js");
Script.load("system/controllers/squeezeHands.js");
Script.load("system/controllers/grab.js");
Script.load("system/controllers/teleport.js");
Script.load("system/controllers/toggleAdvancedMovementForHandControllers.js")
Script.load("system/dialTone.js");
Script.load("system/firstPersonHMD.js");
// add a menu item for debugging
var MENU_CATEGORY = "Developer";
var MENU_ITEM = "Debug defaultScripts.js";
var debuggingDefaultScripts = false;
if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) {
Menu.addMenuItem({
menuName: MENU_CATEGORY,
menuItemName: MENU_ITEM,
isCheckable: true,
isChecked: false,
grouping: "Advanced"
});
}
// start all scripts
if (Menu.isOptionChecked(MENU_ITEM)) {
// we're debugging individual default scripts
// so we load each into its own ScriptEngine instance
debuggingDefaultScripts = true;
for (var i in DEFAULT_SCRIPTS) {
Script.load(DEFAULT_SCRIPTS[i]);
}
} else {
// include all default scripts into this ScriptEngine
for (var j in DEFAULT_SCRIPTS) {
Script.include(DEFAULT_SCRIPTS[j]);
}
}
function stopLoadedScripts() {
if (debuggingDefaultScripts) {
// remove debug script loads
var runningScripts = ScriptDiscoveryService.getRunning();
for (var i in runningScripts) {
var scriptName = runningScripts[i].name;
for (var j in DEFAULT_SCRIPTS) {
if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) {
ScriptDiscoveryService.stopScript(runningScripts[i].url);
}
}
}
if (!Menu.isOptionChecked(MENU_ITEM)) {
Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM);
}
}
}
Script.scriptEnding.connect(stopLoadedScripts);

Some files were not shown because too many files have changed in this diff Show more