mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge remote-tracking branch 'upstream/master' into avatar-verification
This commit is contained in:
commit
d6dc579331
99 changed files with 3710 additions and 2164 deletions
|
@ -33,7 +33,7 @@
|
|||
#include <NodeType.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <PathUtils.h>
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include "AssetServerLogging.h"
|
||||
#include "BakeAssetTask.h"
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <ScriptCache.h>
|
||||
#include <EntityEditFilters.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <AddressManager.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
|
||||
#include "../AssignmentDynamicFactory.h"
|
||||
|
@ -471,77 +470,7 @@ void EntityServer::startDynamicDomainVerification() {
|
|||
qCDebug(entities) << "Starting Dynamic Domain Verification...";
|
||||
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
|
||||
|
||||
QHashIterator<QString, EntityItemID> i(localMap);
|
||||
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
const auto& certificateID = i.key();
|
||||
const auto& entityID = i.value();
|
||||
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
|
||||
|
||||
if (entity) {
|
||||
if (!entity->getProperties().verifyStaticCertificateProperties()) {
|
||||
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed"
|
||||
<< "static certificate verification.";
|
||||
// Delete the entity if it doesn't pass static certificate verification
|
||||
tree->withWriteLock([&] {
|
||||
tree->deleteEntity(entityID, true);
|
||||
});
|
||||
} else {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = certificateID;
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
|
||||
if (jsonObject["domain_id"].toString() != thisDomainID) {
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID;
|
||||
networkReply->deleteLater();
|
||||
return;
|
||||
}
|
||||
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
|
||||
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
|
||||
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
|
||||
tree->withWriteLock([&] {
|
||||
tree->deleteEntity(entityID, true);
|
||||
});
|
||||
} else {
|
||||
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID
|
||||
<< "More info:" << jsonObject;
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!";
|
||||
}
|
||||
}
|
||||
tree->startDynamicDomainVerificationOnServer((float) _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS / MSECS_PER_SECOND);
|
||||
|
||||
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
|
||||
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";
|
||||
|
|
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -17,8 +17,8 @@ if (WIN32)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
|
||||
URL_MD5 06804ff9727b910dcd04a37c800053b5
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip
|
||||
URL_MD5 1e3e8b2101387af07ff9c841d0ea285e
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
|
||||
LOG_DOWNLOAD 1
|
||||
|
|
|
@ -53,10 +53,5 @@ macro(add_crashpad)
|
|||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
)
|
||||
install(
|
||||
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
|
||||
DESTINATION ${INTERFACE_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
||||
|
|
74
cmake/macros/TargetOpenEXR.cmake
Normal file
74
cmake/macros/TargetOpenEXR.cmake
Normal file
|
@ -0,0 +1,74 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Olivier Prat on 2019/03/26
|
||||
#
|
||||
# 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_OPENEXR)
|
||||
if (NOT ANDROID)
|
||||
set(openexr_config_file "${VCPKG_INSTALL_ROOT}/include/OpenEXR/OpenEXRConfig.h")
|
||||
if(EXISTS ${openexr_config_file})
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_STRING.*$")
|
||||
string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP})
|
||||
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_MAJOR.*$")
|
||||
string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP})
|
||||
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_MINOR.*$")
|
||||
string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP})
|
||||
endif()
|
||||
|
||||
foreach(OPENEXR_LIB
|
||||
IlmImf
|
||||
IlmImfUtil
|
||||
Half
|
||||
Iex
|
||||
IexMath
|
||||
Imath
|
||||
IlmThread)
|
||||
|
||||
# OpenEXR libraries may be suffixed with the version number, so we search
|
||||
# using both versioned and unversioned names.
|
||||
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE
|
||||
NAMES
|
||||
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s
|
||||
${OPENEXR_LIB}_s
|
||||
|
||||
PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH
|
||||
)
|
||||
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_LIBRARY)
|
||||
|
||||
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE)
|
||||
list(APPEND OPENEXR_LIBRARY_RELEASE ${OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE})
|
||||
endif()
|
||||
|
||||
# OpenEXR libraries may be suffixed with the version number, so we search
|
||||
# using both versioned and unversioned names.
|
||||
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG
|
||||
NAMES
|
||||
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s_d
|
||||
${OPENEXR_LIB}_s_d
|
||||
|
||||
PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH
|
||||
)
|
||||
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_DEBUG_LIBRARY)
|
||||
|
||||
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG)
|
||||
list(APPEND OPENEXR_LIBRARY_DEBUG ${OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG})
|
||||
endif()
|
||||
endforeach(OPENEXR_LIB)
|
||||
|
||||
select_library_configurations(OPENEXR)
|
||||
target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARY})
|
||||
endif()
|
||||
endmacro()
|
|
@ -1,4 +1,4 @@
|
|||
Source: hifi-deps
|
||||
Version: 0
|
||||
Version: 0.1
|
||||
Description: Collected dependencies for High Fidelity applications
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib
|
||||
|
|
4
cmake/ports/openexr/CONTROL
Normal file
4
cmake/ports/openexr/CONTROL
Normal file
|
@ -0,0 +1,4 @@
|
|||
Source: openexr
|
||||
Version: 2.3.0-2
|
||||
Description: OpenEXR is a high dynamic-range (HDR) image file format developed by Industrial Light & Magic for use in computer imaging applications
|
||||
Build-Depends: zlib
|
87
cmake/ports/openexr/FindOpenEXR.cmake
Normal file
87
cmake/ports/openexr/FindOpenEXR.cmake
Normal file
|
@ -0,0 +1,87 @@
|
|||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h)
|
||||
find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR)
|
||||
|
||||
file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H)
|
||||
|
||||
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}")
|
||||
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}")
|
||||
set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}")
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
if(NOT OpenEXR_BASE_LIBRARY)
|
||||
find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_BASE)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_UTIL_LIBRARY)
|
||||
find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_UTIL)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_HALF_LIBRARY)
|
||||
find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_HALF)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_IEX_LIBRARY)
|
||||
find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_IEX)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_MATH_LIBRARY)
|
||||
find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_MATH)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_THREAD_LIBRARY)
|
||||
find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_THREAD)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_IEXMATH_LIBRARY)
|
||||
find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d)
|
||||
select_library_configurations(OpenEXR_IEXMATH)
|
||||
endif()
|
||||
|
||||
set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}")
|
||||
set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}")
|
||||
set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}")
|
||||
set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}")
|
||||
set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}")
|
||||
set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}")
|
||||
|
||||
set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}")
|
||||
|
||||
set(OpenEXR_LIBRARIES
|
||||
${OpenEXR_LIBRARY}
|
||||
${OpenEXR_MATH_LIBRARY}
|
||||
${OpenEXR_IEXMATH_LIBRARY}
|
||||
${OpenEXR_UTIL_LIBRARY}
|
||||
${OpenEXR_HALF_LIBRARY}
|
||||
${OpenEXR_IEX_LIBRARY}
|
||||
${OpenEXR_THREAD_LIBRARY}
|
||||
)
|
||||
|
||||
set(OPENEXR_LIBRARIES
|
||||
${OPENEXR_HALF_LIBRARY}
|
||||
${OPENEXR_IEX_LIBRARY}
|
||||
${OPENEXR_IMATH_LIBRARY}
|
||||
${OPENEXR_ILMIMF_LIBRARY}
|
||||
${OPENEXR_ILMTHREAD_LIBRARY}
|
||||
)
|
||||
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS)
|
||||
|
||||
if(OpenEXR_FOUND)
|
||||
set(OPENEXR_FOUND 1)
|
||||
endif()
|
19
cmake/ports/openexr/fix_install_ilmimf.patch
Normal file
19
cmake/ports/openexr/fix_install_ilmimf.patch
Normal file
|
@ -0,0 +1,19 @@
|
|||
diff --git a/OpenEXR/IlmImf/CMakeLists.txt b/OpenEXR/IlmImf/CMakeLists.txt
|
||||
index e1a8740..d31cf68 100644
|
||||
--- a/OpenEXR/IlmImf/CMakeLists.txt
|
||||
+++ b/OpenEXR/IlmImf/CMakeLists.txt
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
SET(CMAKE_INCLUDE_CURRENT_DIR 1)
|
||||
|
||||
-IF (WIN32)
|
||||
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/bin)
|
||||
- SET(WORKING_DIR ${RUNTIME_DIR})
|
||||
-ELSE ()
|
||||
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/lib)
|
||||
- SET(WORKING_DIR .)
|
||||
-ENDIF ()
|
||||
-
|
||||
SET(BUILD_B44EXPLOGTABLE OFF)
|
||||
IF (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/b44ExpLogTable.h")
|
||||
SET(BUILD_B44EXPLOGTABLE ON)
|
74
cmake/ports/openexr/portfile.cmake
Normal file
74
cmake/ports/openexr/portfile.cmake
Normal file
|
@ -0,0 +1,74 @@
|
|||
include(vcpkg_common_functions)
|
||||
|
||||
set(OPENEXR_VERSION 2.3.0)
|
||||
set(OPENEXR_HASH 268ae64b40d21d662f405fba97c307dad1456b7d996a447aadafd41b640ca736d4851d9544b4741a94e7b7c335fe6e9d3b16180e710671abfc0c8b2740b147b2)
|
||||
|
||||
vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO openexr/openexr
|
||||
REF v${OPENEXR_VERSION}
|
||||
SHA512 ${OPENEXR_HASH}
|
||||
HEAD_REF master
|
||||
PATCHES "fix_install_ilmimf.patch"
|
||||
)
|
||||
|
||||
set(OPENEXR_STATIC ON)
|
||||
set(OPENEXR_SHARED OFF)
|
||||
|
||||
vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH}
|
||||
PREFER_NINJA
|
||||
OPTIONS
|
||||
-DOPENEXR_BUILD_PYTHON_LIBS=OFF
|
||||
-DOPENEXR_BUILD_VIEWERS=OFF
|
||||
-DOPENEXR_ENABLE_TESTS=OFF
|
||||
-DOPENEXR_RUN_FUZZ_TESTS=OFF
|
||||
-DOPENEXR_BUILD_SHARED=${OPENEXR_SHARED}
|
||||
-DOPENEXR_BUILD_STATIC=${OPENEXR_STATIC}
|
||||
OPTIONS_DEBUG
|
||||
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}/debug
|
||||
OPTIONS_RELEASE
|
||||
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR})
|
||||
|
||||
vcpkg_install_cmake()
|
||||
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share)
|
||||
|
||||
# NOTE: Only use ".exe" extension on Windows executables.
|
||||
# Is there a cleaner way to do this?
|
||||
if(WIN32)
|
||||
set(EXECUTABLE_SUFFIX ".exe")
|
||||
else()
|
||||
set(EXECUTABLE_SUFFIX "")
|
||||
endif()
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrenvmap${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrheader${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmakepreview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmaketiled${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultipart${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultiview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrstdattr${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrenvmap${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrheader${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmakepreview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmaketiled${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultipart${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultiview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrstdattr${EXECUTABLE_SUFFIX})
|
||||
|
||||
vcpkg_copy_pdbs()
|
||||
|
||||
if (OPENEXR_STATIC)
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin)
|
||||
endif()
|
||||
|
||||
if (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(OPENEXR_PORT_DIR "openexr")
|
||||
else()
|
||||
set(OPENEXR_PORT_DIR "OpenEXR")
|
||||
endif()
|
||||
|
||||
file(COPY ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})
|
||||
file(RENAME ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/LICENSE ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/copyright)
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/FindOpenEXR.cmake DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})
|
|
@ -1734,7 +1734,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
|
|||
f.write(data);
|
||||
OctreeUtils::RawEntityData entityData;
|
||||
if (entityData.readOctreeDataInfoFromData(data)) {
|
||||
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.version;
|
||||
qCDebug(domain_server) << "Wrote new entities file" << entityData.id << entityData.dataVersion;
|
||||
} else {
|
||||
qCDebug(domain_server) << "Failed to read new octree data info";
|
||||
}
|
||||
|
|
|
@ -6709,6 +6709,11 @@ void Application::updateRenderArgs(float deltaTime) {
|
|||
// Configure the type of display / stereo
|
||||
appRenderArgs._renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR);
|
||||
}
|
||||
|
||||
appRenderArgs._renderArgs._stencilMode = getActiveDisplayPlugin()->getStencilMaskMode();
|
||||
if (appRenderArgs._renderArgs._stencilMode == StencilMode::MESH) {
|
||||
appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -497,7 +497,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
// it might not fire until after we create a new instance for the same remote avatar, which creates a race
|
||||
// on the creation of entities for that avatar instance and the deletion of entities for this instance
|
||||
avatar->removeAvatarEntitiesFromTree();
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == KillAvatarReason::NoReason) {
|
||||
emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID());
|
||||
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <QJsonObject>
|
||||
#include <DependencyManager.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <EntityItemID.h>
|
||||
#include "AccountManager.h"
|
||||
|
||||
|
||||
|
@ -65,7 +66,7 @@ signals:
|
|||
void availableUpdatesResult(QJsonObject result);
|
||||
void updateItemResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
|
||||
|
||||
public slots:
|
||||
void buySuccess(QNetworkReply* reply);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <QPixmap>
|
||||
|
||||
#include <EntityItemID.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class QmlCommerce : public QObject, public Dependency {
|
||||
|
@ -49,7 +50,7 @@ signals:
|
|||
void availableUpdatesResult(QJsonObject result);
|
||||
void updateItemResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
void updateCertificateStatus(const EntityItemID& entityID, uint certStatus);
|
||||
|
||||
void transferAssetToNodeResult(QJsonObject result);
|
||||
void transferAssetToUsernameResult(QJsonObject result);
|
||||
|
|
|
@ -816,18 +816,18 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
|
|||
|
||||
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||
int status;
|
||||
int certIDByteArraySize;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&idByteArraySize);
|
||||
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
|
||||
if (challengeOriginatedFromClient) {
|
||||
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
// "encryptedText" is now a series of random bytes, a nonce
|
||||
QByteArray certID = packet->read(certIDByteArraySize);
|
||||
QByteArray id = packet->read(idByteArraySize);
|
||||
QByteArray text = packet->read(textByteArraySize);
|
||||
QByteArray challengingNodeUUID;
|
||||
if (challengeOriginatedFromClient) {
|
||||
|
@ -853,32 +853,32 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
|
|||
textByteArray = sig.toUtf8();
|
||||
}
|
||||
textByteArraySize = textByteArray.size();
|
||||
int certIDSize = certID.size();
|
||||
int idSize = id.size();
|
||||
// setup the packet
|
||||
if (challengeOriginatedFromClient) {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
|
||||
textPacket->writePrimitive(certIDSize);
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||
textPacket->write(certID);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
textPacket->write(challengingNodeUUID);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID;
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), *sendingNode);
|
||||
} else {
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true);
|
||||
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
textPacket->writePrimitive(certIDSize);
|
||||
textPacket->writePrimitive(idSize);
|
||||
textPacket->writePrimitive(textByteArraySize);
|
||||
textPacket->write(certID);
|
||||
textPacket->write(id);
|
||||
textPacket->write(textByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID;
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
|
||||
|
||||
nodeList->sendPacket(std::move(textPacket), *sendingNode);
|
||||
}
|
||||
|
|
|
@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
|
|||
|
||||
setLastInspectedEntity(entityID);
|
||||
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -328,31 +328,31 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
|
|||
} else {
|
||||
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
|
||||
|
||||
QByteArray certID = entityProperties.getCertificateID().toUtf8();
|
||||
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(certID, ownerKey);
|
||||
QByteArray id = entityID.toByteArray();
|
||||
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(entityID, ownerKey);
|
||||
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
|
||||
|
||||
int certIDByteArraySize = certID.length();
|
||||
int idByteArraySize = id.length();
|
||||
int textByteArraySize = text.length();
|
||||
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||
certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
|
||||
idByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(idByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(textByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
|
||||
challengeOwnershipPacket->write(certID);
|
||||
challengeOwnershipPacket->write(id);
|
||||
challengeOwnershipPacket->write(text);
|
||||
challengeOwnershipPacket->write(nodeToChallengeByteArray);
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
|
||||
|
||||
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
|
||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityID));
|
||||
return;
|
||||
} else {
|
||||
startChallengeOwnershipTimer();
|
||||
startChallengeOwnershipTimer(entityID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -370,14 +370,14 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
|
|||
// so they always pass Ownership Verification. It's necessary to emit this signal
|
||||
// so that the Inspection Certificate can continue its information-grabbing process.
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
}
|
||||
} else {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||
emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityID);
|
||||
qCDebug(context_overlay) << "Entity" << entityID << "failed static certificate verification!";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,14 +395,14 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
|
|||
}
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::startChallengeOwnershipTimer() {
|
||||
void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags);
|
||||
|
||||
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
qCDebug(entities) << "Ownership challenge timed out for" << entityItemID;
|
||||
emit ledger->updateCertificateStatus(entityItemID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(entityItemID);
|
||||
});
|
||||
|
||||
_challengeOwnershipTimeoutTimer.start(5000);
|
||||
|
@ -413,23 +413,22 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer
|
|||
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
|
||||
int certIDByteArraySize;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&idByteArraySize);
|
||||
packet->readPrimitive(&textByteArraySize);
|
||||
|
||||
QString certID(packet->read(certIDByteArraySize));
|
||||
EntityItemID id(packet->read(idByteArraySize));
|
||||
QString text(packet->read(textByteArraySize));
|
||||
|
||||
EntityItemID id;
|
||||
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(certID, text, id);
|
||||
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(id, text);
|
||||
|
||||
if (verificationSuccess) {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(_lastInspectedEntity);
|
||||
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(id);
|
||||
} else {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public:
|
|||
ContextOverlayInterface();
|
||||
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
||||
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
||||
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
|
||||
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); }
|
||||
void setEnabled(bool enabled);
|
||||
bool getEnabled() { return _enabled; }
|
||||
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
||||
|
@ -83,7 +83,6 @@ private:
|
|||
EntityItemID _mouseDownEntity;
|
||||
quint64 _mouseDownEntityTimestamp;
|
||||
EntityItemID _currentEntityWithContextOverlay;
|
||||
EntityItemID _lastInspectedEntity;
|
||||
QString _entityMarketplaceID;
|
||||
bool _contextOverlayJustClicked { false };
|
||||
|
||||
|
@ -94,7 +93,7 @@ private:
|
|||
|
||||
void deletingEntity(const EntityItemID& entityItemID);
|
||||
|
||||
Q_INVOKABLE void startChallengeOwnershipTimer();
|
||||
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
|
||||
QTimer _challengeOwnershipTimeoutTimer;
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <QtCore/QFile>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <QDir>
|
||||
#include <QImageReader>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include "Baker.h"
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <QtCore/QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
class TextureFileNamer {
|
||||
public:
|
||||
|
|
|
@ -109,7 +109,7 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() {
|
|||
return Parent::internalActivate();
|
||||
}
|
||||
|
||||
void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
auto& virtualPadManager = VirtualPad::Manager::instance();
|
||||
if(virtualPadManager.getLeftVirtualPad()->isShown()) {
|
||||
|
@ -121,7 +121,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin
|
|||
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
|
@ -140,7 +140,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin
|
|||
});
|
||||
}
|
||||
#endif
|
||||
Parent::compositeExtra(compositeFramebuffer);
|
||||
Parent::compositeExtra();
|
||||
}
|
||||
|
||||
static const uint32_t MIN_THROTTLE_CHECK_FRAMES = 60;
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
|
||||
virtual bool isThrottled() const override;
|
||||
|
||||
virtual void compositeExtra(const gpu::FramebufferPointer&) override;
|
||||
virtual void compositeExtra() override;
|
||||
|
||||
virtual void pluginUpdate() override {};
|
||||
|
||||
|
|
|
@ -379,6 +379,14 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
scissorState->setDepthTest(gpu::State::DepthTest(false));
|
||||
scissorState->setScissorEnable(true);
|
||||
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB);
|
||||
#else
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
#endif
|
||||
_simplePipeline = gpu::Pipeline::create(program, scissorState);
|
||||
}
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB);
|
||||
|
@ -388,59 +396,29 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
_presentPipeline = gpu::Pipeline::create(program, scissorState);
|
||||
}
|
||||
|
||||
|
||||
// HUD operator
|
||||
{
|
||||
gpu::PipelinePointer hudPipeline;
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
hudPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
gpu::PipelinePointer hudMirrorPipeline;
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX);
|
||||
hudMirrorPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
|
||||
_hudOperator = [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer& compositeFramebuffer, bool mirror) {
|
||||
auto hudStereo = isStereo();
|
||||
auto hudCompositeFramebufferSize = compositeFramebuffer->getSize();
|
||||
std::array<glm::ivec4, 2> hudEyeViewports;
|
||||
for_each_eye([&](Eye eye) {
|
||||
hudEyeViewports[eye] = eyeViewport(eye);
|
||||
});
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.enableStereo(false);
|
||||
batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline);
|
||||
batch.setResourceTexture(0, hudTexture);
|
||||
if (hudStereo) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(hudEyeViewports[eye]);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
_hudPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX);
|
||||
_mirrorHUDPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture);
|
||||
_cursorPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
}
|
||||
updateCompositeFramebuffer();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::uncustomizeContext() {
|
||||
_presentPipeline.reset();
|
||||
_cursorPipeline.reset();
|
||||
_hudOperator = DEFAULT_HUD_OPERATOR;
|
||||
_hudPipeline.reset();
|
||||
_mirrorHUDPipeline.reset();
|
||||
_compositeFramebuffer.reset();
|
||||
withPresentThreadLock([&] {
|
||||
_currentFrame.reset();
|
||||
_lastFrame = nullptr;
|
||||
|
@ -532,16 +510,24 @@ void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const {
|
|||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) {
|
||||
renderFromTexture(batch, texture, viewport, scissor, nullptr);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) {
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) {
|
||||
auto fbo = gpu::FramebufferPointer();
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(destFbo);
|
||||
batch.setFramebuffer(fbo);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
batch.setStateScissorRect(scissor);
|
||||
batch.setViewportTransform(viewport);
|
||||
batch.setResourceTexture(0, texture);
|
||||
#ifndef USE_GLES
|
||||
batch.setPipeline(_presentPipeline);
|
||||
#else
|
||||
batch.setPipeline(_simplePipeline);
|
||||
#endif
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
if (copyFbo) {
|
||||
gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight());
|
||||
|
@ -567,7 +553,7 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur
|
|||
batch.setViewportTransform(copyFboRect);
|
||||
batch.setStateScissorRect(copyFboRect);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, {0.0f, 0.0f, 0.0f, 1.0f});
|
||||
batch.blit(destFbo, sourceRect, copyFbo, copyRect);
|
||||
batch.blit(fbo, sourceRect, copyFbo, copyRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -595,14 +581,41 @@ void OpenGLDisplayPlugin::updateFrameData() {
|
|||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> OpenGLDisplayPlugin::getHUDOperator() {
|
||||
auto hudPipeline = _hudPipeline;
|
||||
auto hudMirrorPipeline = _mirrorHUDPipeline;
|
||||
auto hudStereo = isStereo();
|
||||
auto hudCompositeFramebufferSize = _compositeFramebuffer->getSize();
|
||||
std::array<glm::ivec4, 2> hudEyeViewports;
|
||||
for_each_eye([&](Eye eye) {
|
||||
hudEyeViewports[eye] = eyeViewport(eye);
|
||||
});
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) {
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.enableStereo(false);
|
||||
batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline);
|
||||
batch.setResourceTexture(0, hudTexture);
|
||||
if (hudStereo) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(hudEyeViewports[eye]);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositePointer() {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
auto cursorTransform = DependencyManager::get<CompositorHelper>()->getReticleTransform(glm::mat4());
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, cursorData.texture);
|
||||
batch.resetViewTransform();
|
||||
|
@ -613,13 +626,34 @@ void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compos
|
|||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), compositeFramebuffer->getSize()));
|
||||
batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenGLDisplayPlugin::compositeScene() {
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setViewportTransform(ivec4(uvec2(), _compositeFramebuffer->getSize()));
|
||||
batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize()));
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_simplePipeline);
|
||||
batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeLayers() {
|
||||
updateCompositeFramebuffer();
|
||||
|
||||
{
|
||||
PROFILE_RANGE_EX(render_detail, "compositeScene", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositeScene();
|
||||
}
|
||||
|
||||
#ifdef HIFI_ENABLE_NSIGHT_DEBUG
|
||||
if (false) // do not draw the HUD if running nsight debug
|
||||
#endif
|
||||
|
@ -633,35 +667,23 @@ void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi
|
|||
|
||||
{
|
||||
PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositeExtra(compositeFramebuffer);
|
||||
compositeExtra();
|
||||
}
|
||||
|
||||
// Draw the pointer last so it's on top of everything
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
if (compositorHelper->getReticleVisible()) {
|
||||
PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositePointer(compositeFramebuffer);
|
||||
compositePointer();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenGLDisplayPlugin::internalPresent() {
|
||||
render([&](gpu::Batch& batch) {
|
||||
// Note: _displayTexture must currently be the same size as the display.
|
||||
uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels();
|
||||
auto viewport = ivec4(uvec2(0), dims);
|
||||
|
||||
gpu::TexturePointer finalTexture;
|
||||
if (_displayTexture) {
|
||||
finalTexture = _displayTexture;
|
||||
} else if (compositeFramebuffer) {
|
||||
finalTexture = compositeFramebuffer->getRenderBuffer(0);
|
||||
} else {
|
||||
qCWarning(displayPlugins) << "No valid texture for output";
|
||||
}
|
||||
|
||||
if (finalTexture) {
|
||||
renderFromTexture(batch, finalTexture, viewport, viewport);
|
||||
}
|
||||
renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport);
|
||||
});
|
||||
swapBuffers();
|
||||
_presentRate.increment();
|
||||
|
@ -678,7 +700,7 @@ void OpenGLDisplayPlugin::present() {
|
|||
}
|
||||
incrementPresentCount();
|
||||
|
||||
if (_currentFrame && _currentFrame->framebuffer) {
|
||||
if (_currentFrame) {
|
||||
auto correction = getViewCorrection();
|
||||
getGLBackend()->setCameraCorrection(correction, _prevRenderView);
|
||||
_prevRenderView = correction * _currentFrame->view;
|
||||
|
@ -698,18 +720,18 @@ void OpenGLDisplayPlugin::present() {
|
|||
// Write all layers to a local framebuffer
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "composite", 0xff00ffff, frameId)
|
||||
compositeLayers(_currentFrame->framebuffer);
|
||||
compositeLayers();
|
||||
}
|
||||
|
||||
// Take the composite framebuffer and send it to the output device
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
|
||||
internalPresent(_currentFrame->framebuffer);
|
||||
internalPresent();
|
||||
}
|
||||
|
||||
gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory());
|
||||
} else if (alwaysPresent()) {
|
||||
internalPresent(nullptr);
|
||||
internalPresent();
|
||||
}
|
||||
_movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent));
|
||||
}
|
||||
|
@ -766,12 +788,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
|
|||
}
|
||||
|
||||
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
||||
if (!_currentFrame || !_currentFrame->framebuffer) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
auto compositeFramebuffer = _currentFrame->framebuffer;
|
||||
auto size = compositeFramebuffer->getSize();
|
||||
auto size = _compositeFramebuffer->getSize();
|
||||
if (isHmd()) {
|
||||
size.x /= 2;
|
||||
}
|
||||
|
@ -789,7 +806,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
|||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
|
||||
withOtherThreadContext([&] {
|
||||
glBackend->downloadFramebuffer(compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||
});
|
||||
return screenshot.mirrored(false, true);
|
||||
}
|
||||
|
@ -841,7 +858,7 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|||
}
|
||||
|
||||
ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const {
|
||||
auto vpSize = glm::uvec2(getRecommendedRenderSize());
|
||||
uvec2 vpSize = _compositeFramebuffer->getSize();
|
||||
vpSize.x /= 2;
|
||||
uvec2 vpPos;
|
||||
if (eye == Eye::Right) {
|
||||
|
@ -874,6 +891,14 @@ void OpenGLDisplayPlugin::render(std::function<void(gpu::Batch& batch)> f) {
|
|||
OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||
auto renderSize = glm::uvec2(getRecommendedRenderSize());
|
||||
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
||||
// _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y));
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) {
|
||||
#if !defined(USE_GLES)
|
||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
|
|
|
@ -94,10 +94,14 @@ protected:
|
|||
// is not populated
|
||||
virtual bool alwaysPresent() const { return false; }
|
||||
|
||||
void updateCompositeFramebuffer();
|
||||
|
||||
virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; }
|
||||
virtual void compositeLayers(const gpu::FramebufferPointer&);
|
||||
virtual void compositePointer(const gpu::FramebufferPointer&);
|
||||
virtual void compositeExtra(const gpu::FramebufferPointer&) {};
|
||||
virtual void compositeLayers();
|
||||
virtual void compositeScene();
|
||||
virtual std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator();
|
||||
virtual void compositePointer();
|
||||
virtual void compositeExtra() {};
|
||||
|
||||
// These functions must only be called on the presentation thread
|
||||
virtual void customizeContext();
|
||||
|
@ -112,10 +116,10 @@ protected:
|
|||
virtual void deactivateSession() {}
|
||||
|
||||
// Plugin specific functionality to send the composed scene to the output window or device
|
||||
virtual void internalPresent(const gpu::FramebufferPointer&);
|
||||
virtual void internalPresent();
|
||||
|
||||
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo = nullptr, const gpu::FramebufferPointer& copyFbo = nullptr);
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo);
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor);
|
||||
virtual void updateFrameData();
|
||||
virtual glm::mat4 getViewCorrection() { return glm::mat4(); }
|
||||
|
||||
|
@ -138,8 +142,14 @@ protected:
|
|||
gpu::FramePointer _currentFrame;
|
||||
gpu::Frame* _lastFrame { nullptr };
|
||||
mat4 _prevRenderView;
|
||||
gpu::FramebufferPointer _compositeFramebuffer;
|
||||
gpu::PipelinePointer _hudPipeline;
|
||||
gpu::PipelinePointer _mirrorHUDPipeline;
|
||||
gpu::ShaderPointer _mirrorHUDPS;
|
||||
gpu::PipelinePointer _simplePipeline;
|
||||
gpu::PipelinePointer _presentPipeline;
|
||||
gpu::PipelinePointer _cursorPipeline;
|
||||
gpu::TexturePointer _displayTexture;
|
||||
gpu::TexturePointer _displayTexture{};
|
||||
float _compositeHUDAlpha { 1.0f };
|
||||
|
||||
struct CursorData {
|
||||
|
@ -175,9 +185,5 @@ protected:
|
|||
// be serialized through this mutex
|
||||
mutable Mutex _presentMutex;
|
||||
float _hudAlpha{ 1.0f };
|
||||
|
||||
private:
|
||||
gpu::PipelinePointer _presentPipeline;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
|
||||
protected:
|
||||
void updatePresentPose() override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override {}
|
||||
void hmdPresent() override {}
|
||||
bool isHmdMounted() const override { return true; }
|
||||
bool internalActivate() override;
|
||||
private:
|
||||
|
|
|
@ -114,23 +114,20 @@ void HmdDisplayPlugin::internalDeactivate() {
|
|||
|
||||
void HmdDisplayPlugin::customizeContext() {
|
||||
Parent::customizeContext();
|
||||
_hudOperator = _hudRenderer.build();
|
||||
_hudRenderer.build();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::uncustomizeContext() {
|
||||
// This stops the weirdness where if the preview was disabled, on switching back to 2D,
|
||||
// the vsync was stuck in the disabled state. No idea why that happens though.
|
||||
_disablePreview = false;
|
||||
if (_currentFrame && _currentFrame->framebuffer) {
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(_currentFrame->framebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
});
|
||||
|
||||
}
|
||||
_hudRenderer = {};
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
});
|
||||
_hudRenderer = HUDRenderer();
|
||||
_previewTexture.reset();
|
||||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
@ -177,11 +174,11 @@ float HmdDisplayPlugin::getLeftCenterPixel() const {
|
|||
return leftCenterPixel;
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void HmdDisplayPlugin::internalPresent() {
|
||||
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
|
||||
|
||||
// Composite together the scene, hud and mouse cursor
|
||||
hmdPresent(compositeFramebuffer);
|
||||
hmdPresent();
|
||||
|
||||
if (_displayTexture) {
|
||||
// Note: _displayTexture must currently be the same size as the display.
|
||||
|
@ -263,7 +260,7 @@ void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeF
|
|||
|
||||
viewport.z *= 2;
|
||||
}
|
||||
renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, scissor, nullptr, fbo);
|
||||
renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor, fbo);
|
||||
});
|
||||
swapBuffers();
|
||||
|
||||
|
@ -348,7 +345,7 @@ glm::mat4 HmdDisplayPlugin::getViewCorrection() {
|
|||
}
|
||||
}
|
||||
|
||||
DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
||||
void HmdDisplayPlugin::HUDRenderer::build() {
|
||||
vertices = std::make_shared<gpu::Buffer>();
|
||||
indices = std::make_shared<gpu::Buffer>();
|
||||
|
||||
|
@ -383,7 +380,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
indexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE;
|
||||
|
||||
// Compute indices order
|
||||
std::vector<GLushort> indexData;
|
||||
std::vector<GLushort> indices;
|
||||
for (int i = 0; i < stacks - 1; i++) {
|
||||
for (int j = 0; j < slices - 1; j++) {
|
||||
GLushort bottomLeftIndex = i * slices + j;
|
||||
|
@ -391,21 +388,24 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
GLushort topLeftIndex = bottomLeftIndex + slices;
|
||||
GLushort topRightIndex = topLeftIndex + 1;
|
||||
// FIXME make a z-order curve for better vertex cache locality
|
||||
indexData.push_back(topLeftIndex);
|
||||
indexData.push_back(bottomLeftIndex);
|
||||
indexData.push_back(topRightIndex);
|
||||
indices.push_back(topLeftIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(topRightIndex);
|
||||
|
||||
indexData.push_back(topRightIndex);
|
||||
indexData.push_back(bottomLeftIndex);
|
||||
indexData.push_back(bottomRightIndex);
|
||||
indices.push_back(topRightIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(bottomRightIndex);
|
||||
}
|
||||
}
|
||||
indices->append(indexData);
|
||||
this->indices->append(indices);
|
||||
format = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
|
||||
format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||
uniformsBuffer = std::make_shared<gpu::Buffer>(sizeof(Uniforms), nullptr);
|
||||
updatePipeline();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::HUDRenderer::updatePipeline() {
|
||||
if (!pipeline) {
|
||||
auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui);
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
@ -416,6 +416,10 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
|
||||
pipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) {
|
||||
updatePipeline();
|
||||
|
||||
auto hudPipeline = pipeline;
|
||||
auto hudFormat = format;
|
||||
|
@ -424,9 +428,9 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
auto hudUniformBuffer = uniformsBuffer;
|
||||
auto hudUniforms = uniforms;
|
||||
auto hudIndexCount = indexCount;
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer&, const bool mirror) {
|
||||
if (pipeline && hudTexture) {
|
||||
batch.setPipeline(pipeline);
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) {
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.setPipeline(hudPipeline);
|
||||
|
||||
batch.setInputFormat(hudFormat);
|
||||
gpu::BufferView posView(hudVertices, VERTEX_OFFSET, hudVertices->getSize(), VERTEX_STRIDE, hudFormat->getAttributes().at(gpu::Stream::POSITION)._element);
|
||||
|
@ -450,7 +454,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
};
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void HmdDisplayPlugin::compositePointer() {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
@ -459,7 +463,7 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite
|
|||
render([&](gpu::Batch& batch) {
|
||||
// FIXME use standard gpu stereo rendering for this.
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, cursorData.texture);
|
||||
batch.resetViewTransform();
|
||||
|
@ -474,6 +478,10 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite
|
|||
});
|
||||
}
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> HmdDisplayPlugin::getHUDOperator() {
|
||||
return _hudRenderer.render(*this);
|
||||
}
|
||||
|
||||
HmdDisplayPlugin::~HmdDisplayPlugin() {
|
||||
}
|
||||
|
||||
|
|
|
@ -48,20 +48,23 @@ public:
|
|||
|
||||
void pluginUpdate() override {};
|
||||
|
||||
virtual StencilMode getStencilMaskMode() const override { return StencilMode::PAINT; }
|
||||
|
||||
signals:
|
||||
void hmdMountedChanged();
|
||||
void hmdVisibleChanged(bool visible);
|
||||
|
||||
protected:
|
||||
virtual void hmdPresent(const gpu::FramebufferPointer&) = 0;
|
||||
virtual void hmdPresent() = 0;
|
||||
virtual bool isHmdMounted() const = 0;
|
||||
virtual void postPreview() {};
|
||||
virtual void updatePresentPose();
|
||||
|
||||
bool internalActivate() override;
|
||||
void internalDeactivate() override;
|
||||
void compositePointer(const gpu::FramebufferPointer&) override;
|
||||
void internalPresent(const gpu::FramebufferPointer&) override;
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator() override;
|
||||
void compositePointer() override;
|
||||
void internalPresent() override;
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void updateFrameData() override;
|
||||
|
@ -119,6 +122,8 @@ private:
|
|||
static const size_t TEXTURE_OFFSET { offsetof(Vertex, uv) };
|
||||
static const int VERTEX_STRIDE { sizeof(Vertex) };
|
||||
|
||||
HUDOperator build();
|
||||
void build();
|
||||
void updatePipeline();
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> render(HmdDisplayPlugin& plugin);
|
||||
} _hudRenderer;
|
||||
};
|
||||
|
|
|
@ -37,13 +37,13 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
void InterleavedStereoDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void InterleavedStereoDisplayPlugin::internalPresent() {
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(gpu::FramebufferPointer());
|
||||
batch.setViewportTransform(ivec4(uvec2(0), getSurfacePixels()));
|
||||
batch.setResourceTexture(0, compositeFramebuffer->getRenderBuffer(0));
|
||||
batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0));
|
||||
batch.setPipeline(_interleavedPresentPipeline);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ protected:
|
|||
// initialize OpenGL context settings needed by the plugin
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void internalPresent(const gpu::FramebufferPointer&) override;
|
||||
void internalPresent() override;
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
|
|
|
@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
|
|||
}
|
||||
|
||||
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||
// because the caching system only allows one Geometry per url, and because this url might also be used
|
||||
// as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it
|
||||
// will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and
|
||||
// parse it twice.
|
||||
auto currentCompoundShapeURL = getCompoundShapeURL();
|
||||
ModelEntityItem::setCompoundShapeURL(url);
|
||||
if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
//
|
||||
// RenderableParticleEffectEntityItem.cpp
|
||||
// interface/src
|
||||
//
|
||||
|
@ -9,12 +9,12 @@
|
|||
//
|
||||
|
||||
#include "RenderableParticleEffectEntityItem.h"
|
||||
|
||||
#include <StencilMaskPass.h>
|
||||
|
||||
#include <GeometryCache.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
@ -79,6 +79,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
|
|||
return true;
|
||||
}
|
||||
|
||||
if (_shapeType != entity->getShapeType()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_compoundShapeURL != entity->getCompoundShapeURL()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -87,10 +95,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
|||
if (!newParticleProperties.valid()) {
|
||||
qCWarning(entitiesrenderer) << "Bad particle properties";
|
||||
}
|
||||
|
||||
if (resultWithReadLock<bool>([&]{ return _particleProperties != newParticleProperties; })) {
|
||||
|
||||
if (resultWithReadLock<bool>([&] { return _particleProperties != newParticleProperties; })) {
|
||||
_timeUntilNextEmit = 0;
|
||||
withWriteLock([&]{
|
||||
withWriteLock([&] {
|
||||
_particleProperties = newParticleProperties;
|
||||
if (!_prevEmitterShouldTrailInitialized) {
|
||||
_prevEmitterShouldTrailInitialized = true;
|
||||
|
@ -101,13 +109,20 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
|||
|
||||
withWriteLock([&] {
|
||||
_pulseProperties = entity->getPulseProperties();
|
||||
_shapeType = entity->getShapeType();
|
||||
QString compoundShapeURL = entity->getCompoundShapeURL();
|
||||
if (_compoundShapeURL != compoundShapeURL) {
|
||||
_compoundShapeURL = compoundShapeURL;
|
||||
_hasComputedTriangles = false;
|
||||
fetchGeometryResource();
|
||||
}
|
||||
});
|
||||
_emitting = entity->getIsEmitting();
|
||||
|
||||
bool textureEmpty = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
|
||||
bool textureEmpty = resultWithReadLock<bool>([&] { return _particleProperties.textures.isEmpty(); });
|
||||
if (textureEmpty) {
|
||||
if (_networkTexture) {
|
||||
withWriteLock([&] {
|
||||
withWriteLock([&] {
|
||||
_networkTexture.reset();
|
||||
});
|
||||
}
|
||||
|
@ -116,11 +131,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
|||
entity->setVisuallyReady(true);
|
||||
});
|
||||
} else {
|
||||
bool textureNeedsUpdate = resultWithReadLock<bool>([&]{
|
||||
bool textureNeedsUpdate = resultWithReadLock<bool>([&] {
|
||||
return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
|
||||
});
|
||||
if (textureNeedsUpdate) {
|
||||
withWriteLock([&] {
|
||||
withWriteLock([&] {
|
||||
_networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
|
||||
});
|
||||
}
|
||||
|
@ -144,7 +159,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
|||
void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
|
||||
// Fill in Uniforms structure
|
||||
ParticleUniforms particleUniforms;
|
||||
withReadLock([&]{
|
||||
withReadLock([&] {
|
||||
particleUniforms.radius.start = _particleProperties.radius.range.start;
|
||||
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
|
||||
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
|
||||
|
@ -181,9 +196,32 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
|
|||
return _bound;
|
||||
}
|
||||
|
||||
static const size_t VERTEX_PER_PARTICLE = 4;
|
||||
// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions
|
||||
float importanceSample2DDimension(float startDim) {
|
||||
float dimension = 1.0f;
|
||||
if (startDim < 1.0f) {
|
||||
float innerDimensionSquared = startDim * startDim;
|
||||
float outerDimensionSquared = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2);
|
||||
float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared);
|
||||
dimension = std::sqrt(randDimensionSquared);
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) {
|
||||
float importanceSample3DDimension(float startDim) {
|
||||
float dimension = 1.0f;
|
||||
if (startDim < 1.0f) {
|
||||
float innerDimensionCubed = startDim * startDim * startDim;
|
||||
float outerDimensionCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
|
||||
float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed);
|
||||
dimension = std::cbrt(randDimensionCubed);
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
|
||||
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
|
||||
const TriangleInfo& triangleInfo) {
|
||||
CpuParticle particle;
|
||||
|
||||
const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
|
||||
|
@ -221,33 +259,130 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
|
|||
|
||||
float azimuth;
|
||||
if (azimuthFinish >= azimuthStart) {
|
||||
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
|
||||
azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
|
||||
} else {
|
||||
azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
|
||||
}
|
||||
// TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too
|
||||
|
||||
if (emitDimensions == Vectors::ZERO) {
|
||||
// Point
|
||||
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
|
||||
} else {
|
||||
// Ellipsoid
|
||||
float radiusScale = 1.0f;
|
||||
if (emitRadiusStart < 1.0f) {
|
||||
float randRadius =
|
||||
emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
|
||||
radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
|
||||
glm::vec3 emitPosition;
|
||||
switch (shapeType) {
|
||||
case SHAPE_TYPE_BOX: {
|
||||
glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||
|
||||
int side = randIntInRange(0, 5);
|
||||
int axis = side % 3;
|
||||
float direction = side > 2 ? 1.0f : -1.0f;
|
||||
|
||||
emitDirection[axis] = direction;
|
||||
emitPosition[axis] = direction * dim[axis];
|
||||
axis = (axis + 1) % 3;
|
||||
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||
axis = (axis + 1) % 3;
|
||||
emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||
break;
|
||||
}
|
||||
|
||||
case SHAPE_TYPE_CYLINDER_X:
|
||||
case SHAPE_TYPE_CYLINDER_Y:
|
||||
case SHAPE_TYPE_CYLINDER_Z: {
|
||||
glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||
int axis = shapeType - SHAPE_TYPE_CYLINDER_X;
|
||||
|
||||
emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f);
|
||||
emitDirection[axis] = 0.0f;
|
||||
axis = (axis + 1) % 3;
|
||||
emitPosition[axis] = radii[axis] * glm::cos(azimuth);
|
||||
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
|
||||
axis = (axis + 1) % 3;
|
||||
emitPosition[axis] = radii[axis] * glm::sin(azimuth);
|
||||
emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
|
||||
emitDirection = glm::normalize(emitDirection);
|
||||
break;
|
||||
}
|
||||
|
||||
case SHAPE_TYPE_CIRCLE: {
|
||||
glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
|
||||
float x = radii.x * glm::cos(azimuth);
|
||||
float z = radii.y * glm::sin(azimuth);
|
||||
emitPosition = glm::vec3(x, 0.0f, z);
|
||||
emitDirection = Vectors::UP;
|
||||
break;
|
||||
}
|
||||
case SHAPE_TYPE_PLANE: {
|
||||
glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
|
||||
|
||||
int side = randIntInRange(0, 3);
|
||||
int axis = side % 2;
|
||||
float direction = side > 1 ? 1.0f : -1.0f;
|
||||
|
||||
glm::vec2 pos;
|
||||
pos[axis] = direction * dim[axis];
|
||||
axis = (axis + 1) % 2;
|
||||
pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
|
||||
|
||||
emitPosition = glm::vec3(pos.x, 0.0f, pos.y);
|
||||
emitDirection = Vectors::UP;
|
||||
break;
|
||||
}
|
||||
|
||||
case SHAPE_TYPE_COMPOUND: {
|
||||
// if we get here we know that geometryResource is loaded
|
||||
|
||||
size_t index = randFloat() * triangleInfo.totalSamples;
|
||||
Triangle triangle;
|
||||
for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) {
|
||||
size_t numSamples = triangleInfo.samplesPerTriangle[i];
|
||||
if (index < numSamples) {
|
||||
triangle = triangleInfo.triangles[i];
|
||||
break;
|
||||
}
|
||||
index -= numSamples;
|
||||
}
|
||||
|
||||
float edgeLength1 = glm::length(triangle.v1 - triangle.v0);
|
||||
float edgeLength2 = glm::length(triangle.v2 - triangle.v1);
|
||||
float edgeLength3 = glm::length(triangle.v0 - triangle.v2);
|
||||
|
||||
float perimeter = edgeLength1 + edgeLength2 + edgeLength3;
|
||||
float fraction1 = randFloatInRange(0.0f, 1.0f);
|
||||
float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f);
|
||||
float fraction2 = fraction1 - edgeLength1 / perimeter;
|
||||
float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f);
|
||||
float fraction3 = fraction2 - edgeLength2 / perimeter;
|
||||
float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f);
|
||||
|
||||
float dim = importanceSample2DDimension(emitRadiusStart);
|
||||
triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform);
|
||||
glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f;
|
||||
glm::vec3 v0 = (dim * (triangle.v0 - center)) + center;
|
||||
glm::vec3 v1 = (dim * (triangle.v1 - center)) + center;
|
||||
glm::vec3 v2 = (dim * (triangle.v2 - center)) + center;
|
||||
|
||||
emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1);
|
||||
emitDirection = triangle.getNormal();
|
||||
break;
|
||||
}
|
||||
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
case SHAPE_TYPE_ELLIPSOID:
|
||||
default: {
|
||||
glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
|
||||
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
|
||||
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
|
||||
float z = radii.z * glm::sin(elevation);
|
||||
emitPosition = glm::vec3(x, y, z);
|
||||
emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
|
||||
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
|
||||
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
|
||||
float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
|
||||
float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
|
||||
float z = radii.z * glm::sin(elevation);
|
||||
glm::vec3 emitPosition = glm::vec3(x, y, z);
|
||||
emitDirection = glm::normalize(glm::vec3(
|
||||
radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
|
||||
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
|
||||
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
|
||||
));
|
||||
particle.relativePosition += emitOrientation * emitPosition;
|
||||
}
|
||||
}
|
||||
|
@ -267,20 +402,28 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
|||
const auto now = usecTimestampNow();
|
||||
const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
|
||||
_lastSimulated = now;
|
||||
|
||||
|
||||
particle::Properties particleProperties;
|
||||
withReadLock([&]{
|
||||
ShapeType shapeType;
|
||||
GeometryResource::Pointer geometryResource;
|
||||
withReadLock([&] {
|
||||
particleProperties = _particleProperties;
|
||||
shapeType = _shapeType;
|
||||
geometryResource = _geometryResource;
|
||||
});
|
||||
|
||||
const auto& modelTransform = getModelTransform();
|
||||
if (_emitting && particleProperties.emitting()) {
|
||||
if (_emitting && particleProperties.emitting() &&
|
||||
(shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) {
|
||||
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
|
||||
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
|
||||
auto timeRemaining = interval;
|
||||
while (timeRemaining > _timeUntilNextEmit) {
|
||||
if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) {
|
||||
computeTriangles(geometryResource->getHFMModel());
|
||||
}
|
||||
// emit particle
|
||||
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties));
|
||||
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
|
||||
_timeUntilNextEmit = emitInterval;
|
||||
if (emitInterval < timeRemaining) {
|
||||
timeRemaining -= emitInterval;
|
||||
|
@ -297,7 +440,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
|||
}
|
||||
|
||||
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
|
||||
// update the particles
|
||||
// update the particles
|
||||
for (auto& particle : _cpuParticles) {
|
||||
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
|
||||
if (_prevEmitterShouldTrail) {
|
||||
|
@ -313,7 +456,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
|||
static GpuParticles gpuParticles;
|
||||
gpuParticles.clear();
|
||||
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
|
||||
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) {
|
||||
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) {
|
||||
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
|
||||
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
|
||||
});
|
||||
|
@ -356,5 +499,131 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
|
|||
batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle));
|
||||
|
||||
auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
|
||||
static const size_t VERTEX_PER_PARTICLE = 4;
|
||||
batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityRenderer::fetchGeometryResource() {
|
||||
QUrl hullURL(_compoundShapeURL);
|
||||
if (hullURL.isEmpty()) {
|
||||
_geometryResource.reset();
|
||||
} else {
|
||||
_geometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this is very similar to Model::calculateTriangleSets
|
||||
void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) {
|
||||
PROFILE_RANGE(render, __FUNCTION__);
|
||||
|
||||
int numberOfMeshes = hfmModel.meshes.size();
|
||||
|
||||
_hasComputedTriangles = true;
|
||||
_triangleInfo.triangles.clear();
|
||||
_triangleInfo.samplesPerTriangle.clear();
|
||||
|
||||
std::vector<float> areas;
|
||||
float minArea = FLT_MAX;
|
||||
AABox bounds;
|
||||
|
||||
for (int i = 0; i < numberOfMeshes; i++) {
|
||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||
|
||||
const int numberOfParts = mesh.parts.size();
|
||||
for (int j = 0; j < numberOfParts; j++) {
|
||||
const HFMMeshPart& part = mesh.parts.at(j);
|
||||
|
||||
const int INDICES_PER_TRIANGLE = 3;
|
||||
const int INDICES_PER_QUAD = 4;
|
||||
const int TRIANGLES_PER_QUAD = 2;
|
||||
|
||||
// tell our triangleSet how many triangles to expect.
|
||||
int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD;
|
||||
int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE;
|
||||
int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris;
|
||||
_triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles);
|
||||
areas.reserve(areas.size() + totalTriangles);
|
||||
|
||||
auto meshTransform = hfmModel.offset * mesh.modelTransform;
|
||||
|
||||
if (part.quadIndices.size() > 0) {
|
||||
int vIndex = 0;
|
||||
for (int q = 0; q < numberOfQuads; q++) {
|
||||
int i0 = part.quadIndices[vIndex++];
|
||||
int i1 = part.quadIndices[vIndex++];
|
||||
int i2 = part.quadIndices[vIndex++];
|
||||
int i3 = part.quadIndices[vIndex++];
|
||||
|
||||
// track the model space version... these points will be transformed by the FST's offset,
|
||||
// which includes the scaling, rotation, and translation specified by the FST/FBX,
|
||||
// this can't change at runtime, so we can safely store these in our TriangleSet
|
||||
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
|
||||
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
|
||||
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
|
||||
glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f));
|
||||
|
||||
Triangle tri1 = { v0, v1, v3 };
|
||||
Triangle tri2 = { v1, v2, v3 };
|
||||
_triangleInfo.triangles.push_back(tri1);
|
||||
_triangleInfo.triangles.push_back(tri2);
|
||||
|
||||
float area1 = tri1.getArea();
|
||||
areas.push_back(area1);
|
||||
if (area1 > EPSILON) {
|
||||
minArea = std::min(minArea, area1);
|
||||
}
|
||||
|
||||
float area2 = tri2.getArea();
|
||||
areas.push_back(area2);
|
||||
if (area2 > EPSILON) {
|
||||
minArea = std::min(minArea, area2);
|
||||
}
|
||||
|
||||
bounds += v0;
|
||||
bounds += v1;
|
||||
bounds += v2;
|
||||
bounds += v3;
|
||||
}
|
||||
}
|
||||
|
||||
if (part.triangleIndices.size() > 0) {
|
||||
int vIndex = 0;
|
||||
for (int t = 0; t < numberOfTris; t++) {
|
||||
int i0 = part.triangleIndices[vIndex++];
|
||||
int i1 = part.triangleIndices[vIndex++];
|
||||
int i2 = part.triangleIndices[vIndex++];
|
||||
|
||||
// track the model space version... these points will be transformed by the FST's offset,
|
||||
// which includes the scaling, rotation, and translation specified by the FST/FBX,
|
||||
// this can't change at runtime, so we can safely store these in our TriangleSet
|
||||
glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
|
||||
glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
|
||||
glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
|
||||
|
||||
Triangle tri = { v0, v1, v2 };
|
||||
_triangleInfo.triangles.push_back(tri);
|
||||
|
||||
float area = tri.getArea();
|
||||
areas.push_back(area);
|
||||
if (area > EPSILON) {
|
||||
minArea = std::min(minArea, area);
|
||||
}
|
||||
|
||||
bounds += v0;
|
||||
bounds += v1;
|
||||
bounds += v2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_triangleInfo.totalSamples = 0;
|
||||
for (auto& area : areas) {
|
||||
size_t numSamples = area / minArea;
|
||||
_triangleInfo.samplesPerTriangle.push_back(numSamples);
|
||||
_triangleInfo.totalSamples += numSamples;
|
||||
}
|
||||
|
||||
glm::vec3 scale = bounds.getScale();
|
||||
_triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter());
|
||||
}
|
|
@ -81,7 +81,18 @@ private:
|
|||
glm::vec2 spare;
|
||||
};
|
||||
|
||||
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties);
|
||||
void computeTriangles(const hfm::Model& hfmModel);
|
||||
bool _hasComputedTriangles{ false };
|
||||
struct TriangleInfo {
|
||||
std::vector<Triangle> triangles;
|
||||
std::vector<size_t> samplesPerTriangle;
|
||||
size_t totalSamples;
|
||||
glm::mat4 transform;
|
||||
} _triangleInfo;
|
||||
|
||||
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
|
||||
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
|
||||
const TriangleInfo& triangleInfo);
|
||||
void stepSimulation();
|
||||
|
||||
particle::Properties _particleProperties;
|
||||
|
@ -90,11 +101,16 @@ private:
|
|||
CpuParticles _cpuParticles;
|
||||
bool _emitting { false };
|
||||
uint64_t _timeUntilNextEmit { 0 };
|
||||
BufferPointer _particleBuffer{ std::make_shared<Buffer>() };
|
||||
BufferPointer _particleBuffer { std::make_shared<Buffer>() };
|
||||
BufferView _uniformBuffer;
|
||||
quint64 _lastSimulated { 0 };
|
||||
|
||||
PulsePropertyGroup _pulseProperties;
|
||||
ShapeType _shapeType;
|
||||
QString _compoundShapeURL;
|
||||
|
||||
void fetchGeometryResource();
|
||||
GeometryResource::Pointer _geometryResource;
|
||||
|
||||
NetworkTexturePointer _networkTexture;
|
||||
ScenePointer _scene;
|
||||
|
|
|
@ -1698,10 +1698,7 @@ AACube EntityItem::getQueryAACube(bool& success) const {
|
|||
}
|
||||
|
||||
bool EntityItem::shouldPuffQueryAACube() const {
|
||||
bool hasGrabs = _grabsLock.resultWithReadLock<bool>([&] {
|
||||
return _grabs.count() > 0;
|
||||
});
|
||||
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs;
|
||||
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent();
|
||||
}
|
||||
|
||||
// TODO: get rid of all users of this function...
|
||||
|
@ -1896,7 +1893,8 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) {
|
|||
|
||||
void EntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
||||
glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION));
|
||||
if (getUnscaledDimensions() != newDimensions) {
|
||||
const float MIN_SCALE_CHANGE_SQUARED = 1.0e-6f;
|
||||
if (glm::length2(getUnscaledDimensions() - newDimensions) > MIN_SCALE_CHANGE_SQUARED) {
|
||||
withWriteLock([&] {
|
||||
_unscaledDimensions = newDimensions;
|
||||
});
|
||||
|
@ -2086,7 +2084,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask
|
|||
} else {
|
||||
if (getDynamic()) {
|
||||
group = BULLET_COLLISION_GROUP_DYNAMIC;
|
||||
} else if (isMovingRelativeToParent() || hasActions()) {
|
||||
} else if (hasActions() || isMovingRelativeToParent()) {
|
||||
group = BULLET_COLLISION_GROUP_KINEMATIC;
|
||||
} else {
|
||||
group = BULLET_COLLISION_GROUP_STATIC;
|
||||
|
@ -3057,30 +3055,18 @@ bool EntityItem::getCollisionless() const {
|
|||
}
|
||||
|
||||
uint16_t EntityItem::getCollisionMask() const {
|
||||
uint16_t result;
|
||||
withReadLock([&] {
|
||||
result = _collisionMask;
|
||||
});
|
||||
return result;
|
||||
return _collisionMask;
|
||||
}
|
||||
|
||||
bool EntityItem::getDynamic() const {
|
||||
if (SHAPE_TYPE_STATIC_MESH == getShapeType()) {
|
||||
return false;
|
||||
}
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _dynamic;
|
||||
});
|
||||
return result;
|
||||
return _dynamic;
|
||||
}
|
||||
|
||||
bool EntityItem::getLocked() const {
|
||||
bool result;
|
||||
withReadLock([&] {
|
||||
result = _locked;
|
||||
});
|
||||
return result;
|
||||
return _locked;
|
||||
}
|
||||
|
||||
void EntityItem::setLocked(bool value) {
|
||||
|
@ -3152,7 +3138,6 @@ uint32_t EntityItem::getDirtyFlags() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
void EntityItem::markDirtyFlags(uint32_t mask) {
|
||||
withWriteLock([&] {
|
||||
mask &= Simulation::DIRTY_FLAGS;
|
||||
|
|
|
@ -448,7 +448,7 @@ public:
|
|||
bool clearActions(EntitySimulationPointer simulation);
|
||||
void setDynamicData(QByteArray dynamicData);
|
||||
const QByteArray getDynamicData() const;
|
||||
bool hasActions() const { return !_objectActions.empty(); }
|
||||
bool hasActions() const { return !_objectActions.empty() || !_grabActions.empty(); }
|
||||
QList<QUuid> getActionIDs() const { return _objectActions.keys(); }
|
||||
QVariantMap getActionArguments(const QUuid& actionID) const;
|
||||
void deserializeActions();
|
||||
|
|
|
@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() {
|
|||
addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
||||
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||
addShapeType(SHAPE_TYPE_ELLIPSOID);
|
||||
addShapeType(SHAPE_TYPE_CIRCLE);
|
||||
}
|
||||
|
||||
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
||||
|
@ -1114,23 +1115,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* default, particles emit along the entity's local z-axis, and <code>azimuthStart</code> and <code>azimuthFinish</code>
|
||||
* are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e.,
|
||||
* the particles emit vertically.
|
||||
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted.
|
||||
* @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted;
|
||||
* range <code>0.0</code> – <code>1.0</code> for the ellipsoid center to the ellipsoid surface, respectively.
|
||||
* Particles are emitted from the portion of the ellipsoid that lies between <code>emitRadiusStart</code> and the
|
||||
* ellipsoid's surface.
|
||||
* @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with
|
||||
* <code>shapeType</code>.
|
||||
* @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted;
|
||||
* range <code>0.0</code> – <code>1.0</code> for the center to the surface, respectively.
|
||||
* Particles are emitted from the portion of the shape that lies between <code>emitRadiusStart</code> and the
|
||||
* shape's surface.
|
||||
* @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted
|
||||
* within the ellipsoid; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
|
||||
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
|
||||
* <code>ellipsoid</code> or <code>sphere</code>.
|
||||
* @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted
|
||||
* within the ellipsoid; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
|
||||
* within the shape; range <code>0</code> – <code>Math.PI</code>. Particles are emitted from the portion of the
|
||||
* shape that lies between <code>polarStart<code> and <code>polarFinish</code>. Only used if <code>shapeType</code> is
|
||||
* <code>ellipsoid</code> or <code>sphere</code>.
|
||||
* @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
||||
* z-axis at which particles start being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
||||
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>.
|
||||
* @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
|
||||
* z-axis at which particles stop being emitted; range <code>-Math.PI</code> – <code>Math.PI</code>. Particles are
|
||||
* emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||
* emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
|
||||
* Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>..
|
||||
*
|
||||
* @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
|
||||
* use PNG format.
|
||||
|
@ -1170,7 +1176,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* up in the world. If true, they will point towards the entity's up vector, based on its orientation.
|
||||
* @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated.
|
||||
*
|
||||
* @property {ShapeType} shapeType="none" - <em>Currently not used.</em> <em>Read-only.</em>
|
||||
* @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled.
|
||||
* @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
|
||||
* <code>"compound"</code>.
|
||||
*
|
||||
* @example <caption>Create a ball of green smoke.</caption>
|
||||
* particles = Entities.addEntity({
|
||||
|
@ -1658,6 +1666,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
// Particles only
|
||||
if (_type == EntityTypes::ParticleEffect) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
||||
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
|
@ -3104,6 +3113,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
|
||||
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
|
||||
_staticPulse.setProperties(properties);
|
||||
|
@ -3584,6 +3594,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
|
||||
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <Extents.h>
|
||||
#include <PerfStat.h>
|
||||
#include <Profile.h>
|
||||
#include <AddressManager.h>
|
||||
|
||||
#include "EntitySimulation.h"
|
||||
#include "VariantMapToScriptValue.h"
|
||||
|
@ -286,27 +287,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
|
|||
assert(entity);
|
||||
|
||||
if (getIsServer()) {
|
||||
QString certID(entity->getCertificateID());
|
||||
EntityItemID entityItemID = entity->getEntityItemID();
|
||||
EntityItemID existingEntityItemID;
|
||||
|
||||
{
|
||||
QWriteLocker locker(&_entityCertificateIDMapLock);
|
||||
existingEntityItemID = _entityCertificateIDMap.value(certID);
|
||||
if (!certID.isEmpty()) {
|
||||
_entityCertificateIDMap.insert(certID, entityItemID);
|
||||
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an already-existing entity from the tree if it has the same
|
||||
// CertificateID as the entity we're trying to add.
|
||||
if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) {
|
||||
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
|
||||
<< existingEntityItemID << ". Deleting existing entity.";
|
||||
deleteEntity(existingEntityItemID, true);
|
||||
return;
|
||||
}
|
||||
addCertifiedEntityOnServer(entity);
|
||||
}
|
||||
|
||||
// check to see if we need to simulate this entity..
|
||||
|
@ -764,13 +745,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
|
|||
theEntity->die();
|
||||
|
||||
if (getIsServer()) {
|
||||
{
|
||||
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
|
||||
QString certID = theEntity->getCertificateID();
|
||||
if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) {
|
||||
_entityCertificateIDMap.remove(certID);
|
||||
}
|
||||
}
|
||||
removeCertifiedEntityOnServer(theEntity);
|
||||
|
||||
// set up the deleted entities ID
|
||||
QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock);
|
||||
|
@ -1421,11 +1396,123 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void EntityTree::addCertifiedEntityOnServer(EntityItemPointer entity) {
|
||||
QString certID(entity->getCertificateID());
|
||||
EntityItemID existingEntityItemID;
|
||||
if (!certID.isEmpty()) {
|
||||
EntityItemID entityItemID = entity->getEntityItemID();
|
||||
QWriteLocker locker(&_entityCertificateIDMapLock);
|
||||
QList<EntityItemID>& entityList = _entityCertificateIDMap[certID]; // inserts it if needed.
|
||||
if (!entityList.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) {
|
||||
existingEntityItemID = entityList.first(); // we will only care about the first, if any, below.
|
||||
entityList.removeOne(existingEntityItemID);
|
||||
}
|
||||
entityList << entityItemID; // adds to list within hash because entityList is a reference.
|
||||
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID << "total" << entityList.size() << "entities.";
|
||||
}
|
||||
// Delete an already-existing entity from the tree if it has the same
|
||||
// CertificateID as the entity we're trying to add.
|
||||
if (!existingEntityItemID.isNull()) {
|
||||
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
|
||||
<< existingEntityItemID << ". Deleting existing entity.";
|
||||
withWriteLock([&] {
|
||||
deleteEntity(existingEntityItemID, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::removeCertifiedEntityOnServer(EntityItemPointer entity) {
|
||||
QString certID = entity->getCertificateID();
|
||||
if (!certID.isEmpty()) {
|
||||
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
|
||||
QList<EntityItemID>& entityList = _entityCertificateIDMap[certID];
|
||||
entityList.removeOne(entity->getEntityItemID());
|
||||
if (entityList.isEmpty()) {
|
||||
// hmmm, do we to make it be a hash instead of a list, so that this is faster if you stamp out 1000 of a domainUnlimited?
|
||||
_entityCertificateIDMap.remove(certID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::startDynamicDomainVerificationOnServer(float minimumAgeToRemove) {
|
||||
QReadLocker locker(&_entityCertificateIDMapLock);
|
||||
QHashIterator<QString, QList<EntityItemID>> i(_entityCertificateIDMap);
|
||||
qCDebug(entities) << _entityCertificateIDMap.size() << "certificates present.";
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
const auto& certificateID = i.key();
|
||||
const auto& entityIDs = i.value();
|
||||
if (entityIDs.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Examine each cert:
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = certificateID;
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, [this, entityIDs, networkReply, minimumAgeToRemove, &certificateID] {
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
bool failure = networkReply->error() != QNetworkReply::NoError;
|
||||
auto failureReason = networkReply->error();
|
||||
networkReply->deleteLater();
|
||||
if (failure) {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << failureReason
|
||||
<< "; NOT deleting cert" << certificateID << "More info:" << jsonObject;
|
||||
return;
|
||||
}
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
|
||||
if (jsonObject["domain_id"].toString() == thisDomainID) {
|
||||
// Entity belongs here. Nothing to do.
|
||||
return;
|
||||
}
|
||||
// Entity does not belong here:
|
||||
QList<EntityItemID> retained;
|
||||
for (int i = 0; i < entityIDs.size(); i++) {
|
||||
EntityItemID entityID = entityIDs.at(i);
|
||||
EntityItemPointer entity = findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID;
|
||||
continue;
|
||||
}
|
||||
if (entity->getAge() <= minimumAgeToRemove) {
|
||||
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
|
||||
retained << entityID;
|
||||
continue;
|
||||
}
|
||||
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
|
||||
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
|
||||
withWriteLock([&] {
|
||||
deleteEntity(entityID, true);
|
||||
});
|
||||
}
|
||||
{
|
||||
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
|
||||
if (retained.isEmpty()) {
|
||||
qCDebug(entities) << "Removed" << certificateID;
|
||||
_entityCertificateIDMap.remove(certificateID);
|
||||
} else {
|
||||
qCDebug(entities) << "Retained" << retained.size() << "young entities for" << certificateID;
|
||||
_entityCertificateIDMap[certificateID] = retained;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
|
||||
QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this);
|
||||
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) {
|
||||
QReadLocker locker(&_entityCertificateIDMapLock);
|
||||
EntityItemID id = _entityCertificateIDMap.value(certID);
|
||||
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const EntityItemID& id) {
|
||||
if (entityItemID == id && _challengeOwnershipTimeoutTimer) {
|
||||
_challengeOwnershipTimeoutTimer->stop();
|
||||
_challengeOwnershipTimeoutTimer->deleteLater();
|
||||
|
@ -1445,26 +1532,21 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID)
|
|||
_challengeOwnershipTimeoutTimer->start(5000);
|
||||
}
|
||||
|
||||
QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) {
|
||||
QByteArray EntityTree::computeNonce(const EntityItemID& entityID, const QString ownerKey) {
|
||||
QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-"
|
||||
QByteArray nonceBytes = nonce.toByteArray();
|
||||
|
||||
QWriteLocker locker(&_certNonceMapLock);
|
||||
_certNonceMap.insert(certID, QPair<QUuid, QString>(nonce, ownerKey));
|
||||
QWriteLocker locker(&_entityNonceMapLock);
|
||||
_entityNonceMap.insert(entityID, QPair<QUuid, QString>(nonce, ownerKey));
|
||||
|
||||
return nonceBytes;
|
||||
}
|
||||
|
||||
bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id) {
|
||||
{
|
||||
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
|
||||
id = _entityCertificateIDMap.value(certID);
|
||||
}
|
||||
|
||||
bool EntityTree::verifyNonce(const EntityItemID& entityID, const QString& nonce) {
|
||||
QString actualNonce, key;
|
||||
{
|
||||
QWriteLocker locker(&_certNonceMapLock);
|
||||
QPair<QUuid, QString> sent = _certNonceMap.take(certID);
|
||||
QWriteLocker locker(&_entityNonceMapLock);
|
||||
QPair<QUuid, QString> sent = _entityNonceMap.take(entityID);
|
||||
actualNonce = sent.first.toString();
|
||||
key = sent.second;
|
||||
}
|
||||
|
@ -1474,9 +1556,9 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity
|
|||
bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8()));
|
||||
|
||||
if (verificationSuccess) {
|
||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
|
||||
qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "succeeded.";
|
||||
} else {
|
||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce <<
|
||||
qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "failed. Actual nonce:" << actualNonce <<
|
||||
"\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key;
|
||||
}
|
||||
|
||||
|
@ -1484,42 +1566,42 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity
|
|||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
int certIDByteArraySize;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
int nodeToChallengeByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&idByteArraySize);
|
||||
message.readPrimitive(&textByteArraySize);
|
||||
message.readPrimitive(&nodeToChallengeByteArraySize);
|
||||
|
||||
QByteArray certID(message.read(certIDByteArraySize));
|
||||
QByteArray id(message.read(idByteArraySize));
|
||||
QByteArray text(message.read(textByteArraySize));
|
||||
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
|
||||
|
||||
sendChallengeOwnershipRequestPacket(certID, text, nodeToChallenge, sourceNode);
|
||||
sendChallengeOwnershipRequestPacket(id, text, nodeToChallenge, sourceNode);
|
||||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
int certIDByteArraySize;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&idByteArraySize);
|
||||
message.readPrimitive(&textByteArraySize);
|
||||
message.readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
|
||||
QByteArray certID(message.read(certIDByteArraySize));
|
||||
QByteArray id(message.read(idByteArraySize));
|
||||
QByteArray text(message.read(textByteArraySize));
|
||||
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
|
||||
|
||||
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
certIDByteArraySize + text.length() + 2 * sizeof(int),
|
||||
idByteArraySize + text.length() + 2 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipReplyPacket->writePrimitive(idByteArraySize);
|
||||
challengeOwnershipReplyPacket->writePrimitive(text.length());
|
||||
challengeOwnershipReplyPacket->write(certID);
|
||||
challengeOwnershipReplyPacket->write(id);
|
||||
challengeOwnershipReplyPacket->write(text);
|
||||
|
||||
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
|
||||
|
@ -1529,7 +1611,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
|
|||
// 1. Obtain a nonce
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray text = computeNonce(certID, ownerKey);
|
||||
QByteArray text = computeNonce(entityItemID, ownerKey);
|
||||
|
||||
if (text == "") {
|
||||
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity...";
|
||||
|
@ -1539,14 +1621,14 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
|
|||
} else {
|
||||
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
|
||||
// 2. Send the nonce to the rezzing avatar's node
|
||||
QByteArray certIDByteArray = certID.toUtf8();
|
||||
int certIDByteArraySize = certIDByteArray.size();
|
||||
QByteArray idByteArray = entityItemID.toByteArray();
|
||||
int idByteArraySize = idByteArray.size();
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||
certIDByteArraySize + text.length() + 2 * sizeof(int),
|
||||
idByteArraySize + text.length() + 2 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(idByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(text.length());
|
||||
challengeOwnershipPacket->write(certIDByteArray);
|
||||
challengeOwnershipPacket->write(idByteArray);
|
||||
challengeOwnershipPacket->write(text);
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
|
||||
|
||||
|
@ -1560,24 +1642,24 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
|
||||
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
|
||||
// to make sure belongs to Avatar B.
|
||||
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
|
||||
|
||||
int certIDByteArraySize = certID.length();
|
||||
int idByteArraySize = id.length();
|
||||
int TextByteArraySize = text.length();
|
||||
int senderNodeUUIDSize = senderNodeUUID.length();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||
certIDByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
|
||||
idByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(idByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(TextByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
|
||||
challengeOwnershipPacket->write(certID);
|
||||
challengeOwnershipPacket->write(id);
|
||||
challengeOwnershipPacket->write(text);
|
||||
challengeOwnershipPacket->write(senderNodeUUID);
|
||||
|
||||
|
@ -1636,22 +1718,21 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
|
|||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
int certIDByteArraySize;
|
||||
int idByteArraySize;
|
||||
int textByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&idByteArraySize);
|
||||
message.readPrimitive(&textByteArraySize);
|
||||
|
||||
QString certID(message.read(certIDByteArraySize));
|
||||
EntityItemID id(message.read(idByteArraySize));
|
||||
QString text(message.read(textByteArraySize));
|
||||
|
||||
emit killChallengeOwnershipTimeoutTimer(certID);
|
||||
emit killChallengeOwnershipTimeoutTimer(id);
|
||||
|
||||
EntityItemID id;
|
||||
if (!verifyNonce(certID, text, id)) {
|
||||
if (!id.isNull()) {
|
||||
if (!verifyNonce(id, text)) {
|
||||
withWriteLock([&] {
|
||||
deleteEntity(id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -157,11 +157,6 @@ public:
|
|||
return _recentlyDeletedEntityItemIDs;
|
||||
}
|
||||
|
||||
QHash<QString, EntityItemID> getEntityCertificateIDMap() const {
|
||||
QReadLocker locker(&_entityCertificateIDMapLock);
|
||||
return _entityCertificateIDMap;
|
||||
}
|
||||
|
||||
void forgetEntitiesDeletedBefore(quint64 sinceTime);
|
||||
|
||||
int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode);
|
||||
|
@ -252,8 +247,8 @@ public:
|
|||
|
||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||
|
||||
QByteArray computeNonce(const QString& certID, const QString ownerKey);
|
||||
bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id);
|
||||
QByteArray computeNonce(const EntityItemID& entityID, const QString ownerKey);
|
||||
bool verifyNonce(const EntityItemID& entityID, const QString& nonce);
|
||||
|
||||
QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); }
|
||||
void setMyAvatar(std::shared_ptr<AvatarData> myAvatar) { _myAvatar = myAvatar; }
|
||||
|
@ -279,6 +274,7 @@ public:
|
|||
|
||||
void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||
bool force, bool tellServer);
|
||||
void startDynamicDomainVerificationOnServer(float minimumAgeToRemove);
|
||||
|
||||
signals:
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
|
@ -290,7 +286,7 @@ signals:
|
|||
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
|
||||
void clearingEntities();
|
||||
void killChallengeOwnershipTimeoutTimer(const QString& certID);
|
||||
void killChallengeOwnershipTimeoutTimer(const EntityItemID& certID);
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -327,10 +323,10 @@ protected:
|
|||
QHash<EntityItemID, EntityItemPointer> _entityMap;
|
||||
|
||||
mutable QReadWriteLock _entityCertificateIDMapLock;
|
||||
QHash<QString, EntityItemID> _entityCertificateIDMap;
|
||||
QHash<QString, QList<EntityItemID>> _entityCertificateIDMap;
|
||||
|
||||
mutable QReadWriteLock _certNonceMapLock;
|
||||
QHash<QString, QPair<QUuid, QString>> _certNonceMap;
|
||||
mutable QReadWriteLock _entityNonceMapLock;
|
||||
QHash<EntityItemID, QPair<QUuid, QString>> _entityNonceMap;
|
||||
|
||||
EntitySimulationPointer _simulation;
|
||||
|
||||
|
@ -377,8 +373,10 @@ protected:
|
|||
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
|
||||
|
||||
private:
|
||||
void addCertifiedEntityOnServer(EntityItemPointer entity);
|
||||
void removeCertifiedEntityOnServer(EntityItemPointer entity);
|
||||
void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
|
||||
void sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
|
||||
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||
|
||||
std::shared_ptr<AvatarData> _myAvatar{ nullptr };
|
||||
|
|
|
@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem {
|
|||
|
||||
QVector<glm::vec3> getLinePoints() const;
|
||||
|
||||
virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
|
||||
|
||||
// never have a ray intersection pick a LineEntityItem.
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
|
|
|
@ -175,7 +175,7 @@ protected:
|
|||
|
||||
QString _textures;
|
||||
|
||||
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
||||
ShapeType _shapeType { SHAPE_TYPE_NONE };
|
||||
|
||||
private:
|
||||
uint64_t _lastAnimated{ 0 };
|
||||
|
|
|
@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert
|
|||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
|
||||
withReadLock([&] {
|
||||
|
@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
|||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
||||
withWriteLock([&] {
|
||||
|
@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
|
||||
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
|
||||
withWriteLock([&] {
|
||||
|
@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
|
|||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
requestedProperties += PROP_SHAPE_TYPE;
|
||||
requestedProperties += PROP_COMPOUND_SHAPE_URL;
|
||||
requestedProperties += PROP_COLOR;
|
||||
requestedProperties += PROP_ALPHA;
|
||||
requestedProperties += _pulseProperties.getEntityProperties(params);
|
||||
|
@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
|
|||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
|
||||
withReadLock([&] {
|
||||
|
@ -718,11 +723,42 @@ void ParticleEffectEntityItem::debugDump() const {
|
|||
}
|
||||
|
||||
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
|
||||
switch (type) {
|
||||
case SHAPE_TYPE_NONE:
|
||||
case SHAPE_TYPE_CAPSULE_X:
|
||||
case SHAPE_TYPE_CAPSULE_Y:
|
||||
case SHAPE_TYPE_CAPSULE_Z:
|
||||
case SHAPE_TYPE_HULL:
|
||||
case SHAPE_TYPE_SIMPLE_HULL:
|
||||
case SHAPE_TYPE_SIMPLE_COMPOUND:
|
||||
case SHAPE_TYPE_STATIC_MESH:
|
||||
// these types are unsupported for ParticleEffectEntity
|
||||
type = particle::DEFAULT_SHAPE_TYPE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
if (type != _shapeType) {
|
||||
_shapeType = type;
|
||||
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
}
|
||||
_shapeType = type;
|
||||
});
|
||||
}
|
||||
|
||||
ShapeType ParticleEffectEntityItem::getShapeType() const {
|
||||
return resultWithReadLock<ShapeType>([&] {
|
||||
return _shapeType;
|
||||
});
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) {
|
||||
withWriteLock([&] {
|
||||
_compoundShapeURL = compoundShapeURL;
|
||||
});
|
||||
}
|
||||
|
||||
QString ParticleEffectEntityItem::getCompoundShapeURL() const {
|
||||
return resultWithReadLock<QString>([&] {
|
||||
return _compoundShapeURL;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ namespace particle {
|
|||
static const QString DEFAULT_TEXTURES = "";
|
||||
static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
|
||||
static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
|
||||
static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID;
|
||||
|
||||
template <typename T>
|
||||
struct Range {
|
||||
|
@ -255,7 +256,10 @@ public:
|
|||
float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; }
|
||||
|
||||
void setShapeType(ShapeType type) override;
|
||||
virtual ShapeType getShapeType() const override { return _shapeType; }
|
||||
virtual ShapeType getShapeType() const override;
|
||||
|
||||
QString getCompoundShapeURL() const;
|
||||
virtual void setCompoundShapeURL(const QString& url);
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
@ -349,7 +353,8 @@ protected:
|
|||
PulsePropertyGroup _pulseProperties;
|
||||
bool _isEmitting { true };
|
||||
|
||||
ShapeType _shapeType { SHAPE_TYPE_NONE };
|
||||
ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE };
|
||||
QString _compoundShapeURL { "" };
|
||||
};
|
||||
|
||||
#endif // hifi_ParticleEffectEntityItem_h
|
||||
|
|
|
@ -112,7 +112,7 @@ protected:
|
|||
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
||||
//! prior functionality where new or unsupported shapes are treated as
|
||||
//! ellipsoids.
|
||||
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||
ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||
};
|
||||
|
||||
#endif // hifi_ShapeEntityItem_h
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <QDebug>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
|
||||
|
@ -353,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const {
|
|||
|
||||
Extents meshExtents = hfmModel.getMeshExtents();
|
||||
glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
|
||||
glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint());
|
||||
glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint());
|
||||
glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
|
||||
|
||||
glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
|
||||
|
@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
|
|||
if (hullURL.isEmpty()) {
|
||||
_shapeResource.reset();
|
||||
} else {
|
||||
QUrlQuery queryArgs(hullURL);
|
||||
queryArgs.addQueryItem("collision-hull", "");
|
||||
hullURL.setQuery(queryArgs);
|
||||
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ protected:
|
|||
KeyLightPropertyGroup _keyLightProperties;
|
||||
AmbientLightPropertyGroup _ambientLightProperties;
|
||||
|
||||
ShapeType _shapeType = DEFAULT_SHAPE_TYPE;
|
||||
ShapeType _shapeType { DEFAULT_SHAPE_TYPE };
|
||||
QString _compoundShapeURL;
|
||||
|
||||
// The following 3 values are the defaults for zone creation
|
||||
|
|
|
@ -711,19 +711,19 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) {
|
|||
node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]);
|
||||
} else {
|
||||
|
||||
if (node.defined["rotation"] && node.rotation.size() == 4) {
|
||||
//quat(x,y,z,w) to quat(w,x,y,z)
|
||||
glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]);
|
||||
tmat = glm::mat4_cast(rotquat) * tmat;
|
||||
}
|
||||
|
||||
if (node.defined["scale"] && node.scale.size() == 3) {
|
||||
glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]);
|
||||
glm::mat4 s = glm::mat4(1.0);
|
||||
s = glm::scale(s, scale);
|
||||
tmat = s * tmat;
|
||||
}
|
||||
|
||||
|
||||
if (node.defined["rotation"] && node.rotation.size() == 4) {
|
||||
//quat(x,y,z,w) to quat(w,x,y,z)
|
||||
glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]);
|
||||
tmat = glm::mat4_cast(rotquat) * tmat;
|
||||
}
|
||||
|
||||
if (node.defined["translation"] && node.translation.size() == 3) {
|
||||
glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]);
|
||||
glm::mat4 t = glm::mat4(1.0);
|
||||
|
@ -734,15 +734,54 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) {
|
|||
return tmat;
|
||||
}
|
||||
|
||||
void GLTFSerializer::getSkinInverseBindMatrices(std::vector<std::vector<float>>& inverseBindMatrixValues) {
|
||||
for (auto &skin : _file.skins) {
|
||||
GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices];
|
||||
GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView];
|
||||
GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer];
|
||||
int accBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0;
|
||||
QVector<float> matrices;
|
||||
addArrayOfType(indicesBuffer.blob,
|
||||
indicesBufferview.byteOffset + accBoffset,
|
||||
indicesAccessor.count,
|
||||
matrices,
|
||||
indicesAccessor.type,
|
||||
indicesAccessor.componentType);
|
||||
inverseBindMatrixValues.push_back(matrices.toStdVector());
|
||||
}
|
||||
}
|
||||
|
||||
void GLTFSerializer::getNodeQueueByDepthFirstChildren(std::vector<int>& children, int stride, std::vector<int>& result) {
|
||||
int startingIndex = 0;
|
||||
int finalIndex = (int)children.size();
|
||||
if (stride == -1) {
|
||||
startingIndex = (int)children.size() - 1;
|
||||
finalIndex = -1;
|
||||
}
|
||||
for (int index = startingIndex; index != finalIndex; index += stride) {
|
||||
int c = children[index];
|
||||
result.push_back(c);
|
||||
std::vector<int> nested = _file.nodes[c].children.toStdVector();
|
||||
if (nested.size() != 0) {
|
||||
std::sort(nested.begin(), nested.end());
|
||||
for (int r : nested) {
|
||||
if (result.end() == std::find(result.begin(), result.end(), r)) {
|
||||
getNodeQueueByDepthFirstChildren(nested, stride, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
||||
int numNodes = _file.nodes.size();
|
||||
|
||||
//Build dependencies
|
||||
QVector<QVector<int>> nodeDependencies(_file.nodes.size());
|
||||
QVector<QVector<int>> nodeDependencies(numNodes);
|
||||
int nodecount = 0;
|
||||
bool hasChildren = false;
|
||||
foreach(auto &node, _file.nodes) {
|
||||
//nodes_transforms.push_back(getModelTransform(node));
|
||||
hasChildren |= !node.children.isEmpty();
|
||||
foreach(int child, node.children) nodeDependencies[child].push_back(nodecount);
|
||||
nodecount++;
|
||||
}
|
||||
|
@ -764,26 +803,98 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
|
||||
nodecount++;
|
||||
}
|
||||
|
||||
HFMJoint joint;
|
||||
joint.isSkeletonJoint = true;
|
||||
joint.bindTransformFoundInCluster = false;
|
||||
joint.distanceToParent = 0;
|
||||
joint.parentIndex = -1;
|
||||
hfmModel.joints.resize(_file.nodes.size());
|
||||
hfmModel.jointIndices["x"] = _file.nodes.size();
|
||||
int jointInd = 0;
|
||||
for (auto& node : _file.nodes) {
|
||||
int size = node.transforms.size();
|
||||
if (hasChildren) { size--; }
|
||||
joint.preTransform = glm::mat4(1);
|
||||
for (int i = 0; i < size; i++) {
|
||||
joint.preTransform = node.transforms[i] * joint.preTransform;
|
||||
}
|
||||
joint.name = node.name;
|
||||
hfmModel.joints[jointInd] = joint;
|
||||
jointInd++;
|
||||
|
||||
|
||||
// initialize order in which nodes will be parsed
|
||||
std::vector<int> nodeQueue;
|
||||
nodeQueue.reserve(numNodes);
|
||||
int rootNode = 0;
|
||||
int finalNode = numNodes;
|
||||
if (!_file.scenes[_file.scene].nodes.contains(0)) {
|
||||
rootNode = numNodes - 1;
|
||||
finalNode = -1;
|
||||
}
|
||||
bool rootAtStartOfList = rootNode < finalNode;
|
||||
int nodeListStride = 1;
|
||||
if (!rootAtStartOfList) { nodeListStride = -1; }
|
||||
|
||||
QVector<int> initialSceneNodes = _file.scenes[_file.scene].nodes;
|
||||
std::sort(initialSceneNodes.begin(), initialSceneNodes.end());
|
||||
int sceneRootNode = 0;
|
||||
int sceneFinalNode = initialSceneNodes.size();
|
||||
if (!rootAtStartOfList) {
|
||||
sceneRootNode = initialSceneNodes.size() - 1;
|
||||
sceneFinalNode = -1;
|
||||
}
|
||||
for (int index = sceneRootNode; index != sceneFinalNode; index += nodeListStride) {
|
||||
int i = initialSceneNodes[index];
|
||||
nodeQueue.push_back(i);
|
||||
std::vector<int> children = _file.nodes[i].children.toStdVector();
|
||||
std::sort(children.begin(), children.end());
|
||||
getNodeQueueByDepthFirstChildren(children, nodeListStride, nodeQueue);
|
||||
}
|
||||
|
||||
// Build joints
|
||||
HFMJoint joint;
|
||||
joint.distanceToParent = 0;
|
||||
hfmModel.jointIndices["x"] = numNodes;
|
||||
hfmModel.hasSkeletonJoints = false;
|
||||
|
||||
for (int nodeIndex : nodeQueue) {
|
||||
auto& node = _file.nodes[nodeIndex];
|
||||
|
||||
joint.parentIndex = -1;
|
||||
if (!_file.scenes[_file.scene].nodes.contains(nodeIndex)) {
|
||||
joint.parentIndex = std::distance(nodeQueue.begin(), std::find(nodeQueue.begin(), nodeQueue.end(), nodeDependencies[nodeIndex][0]));
|
||||
}
|
||||
joint.transform = node.transforms.first();
|
||||
joint.translation = extractTranslation(joint.transform);
|
||||
joint.rotation = glmExtractRotation(joint.transform);
|
||||
glm::vec3 scale = extractScale(joint.transform);
|
||||
joint.postTransform = glm::scale(glm::mat4(), scale);
|
||||
|
||||
joint.name = node.name;
|
||||
joint.isSkeletonJoint = false;
|
||||
hfmModel.joints.push_back(joint);
|
||||
}
|
||||
|
||||
|
||||
// Build skeleton
|
||||
std::vector<glm::mat4> jointInverseBindTransforms;
|
||||
jointInverseBindTransforms.resize(numNodes);
|
||||
if (!_file.skins.isEmpty()) {
|
||||
std::vector<std::vector<float>> inverseBindValues;
|
||||
getSkinInverseBindMatrices(inverseBindValues);
|
||||
|
||||
int jointIndex = finalNode;
|
||||
while (jointIndex != rootNode) {
|
||||
rootAtStartOfList ? jointIndex-- : jointIndex++;
|
||||
int jOffset = nodeQueue[jointIndex];
|
||||
auto joint = hfmModel.joints[jointIndex];
|
||||
|
||||
hfmModel.hasSkeletonJoints = true;
|
||||
for (int s = 0; s < _file.skins.size(); s++) {
|
||||
auto skin = _file.skins[s];
|
||||
joint.isSkeletonJoint = skin.joints.contains(jOffset);
|
||||
|
||||
if (joint.isSkeletonJoint) {
|
||||
std::vector<float> value = inverseBindValues[s];
|
||||
int matrixCount = 16 * skin.joints.indexOf(jOffset);
|
||||
jointInverseBindTransforms[jointIndex] =
|
||||
glm::mat4(value[matrixCount], value[matrixCount + 1], value[matrixCount + 2], value[matrixCount + 3],
|
||||
value[matrixCount + 4], value[matrixCount + 5], value[matrixCount + 6], value[matrixCount + 7],
|
||||
value[matrixCount + 8], value[matrixCount + 9], value[matrixCount + 10], value[matrixCount + 11],
|
||||
value[matrixCount + 12], value[matrixCount + 13], value[matrixCount + 14], value[matrixCount + 15]);
|
||||
} else {
|
||||
jointInverseBindTransforms[jointIndex] = glm::mat4();
|
||||
}
|
||||
glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex]));
|
||||
hfmModel.bindExtents.addPoint(bindTranslation);
|
||||
}
|
||||
hfmModel.joints[jointIndex] = joint;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Build materials
|
||||
QVector<QString> materialIDs;
|
||||
|
@ -803,23 +914,39 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
nodecount = 0;
|
||||
// Build meshes
|
||||
foreach(auto &node, _file.nodes) {
|
||||
nodecount = 0;
|
||||
for (int nodeIndex = rootNode; nodeIndex != finalNode; nodeIndex += nodeListStride) {
|
||||
auto& node = _file.nodes[nodeIndex];
|
||||
|
||||
if (node.defined["mesh"]) {
|
||||
qCDebug(modelformat) << "node_transforms" << node.transforms;
|
||||
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
|
||||
hfmModel.meshes.append(HFMMesh());
|
||||
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
|
||||
HFMCluster cluster;
|
||||
cluster.jointIndex = nodecount;
|
||||
cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1);
|
||||
mesh.clusters.append(cluster);
|
||||
if (!hfmModel.hasSkeletonJoints) {
|
||||
HFMCluster cluster;
|
||||
cluster.jointIndex = nodecount;
|
||||
cluster.inverseBindMatrix = glm::mat4();
|
||||
cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix);
|
||||
mesh.clusters.append(cluster);
|
||||
} else {
|
||||
for (int j = rootNode; j != finalNode; j += nodeListStride) {
|
||||
HFMCluster cluster;
|
||||
cluster.jointIndex = j;
|
||||
cluster.inverseBindMatrix = jointInverseBindTransforms[j];
|
||||
cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix);
|
||||
mesh.clusters.append(cluster);
|
||||
}
|
||||
}
|
||||
HFMCluster root;
|
||||
root.jointIndex = rootNode;
|
||||
if (root.jointIndex == -1) {
|
||||
root.jointIndex = 0;
|
||||
}
|
||||
root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex];
|
||||
root.inverseBindTransform = Transform(root.inverseBindMatrix);
|
||||
mesh.clusters.append(root);
|
||||
|
||||
HFMMeshPart part = HFMMeshPart();
|
||||
|
||||
|
@ -848,6 +975,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
}
|
||||
|
||||
QList<QString> keys = primitive.attributes.values.keys();
|
||||
QVector<uint16_t> clusterJoints;
|
||||
QVector<float> clusterWeights;
|
||||
|
||||
foreach(auto &key, keys) {
|
||||
int accessorIdx = primitive.attributes.values[key];
|
||||
|
@ -949,8 +1078,69 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
for (int n = 0; n < texcoords.size(); n = n + 2) {
|
||||
mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
|
||||
}
|
||||
} else if (key == "JOINTS_0") {
|
||||
QVector<uint16_t> joints;
|
||||
success = addArrayOfType(buffer.blob,
|
||||
bufferview.byteOffset + accBoffset,
|
||||
accessor.count,
|
||||
joints,
|
||||
accessor.type,
|
||||
accessor.componentType);
|
||||
if (!success) {
|
||||
qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url;
|
||||
continue;
|
||||
}
|
||||
for (int n = 0; n < joints.size(); n++) {
|
||||
clusterJoints.push_back(joints[n]);
|
||||
}
|
||||
} else if (key == "WEIGHTS_0") {
|
||||
QVector<float> weights;
|
||||
success = addArrayOfType(buffer.blob,
|
||||
bufferview.byteOffset + accBoffset,
|
||||
accessor.count,
|
||||
weights,
|
||||
accessor.type,
|
||||
accessor.componentType);
|
||||
if (!success) {
|
||||
qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url;
|
||||
continue;
|
||||
}
|
||||
for (int n = 0; n < weights.size(); n++) {
|
||||
clusterWeights.push_back(weights[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adapted from FBXSerializer.cpp
|
||||
if (hfmModel.hasSkeletonJoints) {
|
||||
int numClusterIndices = clusterJoints.size();
|
||||
const int WEIGHTS_PER_VERTEX = 4;
|
||||
const float ALMOST_HALF = 0.499f;
|
||||
int numVertices = mesh.vertices.size();
|
||||
mesh.clusterIndices.fill(mesh.clusters.size() - 1, numClusterIndices);
|
||||
mesh.clusterWeights.fill(0, numClusterIndices);
|
||||
|
||||
for (int c = 0; c < clusterJoints.size(); c++) {
|
||||
mesh.clusterIndices[c] = _file.skins[node.skin].joints[clusterJoints[c]];
|
||||
}
|
||||
|
||||
// normalize and compress to 16-bits
|
||||
for (int i = 0; i < numVertices; ++i) {
|
||||
int j = i * WEIGHTS_PER_VERTEX;
|
||||
|
||||
float totalWeight = 0.0f;
|
||||
for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) {
|
||||
totalWeight += clusterWeights[k];
|
||||
}
|
||||
if (totalWeight > 0.0f) {
|
||||
float weightScalingFactor = (float)(UINT16_MAX) / totalWeight;
|
||||
for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) {
|
||||
mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF);
|
||||
}
|
||||
} else {
|
||||
mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (primitive.defined["material"]) {
|
||||
|
@ -959,8 +1149,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
mesh.parts.push_back(part);
|
||||
|
||||
// populate the texture coordinates if they don't exist
|
||||
if (mesh.texCoords.size() == 0) {
|
||||
for (int i = 0; i < part.triangleIndices.size(); i++) mesh.texCoords.push_back(glm::vec2(0.0, 1.0));
|
||||
if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) {
|
||||
for (int i = 0; i < part.triangleIndices.size(); i++) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); }
|
||||
}
|
||||
mesh.meshExtents.reset();
|
||||
foreach(const glm::vec3& vertex, mesh.vertices) {
|
||||
|
|
|
@ -712,6 +712,8 @@ private:
|
|||
hifi::ByteArray _glbBinary;
|
||||
|
||||
glm::mat4 getModelTransform(const GLTFNode& node);
|
||||
void getSkinInverseBindMatrices(std::vector<std::vector<float>>& inverseBindMatrixValues);
|
||||
void getNodeQueueByDepthFirstChildren(std::vector<int>& children, int stride, std::vector<int>& result);
|
||||
|
||||
bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url);
|
||||
bool parseGLTF(const hifi::ByteArray& data);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "GraphicsScriptingUtil.h"
|
||||
#include "ScriptableMesh.h"
|
||||
#include "graphics/Material.h"
|
||||
#include "image/Image.h"
|
||||
#include "image/TextureProcessing.h"
|
||||
|
||||
// #define SCRIPTABLE_MESH_DEBUG 1
|
||||
|
||||
|
|
|
@ -362,7 +362,6 @@ MeshPointer Mesh::createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numI
|
|||
mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint32_t), (gpu::Byte*) indices), gpu::Element::INDEX_INT32));
|
||||
}
|
||||
|
||||
|
||||
std::vector<graphics::Mesh::Part> parts;
|
||||
parts.push_back(graphics::Mesh::Part(0, numIndices, 0, graphics::Mesh::TRIANGLES));
|
||||
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
|
||||
|
|
|
@ -3,6 +3,7 @@ setup_hifi_library()
|
|||
link_hifi_libraries(shared gpu)
|
||||
target_nvtt()
|
||||
target_etc2comp()
|
||||
target_openexr()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,10 @@
|
|||
#pragma once
|
||||
//
|
||||
// Image.h
|
||||
// image/src/image
|
||||
// image/src/Image
|
||||
//
|
||||
// Created by Clement Brisset on 4/5/2017.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
// Created by Olivier Prat on 29/3/2019.
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -12,80 +13,85 @@
|
|||
#ifndef hifi_image_Image_h
|
||||
#define hifi_image_Image_h
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
#include <QImage>
|
||||
|
||||
#include "ColorChannel.h"
|
||||
|
||||
class QByteArray;
|
||||
class QImage;
|
||||
#include <glm/fwd.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
namespace image {
|
||||
|
||||
namespace TextureUsage {
|
||||
class Image {
|
||||
public:
|
||||
|
||||
enum Type {
|
||||
DEFAULT_TEXTURE,
|
||||
STRICT_TEXTURE,
|
||||
ALBEDO_TEXTURE,
|
||||
NORMAL_TEXTURE,
|
||||
BUMP_TEXTURE,
|
||||
SPECULAR_TEXTURE,
|
||||
METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
|
||||
ROUGHNESS_TEXTURE,
|
||||
GLOSS_TEXTURE,
|
||||
EMISSIVE_TEXTURE,
|
||||
CUBE_TEXTURE,
|
||||
OCCLUSION_TEXTURE,
|
||||
SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
|
||||
LIGHTMAP_TEXTURE,
|
||||
UNUSED_TEXTURE
|
||||
};
|
||||
enum Format {
|
||||
Format_Invalid = QImage::Format_Invalid,
|
||||
Format_Mono = QImage::Format_Mono,
|
||||
Format_MonoLSB = QImage::Format_MonoLSB,
|
||||
Format_Indexed8 = QImage::Format_Indexed8,
|
||||
Format_RGB32 = QImage::Format_RGB32,
|
||||
Format_ARGB32 = QImage::Format_ARGB32,
|
||||
Format_ARGB32_Premultiplied = QImage::Format_ARGB32_Premultiplied,
|
||||
Format_RGB16 = QImage::Format_RGB16,
|
||||
Format_ARGB8565_Premultiplied = QImage::Format_ARGB8565_Premultiplied,
|
||||
Format_RGB666 = QImage::Format_RGB666,
|
||||
Format_ARGB6666_Premultiplied = QImage::Format_ARGB6666_Premultiplied,
|
||||
Format_RGB555 = QImage::Format_RGB555,
|
||||
Format_ARGB8555_Premultiplied = QImage::Format_ARGB8555_Premultiplied,
|
||||
Format_RGB888 = QImage::Format_RGB888,
|
||||
Format_RGB444 = QImage::Format_RGB444,
|
||||
Format_ARGB4444_Premultiplied = QImage::Format_ARGB4444_Premultiplied,
|
||||
Format_RGBX8888 = QImage::Format_RGBX8888,
|
||||
Format_RGBA8888 = QImage::Format_RGBA8888,
|
||||
Format_RGBA8888_Premultiplied = QImage::Format_RGBA8888_Premultiplied,
|
||||
Format_Grayscale8 = QImage::Format_Grayscale8,
|
||||
Format_R11G11B10F = QImage::Format_RGB30,
|
||||
Format_PACKED_FLOAT = Format_R11G11B10F
|
||||
};
|
||||
|
||||
using TextureLoader = std::function<gpu::TexturePointer(QImage&&, const std::string&, bool, gpu::BackendTarget, const std::atomic<bool>&)>;
|
||||
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
|
||||
using AspectRatioMode = Qt::AspectRatioMode;
|
||||
using TransformationMode = Qt::TransformationMode;
|
||||
|
||||
gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isStrict, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isBumpMap, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isInvertedPixels, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool generateIrradiance, const std::atomic<bool>& abortProcessing);
|
||||
Image() {}
|
||||
Image(int width, int height, Format format) : _data(width, height, (QImage::Format)format) {}
|
||||
Image(const QImage& data) : _data(data) {}
|
||||
void operator=(const QImage& image) {
|
||||
_data = image;
|
||||
}
|
||||
|
||||
} // namespace TextureUsage
|
||||
bool isNull() const { return _data.isNull(); }
|
||||
|
||||
const QStringList getSupportedFormats();
|
||||
Format getFormat() const { return (Format)_data.format(); }
|
||||
bool hasAlphaChannel() const { return _data.hasAlphaChannel(); }
|
||||
|
||||
gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannel sourceChannel,
|
||||
int maxNumPixels, TextureUsage::Type textureType,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
|
||||
glm::uint32 getWidth() const { return (glm::uint32)_data.width(); }
|
||||
glm::uint32 getHeight() const { return (glm::uint32)_data.height(); }
|
||||
glm::uvec2 getSize() const { return toGlm(_data.size()); }
|
||||
size_t getByteCount() const { return _data.byteCount(); }
|
||||
|
||||
QRgb getPixel(int x, int y) const { return _data.pixel(x, y); }
|
||||
void setPixel(int x, int y, QRgb value) {
|
||||
_data.setPixel(x, y, value);
|
||||
}
|
||||
|
||||
glm::uint8* editScanLine(int y) { return _data.scanLine(y); }
|
||||
const glm::uint8* getScanLine(int y) const { return _data.scanLine(y); }
|
||||
const glm::uint8* getBits() const { return _data.constBits(); }
|
||||
|
||||
Image getScaled(glm::uvec2 newSize, AspectRatioMode ratioMode, TransformationMode transformationMode = Qt::SmoothTransformation) const;
|
||||
Image getConvertedToFormat(Format newFormat) const;
|
||||
Image getSubImage(QRect rect) const;
|
||||
Image getMirrored(bool horizontal, bool vertical) const;
|
||||
|
||||
// Inplace transformations
|
||||
void invertPixels();
|
||||
|
||||
private:
|
||||
|
||||
QImage _data;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
|
||||
|
|
99
libraries/image/src/image/OpenEXRReader.cpp
Normal file
99
libraries/image/src/image/OpenEXRReader.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// OpenEXRReader.cpp
|
||||
// image/src/image
|
||||
//
|
||||
// Created by Olivier Prat
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "OpenEXRReader.h"
|
||||
|
||||
#include "TextureProcessing.h"
|
||||
#include "ImageLogging.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QDebug>
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
|
||||
#include <OpenEXR/ImfIO.h>
|
||||
#include <OpenEXR/ImfRgbaFile.h>
|
||||
#include <OpenEXR/ImfArray.h>
|
||||
#include <OpenEXR/ImfTestFile.h>
|
||||
|
||||
class QIODeviceImfStream : public Imf::IStream {
|
||||
public:
|
||||
|
||||
QIODeviceImfStream(QIODevice& device, const std::string& filename) :
|
||||
Imf::IStream(filename.c_str()), _device(device) {
|
||||
}
|
||||
|
||||
bool read(char c[/*n*/], int n) override {
|
||||
if (_device.read(c, n) <= 0) {
|
||||
qWarning(imagelogging) << "OpenEXR - in file " << fileName() << " : " << _device.errorString();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Imf::Int64 tellg() override {
|
||||
return _device.pos();
|
||||
}
|
||||
|
||||
void seekg(Imf::Int64 pos) override {
|
||||
_device.seek(pos);
|
||||
}
|
||||
|
||||
void clear() override {
|
||||
// Not much to do
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
QIODevice& _device;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
image::Image image::readOpenEXR(QIODevice& content, const std::string& filename) {
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
QIODeviceImfStream device(content, filename);
|
||||
|
||||
if (Imf::isOpenExrFile(device)) {
|
||||
Imf::RgbaInputFile file(device);
|
||||
Imath::Box2i viewport = file.dataWindow();
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
int width = viewport.max.x - viewport.min.x + 1;
|
||||
int height = viewport.max.y - viewport.min.y + 1;
|
||||
|
||||
pixels.resizeErase(height, width);
|
||||
|
||||
file.setFrameBuffer(&pixels[0][0] - viewport.min.x - viewport.min.y * width, 1, width);
|
||||
file.readPixels(viewport.min.y, viewport.max.y);
|
||||
|
||||
Image image{ width, height, Image::Format_PACKED_FLOAT };
|
||||
auto packHDRPixel = getHDRPackingFunction();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
const auto srcScanline = pixels[y];
|
||||
gpu::uint32* dstScanline = (gpu::uint32*) image.editScanLine(y);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
const auto& srcPixel = srcScanline[x];
|
||||
auto& dstPixel = dstScanline[x];
|
||||
glm::vec3 floatPixel{ srcPixel.r, srcPixel.g, srcPixel.b };
|
||||
|
||||
dstPixel = packHDRPixel(floatPixel);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
} else {
|
||||
qWarning(imagelogging) << "OpenEXR - File " << filename.c_str() << " doesn't have the proper format";
|
||||
}
|
||||
#endif
|
||||
|
||||
return QImage();
|
||||
}
|
24
libraries/image/src/image/OpenEXRReader.h
Normal file
24
libraries/image/src/image/OpenEXRReader.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// OpenEXRReader.h
|
||||
// image/src/image
|
||||
//
|
||||
// Created by Olivier Prat
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_image_OpenEXRReader_h
|
||||
#define hifi_image_OpenEXRReader_h
|
||||
|
||||
#include "Image.h"
|
||||
|
||||
namespace image {
|
||||
|
||||
// TODO Move this into a plugin that QImageReader can use
|
||||
Image readOpenEXR(QIODevice& contents, const std::string& filename);
|
||||
|
||||
}
|
||||
|
||||
#endif // hifi_image_OpenEXRReader_h
|
|
@ -16,7 +16,7 @@
|
|||
#include <QIODevice>
|
||||
#include <QDebug>
|
||||
|
||||
QImage image::readTGA(QIODevice& content) {
|
||||
image::Image image::readTGA(QIODevice& content) {
|
||||
enum class TGAImageType : uint8_t {
|
||||
NoImageData = 0,
|
||||
UncompressedColorMapped = 1,
|
||||
|
|
|
@ -12,12 +12,11 @@
|
|||
#ifndef hifi_image_TGAReader_h
|
||||
#define hifi_image_TGAReader_h
|
||||
|
||||
#include <QImage>
|
||||
#include "Image.h"
|
||||
|
||||
namespace image {
|
||||
|
||||
// TODO Move this into a plugin that QImageReader can use
|
||||
QImage readTGA(QIODevice& contents);
|
||||
Image readTGA(QIODevice& contents);
|
||||
|
||||
}
|
||||
|
||||
|
|
1525
libraries/image/src/image/TextureProcessing.cpp
Normal file
1525
libraries/image/src/image/TextureProcessing.cpp
Normal file
File diff suppressed because it is too large
Load diff
92
libraries/image/src/image/TextureProcessing.h
Normal file
92
libraries/image/src/image/TextureProcessing.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// TextureProcessing.h
|
||||
// image/src/TextureProcessing
|
||||
//
|
||||
// Created by Clement Brisset on 4/5/2017.
|
||||
// Copyright 2017 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_image_TextureProcessing_h
|
||||
#define hifi_image_TextureProcessing_h
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
#include "Image.h"
|
||||
|
||||
namespace image {
|
||||
|
||||
std::function<gpu::uint32(const glm::vec3&)> getHDRPackingFunction();
|
||||
std::function<glm::vec3(gpu::uint32)> getHDRUnpackingFunction();
|
||||
|
||||
namespace TextureUsage {
|
||||
|
||||
enum Type {
|
||||
DEFAULT_TEXTURE,
|
||||
STRICT_TEXTURE,
|
||||
ALBEDO_TEXTURE,
|
||||
NORMAL_TEXTURE,
|
||||
BUMP_TEXTURE,
|
||||
SPECULAR_TEXTURE,
|
||||
METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey
|
||||
ROUGHNESS_TEXTURE,
|
||||
GLOSS_TEXTURE,
|
||||
EMISSIVE_TEXTURE,
|
||||
CUBE_TEXTURE,
|
||||
OCCLUSION_TEXTURE,
|
||||
SCATTERING_TEXTURE = OCCLUSION_TEXTURE,
|
||||
LIGHTMAP_TEXTURE,
|
||||
UNUSED_TEXTURE
|
||||
};
|
||||
|
||||
using TextureLoader = std::function<gpu::TexturePointer(Image&&, const std::string&, bool, gpu::BackendTarget, const std::atomic<bool>&)>;
|
||||
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
|
||||
|
||||
gpu::TexturePointer create2DTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createStrict2DTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createAlbedoTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createEmissiveTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createNormalTextureFromNormalImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createNormalTextureFromBumpImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createRoughnessTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createRoughnessTextureFromGlossImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createMetallicTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createLightmapTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isStrict, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isBumpMap, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool isInvertedPixels, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress,
|
||||
gpu::BackendTarget target, bool generateIrradiance, const std::atomic<bool>& abortProcessing);
|
||||
|
||||
} // namespace TextureUsage
|
||||
|
||||
const QStringList getSupportedFormats();
|
||||
|
||||
gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::string& url, ColorChannel sourceChannel,
|
||||
int maxNumPixels, TextureUsage::Type textureType,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing = false);
|
||||
|
||||
} // namespace image
|
||||
|
||||
#endif // hifi_image_TextureProcessing_h
|
|
@ -34,7 +34,7 @@
|
|||
#include <gl/GLHelpers.h>
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <shared/NsightHelpers.h>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <graphics/TextureMap.h>
|
||||
#include <image/ColorChannel.h>
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <TextureMeta.h>
|
||||
|
||||
|
|
|
@ -267,6 +267,7 @@ enum class EntityVersion : PacketVersion {
|
|||
ReOrderParentIDProperties,
|
||||
CertificateTypeProperty,
|
||||
DisableWebMedia,
|
||||
ParticleShapeType,
|
||||
|
||||
// Add new versions above here
|
||||
NUM_PACKET_TYPE,
|
||||
|
|
|
@ -97,7 +97,7 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
|||
_errorString = "Duplicate Id entries";
|
||||
return false;
|
||||
}
|
||||
|
||||
gotId = true;
|
||||
if (nextToken() != '"') {
|
||||
_errorString = "Invalid Id value";
|
||||
return false;
|
||||
|
@ -107,14 +107,21 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
|||
_errorString = "Invalid Id string";
|
||||
return false;
|
||||
}
|
||||
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
|
||||
if (idValue.isNull()) {
|
||||
_errorString = "Id value invalid UUID string: " + idString;
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedEntities["Id"] = idValue;
|
||||
gotId = true;
|
||||
// some older archives may have a null string id, so
|
||||
// return success without setting parsedEntities,
|
||||
// which will result in a new uuid for the restored
|
||||
// archive. (not parsing and using isNull as parsing
|
||||
// results in null if there is a corrupt string)
|
||||
|
||||
if (idString != "{00000000-0000-0000-0000-000000000000}") {
|
||||
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
|
||||
if (idValue.isNull()) {
|
||||
_errorString = "Id value invalid UUID string: " + idString;
|
||||
return false;
|
||||
}
|
||||
parsedEntities["Id"] = idValue;
|
||||
}
|
||||
} else if (key == "Version") {
|
||||
if (gotVersion) {
|
||||
_errorString = "Duplicate Version entries";
|
||||
|
|
|
@ -245,7 +245,7 @@ void OculusMobileDisplayPlugin::updatePresentPose() {
|
|||
});
|
||||
}
|
||||
|
||||
void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compsiteFramebuffer) {
|
||||
void OculusMobileDisplayPlugin::internalPresent() {
|
||||
VrHandler::pollTask();
|
||||
|
||||
if (!vrActive()) {
|
||||
|
@ -253,12 +253,8 @@ void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& c
|
|||
return;
|
||||
}
|
||||
|
||||
GLuint sourceTexture = 0;
|
||||
glm::uvec2 sourceSize;
|
||||
if (compsiteFramebuffer) {
|
||||
sourceTexture = getGLBackend()->getTextureID(compsiteFramebuffer->getRenderBuffer(0));
|
||||
sourceSize = { compsiteFramebuffer->getWidth(), compsiteFramebuffer->getHeight() };
|
||||
}
|
||||
auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0));
|
||||
glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() };
|
||||
VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking);
|
||||
_presentRate.increment();
|
||||
}
|
||||
|
|
|
@ -54,8 +54,8 @@ protected:
|
|||
void uncustomizeContext() override;
|
||||
|
||||
void updatePresentPose() override;
|
||||
void internalPresent(const gpu::FramebufferPointer&) override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override { throw std::runtime_error("Unused"); }
|
||||
void internalPresent() override;
|
||||
void hmdPresent() override { throw std::runtime_error("Unused"); }
|
||||
bool isHmdMounted() const override;
|
||||
bool alwaysPresent() const override { return true; }
|
||||
|
||||
|
|
|
@ -217,9 +217,8 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
|
|||
}
|
||||
return MOTION_TYPE_DYNAMIC;
|
||||
}
|
||||
if (_entity->isMovingRelativeToParent() ||
|
||||
_entity->hasActions() ||
|
||||
_entity->hasGrabs() ||
|
||||
if (_entity->hasActions() ||
|
||||
_entity->isMovingRelativeToParent() ||
|
||||
_entity->hasAncestorOfType(NestableType::Avatar)) {
|
||||
return MOTION_TYPE_KINEMATIC;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
|
||||
const DisplayPlugin::HUDOperator DisplayPlugin::DEFAULT_HUD_OPERATOR{ std::function<void(gpu::Batch&, const gpu::TexturePointer&, const gpu::FramebufferPointer&, bool mirror)>() };
|
||||
|
||||
DisplayPlugin::DisplayPlugin() : _hudOperator{ DEFAULT_HUD_OPERATOR } {
|
||||
}
|
||||
|
||||
int64_t DisplayPlugin::getPaintDelayUsecs() const {
|
||||
std::lock_guard<std::mutex> lock(_paintDelayMutex);
|
||||
return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0;
|
||||
|
@ -41,8 +35,8 @@ void DisplayPlugin::waitForPresent() {
|
|||
}
|
||||
}
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, const gpu::FramebufferPointer& compositeFramebuffer, bool mirror)> DisplayPlugin::getHUDOperator() {
|
||||
HUDOperator hudOperator;
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> DisplayPlugin::getHUDOperator() {
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> hudOperator;
|
||||
{
|
||||
QMutexLocker locker(&_presentMutex);
|
||||
hudOperator = _hudOperator;
|
||||
|
@ -54,5 +48,3 @@ glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const {
|
|||
static const glm::mat4 xform;
|
||||
return xform;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <gpu/Forward.h>
|
||||
#include "Plugin.h"
|
||||
#include "StencilMode.h"
|
||||
|
||||
class QOpenGLFramebufferObject;
|
||||
|
||||
|
@ -121,8 +122,6 @@ class DisplayPlugin : public Plugin, public HmdDisplay {
|
|||
Q_OBJECT
|
||||
using Parent = Plugin;
|
||||
public:
|
||||
DisplayPlugin();
|
||||
|
||||
virtual int getRequiredThreadCount() const { return 0; }
|
||||
virtual bool isHmd() const { return false; }
|
||||
virtual int getHmdScreen() const { return -1; }
|
||||
|
@ -216,14 +215,17 @@ public:
|
|||
void waitForPresent();
|
||||
float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec
|
||||
|
||||
using HUDOperator = std::function<void(gpu::Batch&, const gpu::TexturePointer&, const gpu::FramebufferPointer&, bool mirror)>;
|
||||
virtual HUDOperator getHUDOperator() final;
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator();
|
||||
|
||||
static const QString& MENU_PATH();
|
||||
|
||||
// for updating plugin-related commands. Mimics the input plugin.
|
||||
virtual void pluginUpdate() = 0;
|
||||
|
||||
virtual StencilMode getStencilMaskMode() const { return StencilMode::NONE; }
|
||||
using StencilMaskMeshOperator = std::function<void(gpu::Batch&)>;
|
||||
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; }
|
||||
|
||||
signals:
|
||||
void recommendedFramebufferSizeChanged(const QSize& size);
|
||||
void resetSensorsRequested();
|
||||
|
@ -234,8 +236,7 @@ protected:
|
|||
|
||||
gpu::ContextPointer _gpuContext;
|
||||
|
||||
static const HUDOperator DEFAULT_HUD_OPERATOR;
|
||||
HUDOperator _hudOperator;
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator { std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)>() };
|
||||
|
||||
MovingAverage<float, 10> _movingAveragePresent;
|
||||
|
||||
|
|
|
@ -126,8 +126,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra
|
|||
if (inputs) {
|
||||
batch.setFramebuffer(inputs);
|
||||
}
|
||||
if (renderContext->args->_hudOperator && renderContext->args->_blitFramebuffer) {
|
||||
renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_blitFramebuffer, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE);
|
||||
if (renderContext->args->_hudOperator) {
|
||||
renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
|
|
@ -19,7 +19,6 @@ using namespace render;
|
|||
|
||||
void PrepareStencil::configure(const Config& config) {
|
||||
_maskMode = config.maskMode;
|
||||
_forceDraw = config.forceDraw;
|
||||
}
|
||||
|
||||
graphics::MeshPointer PrepareStencil::getMesh() {
|
||||
|
@ -43,6 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() {
|
|||
auto state = std::make_shared<gpu::State>();
|
||||
drawMask(*state);
|
||||
state->setColorWriteMask(gpu::State::WRITE_NONE);
|
||||
state->setCullMode(gpu::State::CullMode::CULL_NONE);
|
||||
|
||||
_meshStencilPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
@ -64,30 +64,37 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() {
|
|||
void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
// Only draw the stencil mask if in HMD mode or not forced.
|
||||
if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) {
|
||||
StencilMode maskMode = _maskMode;
|
||||
std::function<void(gpu::Batch&)> maskOperator = [this](gpu::Batch& batch) {
|
||||
auto mesh = getMesh();
|
||||
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||
batch.setInputFormat((mesh->getVertexFormat()));
|
||||
batch.setInputStream(0, mesh->getVertexStream());
|
||||
|
||||
// Draw
|
||||
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||
};
|
||||
|
||||
if (maskMode == StencilMode::NONE) {
|
||||
maskMode = args->_stencilMode;
|
||||
maskOperator = args->_stencilMaskOperator;
|
||||
}
|
||||
|
||||
if (maskMode == StencilMode::NONE || (maskMode == StencilMode::MESH && !maskOperator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
doInBatch("PrepareStencil::run", args->_context, [&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
|
||||
if (_maskMode < 0) {
|
||||
batch.setPipeline(getMeshStencilPipeline());
|
||||
|
||||
auto mesh = getMesh();
|
||||
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||
batch.setInputFormat((mesh->getVertexFormat()));
|
||||
batch.setInputStream(0, mesh->getVertexStream());
|
||||
|
||||
// Draw
|
||||
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||
} else {
|
||||
if (maskMode == StencilMode::PAINT) {
|
||||
batch.setPipeline(getPaintStencilPipeline());
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
} else if (maskMode == StencilMode::MESH) {
|
||||
batch.setPipeline(getMeshStencilPipeline());
|
||||
maskOperator(batch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,17 +15,19 @@
|
|||
#include <render/Engine.h>
|
||||
#include <gpu/Pipeline.h>
|
||||
#include <graphics/Geometry.h>
|
||||
#include <StencilMode.h>
|
||||
|
||||
class PrepareStencilConfig : public render::Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty)
|
||||
Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty)
|
||||
Q_PROPERTY(StencilMode maskMode MEMBER maskMode NOTIFY dirty)
|
||||
|
||||
public:
|
||||
PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {}
|
||||
|
||||
int maskMode { 0 };
|
||||
bool forceDraw { false };
|
||||
// -1 -> don't force drawing (fallback to render args mode)
|
||||
// 0 -> force draw without mesh
|
||||
// 1 -> force draw with mesh
|
||||
StencilMode maskMode { StencilMode::NONE };
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
|
@ -66,8 +68,7 @@ private:
|
|||
graphics::MeshPointer _mesh;
|
||||
graphics::MeshPointer getMesh();
|
||||
|
||||
int _maskMode { 0 };
|
||||
bool _forceDraw { false };
|
||||
StencilMode _maskMode { StencilMode::NONE };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
|
||||
#include <GLMHelpers.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <StencilMode.h>
|
||||
|
||||
#include <gpu/Forward.h>
|
||||
#include "Forward.h"
|
||||
|
||||
|
||||
class AABox;
|
||||
|
||||
namespace render {
|
||||
|
@ -131,8 +131,11 @@ namespace render {
|
|||
render::ScenePointer _scene;
|
||||
int8_t _cameraMode { -1 };
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, const gpu::FramebufferPointer&, bool mirror)> _hudOperator;
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator;
|
||||
gpu::TexturePointer _hudTexture;
|
||||
|
||||
StencilMode _stencilMode { StencilMode::NONE };
|
||||
std::function<void(gpu::Batch&)> _stencilMaskOperator;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const {
|
|||
return glm::normalize(glm::cross(edge1, edge2));
|
||||
}
|
||||
|
||||
float Triangle::getArea() const {
|
||||
glm::vec3 edge1 = v1 - v0;
|
||||
glm::vec3 edge2 = v2 - v0;
|
||||
return 0.5f * glm::length(glm::cross(edge1, edge2));
|
||||
}
|
||||
|
||||
Triangle Triangle::operator*(const glm::mat4& transform) const {
|
||||
return {
|
||||
glm::vec3(transform * glm::vec4(v0, 1.0f)),
|
||||
|
|
|
@ -125,6 +125,7 @@ public:
|
|||
glm::vec3 v1;
|
||||
glm::vec3 v2;
|
||||
glm::vec3 getNormal() const;
|
||||
float getArea() const;
|
||||
Triangle operator*(const glm::mat4& transform) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
|
|||
int socketErrorMetaTypeId = qRegisterMetaType<QAbstractSocket::SocketError>();
|
||||
int voidLambdaType = qRegisterMetaType<std::function<void()>>();
|
||||
int variantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
||||
int stencilModeMetaTypeId = qRegisterMetaType<StencilMode>();
|
||||
|
||||
void registerMetaTypes(QScriptEngine* engine) {
|
||||
qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue);
|
||||
|
@ -64,6 +65,8 @@ void registerMetaTypes(QScriptEngine* engine) {
|
|||
qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
|
||||
|
||||
qScriptRegisterMetaType(engine, stencilModeToScriptValue, stencilModeFromScriptValue);
|
||||
}
|
||||
|
||||
QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) {
|
||||
|
@ -1283,4 +1286,12 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe
|
|||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
QScriptValue stencilModeToScriptValue(QScriptEngine* engine, const StencilMode& stencilMode) {
|
||||
return engine->newVariant((int)stencilMode);
|
||||
}
|
||||
|
||||
void stencilModeFromScriptValue(const QScriptValue& object, StencilMode& stencilMode) {
|
||||
stencilMode = StencilMode(object.toVariant().toInt());
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
#include "shared/Bilateral.h"
|
||||
#include "Transform.h"
|
||||
#include "PhysicsCollisionGroups.h"
|
||||
#include "StencilMode.h"
|
||||
|
||||
class QColor;
|
||||
class QUrl;
|
||||
|
@ -729,5 +730,8 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>
|
|||
|
||||
QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures);
|
||||
|
||||
Q_DECLARE_METATYPE(StencilMode)
|
||||
QScriptValue stencilModeToScriptValue(QScriptEngine* engine, const StencilMode& stencilMode);
|
||||
void stencilModeFromScriptValue(const QScriptValue &object, StencilMode& stencilMode);
|
||||
|
||||
#endif // hifi_RegisteredMetaTypes_h
|
||||
|
|
19
libraries/shared/src/StencilMode.h
Normal file
19
libraries/shared/src/StencilMode.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 3/26/19.
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_StencilMode_h
|
||||
#define hifi_StencilMode_h
|
||||
|
||||
enum class StencilMode {
|
||||
NONE = -1, // for legacy reasons, this is -1
|
||||
PAINT = 0,
|
||||
MESH = 1
|
||||
};
|
||||
|
||||
#endif // hifi_StencilMode_h
|
||||
|
|
@ -18,7 +18,7 @@ if (WIN32 AND (NOT USE_GLES))
|
|||
link_hifi_libraries(
|
||||
shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml
|
||||
plugins ui-plugins display-plugins input-plugins
|
||||
audio-client networking render-utils
|
||||
audio-client networking render-utils graphics
|
||||
${PLATFORM_GL_BACKEND}
|
||||
)
|
||||
include_hifi_library_headers(octree)
|
||||
|
|
|
@ -227,3 +227,66 @@ QVector<glm::vec3> OculusBaseDisplayPlugin::getSensorPositions() {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
DisplayPlugin::StencilMaskMeshOperator OculusBaseDisplayPlugin::getStencilMaskMeshOperator() {
|
||||
if (_session) {
|
||||
if (!_stencilMeshesInitialized) {
|
||||
_stencilMeshesInitialized = true;
|
||||
ovr::for_each_eye([&](ovrEyeType eye) {
|
||||
ovrFovStencilDesc stencilDesc = {
|
||||
ovrFovStencil_HiddenArea, 0, eye,
|
||||
_eyeRenderDescs[eye].Fov, _eyeRenderDescs[eye].HmdToEyePose.Orientation
|
||||
};
|
||||
// First we get the size of the buffer we need
|
||||
ovrFovStencilMeshBuffer buffer = { 0, 0, nullptr, 0, 0, nullptr };
|
||||
ovrResult result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
|
||||
if (!OVR_SUCCESS(result)) {
|
||||
_stencilMeshesInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ovrVector2f> ovrVertices(buffer.UsedVertexCount);
|
||||
std::vector<uint16_t> ovrIndices(buffer.UsedIndexCount);
|
||||
|
||||
// Now we populate the actual buffer
|
||||
buffer = { (int)ovrVertices.size(), 0, ovrVertices.data(), (int)ovrIndices.size(), 0, ovrIndices.data() };
|
||||
result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
|
||||
|
||||
if (!OVR_SUCCESS(result)) {
|
||||
_stencilMeshesInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<glm::vec3> vertices;
|
||||
vertices.reserve(ovrVertices.size());
|
||||
for (auto& ovrVertex : ovrVertices) {
|
||||
// We need the vertices in clip space
|
||||
vertices.emplace_back(ovrVertex.x - (1.0f - (float)eye), 2.0f * ovrVertex.y - 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> indices;
|
||||
indices.reserve(ovrIndices.size());
|
||||
for (auto& ovrIndex : ovrIndices) {
|
||||
indices.push_back(ovrIndex);
|
||||
}
|
||||
|
||||
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
|
||||
});
|
||||
}
|
||||
|
||||
if (_stencilMeshesInitialized) {
|
||||
return [&](gpu::Batch& batch) {
|
||||
for (auto& mesh : _stencilMeshes) {
|
||||
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||
batch.setInputFormat((mesh->getVertexFormat()));
|
||||
batch.setInputStream(0, mesh->getVertexStream());
|
||||
|
||||
// Draw
|
||||
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
#define OVRPL_DISABLED
|
||||
#include <OVR_Platform.h>
|
||||
|
||||
#include <graphics/Geometry.h>
|
||||
|
||||
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
|
||||
using Parent = HmdDisplayPlugin;
|
||||
public:
|
||||
|
@ -34,6 +36,9 @@ public:
|
|||
QRectF getPlayAreaRect() override;
|
||||
QVector<glm::vec3> getSensorPositions() override;
|
||||
|
||||
virtual StencilMode getStencilMaskMode() const override { return StencilMode::MESH; }
|
||||
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
|
||||
|
||||
protected:
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
|
@ -52,4 +57,7 @@ protected:
|
|||
// ovrLayerEyeFovDepth _depthLayer;
|
||||
bool _hmdMounted { false };
|
||||
bool _visible { true };
|
||||
|
||||
std::array<graphics::MeshPointer, 2> _stencilMeshes;
|
||||
bool _stencilMeshesInitialized { false };
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ public:
|
|||
bool isSupported() const override;
|
||||
|
||||
protected:
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override {}
|
||||
void hmdPresent() override {}
|
||||
bool isHmdMounted() const override { return true; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -108,16 +108,13 @@ void OculusDisplayPlugin::customizeContext() {
|
|||
}
|
||||
|
||||
void OculusDisplayPlugin::uncustomizeContext() {
|
||||
|
||||
#if 0
|
||||
if (_currentFrame && _currentFrame->framebuffer) {
|
||||
// Present a final black frame to the HMD
|
||||
_currentFrame->framebuffer->Bound(FramebufferTarget::Draw, [] {
|
||||
Context::ClearColor(0, 0, 0, 1);
|
||||
Context::Clear().ColorBuffer();
|
||||
});
|
||||
hmdPresent();
|
||||
}
|
||||
// Present a final black frame to the HMD
|
||||
_compositeFramebuffer->Bound(FramebufferTarget::Draw, [] {
|
||||
Context::ClearColor(0, 0, 0, 1);
|
||||
Context::Clear().ColorBuffer();
|
||||
});
|
||||
hmdPresent();
|
||||
#endif
|
||||
|
||||
ovr_DestroyTextureSwapChain(_session, _textureSwapChain);
|
||||
|
@ -130,7 +127,7 @@ void OculusDisplayPlugin::uncustomizeContext() {
|
|||
static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC);
|
||||
static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC);
|
||||
|
||||
void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OculusDisplayPlugin::hmdPresent() {
|
||||
static uint64_t lastSubmitEnd = 0;
|
||||
|
||||
if (!_customized) {
|
||||
|
@ -160,8 +157,15 @@ void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFra
|
|||
auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer);
|
||||
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0);
|
||||
render([&](gpu::Batch& batch) {
|
||||
auto viewport = ivec4(uvec2(), _outputFramebuffer->getSize());
|
||||
renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, viewport, _outputFramebuffer);
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(_outputFramebuffer);
|
||||
batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize()));
|
||||
batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize()));
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_presentPipeline);
|
||||
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ protected:
|
|||
QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; }
|
||||
|
||||
bool internalActivate() override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override;
|
||||
void hmdPresent() override;
|
||||
bool isHmdMounted() const override;
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
|
|
|
@ -237,7 +237,7 @@ void OculusLegacyDisplayPlugin::uncustomizeContext() {
|
|||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
||||
void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OculusLegacyDisplayPlugin::hmdPresent() {
|
||||
if (!_hswDismissed) {
|
||||
ovrHSWDisplayState hswState;
|
||||
ovrHmd_GetHSWDisplayState(_hmd, &hswState);
|
||||
|
@ -252,7 +252,7 @@ void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compos
|
|||
memset(eyePoses, 0, sizeof(ovrPosef) * 2);
|
||||
eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation;
|
||||
|
||||
GLint texture = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0));
|
||||
GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0));
|
||||
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
if (_hmdWindow->makeCurrent()) {
|
||||
|
|
|
@ -39,7 +39,7 @@ protected:
|
|||
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override;
|
||||
void hmdPresent() override;
|
||||
bool isHmdMounted() const override { return true; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -511,13 +511,13 @@ void OpenVrDisplayPlugin::customizeContext() {
|
|||
Parent::customizeContext();
|
||||
|
||||
if (_threadedSubmit) {
|
||||
// _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0);
|
||||
_compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0);
|
||||
for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) {
|
||||
// if (0 != i) {
|
||||
if (0 != i) {
|
||||
_compositeInfos[i].texture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x,
|
||||
_renderTargetSize.y, gpu::Texture::SINGLE_MIP,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT));
|
||||
// }
|
||||
}
|
||||
_compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture);
|
||||
}
|
||||
_submitThread->_canvas = _submitCanvas;
|
||||
|
@ -613,17 +613,17 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|||
return Parent::beginFrameRender(frameIndex);
|
||||
}
|
||||
|
||||
void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenVrDisplayPlugin::compositeLayers() {
|
||||
if (_threadedSubmit) {
|
||||
++_renderingIndex;
|
||||
_renderingIndex %= COMPOSITING_BUFFER_SIZE;
|
||||
|
||||
auto& newComposite = _compositeInfos[_renderingIndex];
|
||||
newComposite.pose = _currentPresentFrameInfo.presentPose;
|
||||
compositeFramebuffer->setRenderBuffer(0, newComposite.texture);
|
||||
_compositeFramebuffer->setRenderBuffer(0, newComposite.texture);
|
||||
}
|
||||
|
||||
Parent::compositeLayers(compositeFramebuffer);
|
||||
Parent::compositeLayers();
|
||||
|
||||
if (_threadedSubmit) {
|
||||
auto& newComposite = _compositeInfos[_renderingIndex];
|
||||
|
@ -645,13 +645,13 @@ void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi
|
|||
}
|
||||
}
|
||||
|
||||
void OpenVrDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenVrDisplayPlugin::hmdPresent() {
|
||||
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
|
||||
|
||||
if (_threadedSubmit) {
|
||||
_submitThread->waitForPresent();
|
||||
} else {
|
||||
GLuint glTexId = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0));
|
||||
GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0));
|
||||
vr::Texture_t vrTexture{ (void*)(uintptr_t)glTexId, vr::TextureType_OpenGL, vr::ColorSpace_Auto };
|
||||
vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT);
|
||||
vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT);
|
||||
|
@ -784,3 +784,49 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() {
|
|||
|
||||
return QRectF(center.x, center.y, dimensions.x, dimensions.y);
|
||||
}
|
||||
|
||||
|
||||
DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() {
|
||||
if (_system) {
|
||||
if (!_stencilMeshesInitialized) {
|
||||
_stencilMeshesInitialized = true;
|
||||
for (auto eye : VR_EYES) {
|
||||
vr::HiddenAreaMesh_t stencilMesh = _system->GetHiddenAreaMesh(eye);
|
||||
if (stencilMesh.pVertexData && stencilMesh.unTriangleCount > 0) {
|
||||
std::vector<glm::vec3> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
const int NUM_INDICES_PER_TRIANGLE = 3;
|
||||
int numIndices = stencilMesh.unTriangleCount * NUM_INDICES_PER_TRIANGLE;
|
||||
vertices.reserve(numIndices);
|
||||
indices.reserve(numIndices);
|
||||
for (int i = 0; i < numIndices; i++) {
|
||||
vr::HmdVector2_t vertex2D = stencilMesh.pVertexData[i];
|
||||
// We need the vertices in clip space
|
||||
vertices.emplace_back(vertex2D.v[0] - (1.0f - (float)eye), 2.0f * vertex2D.v[1] - 1.0f, 0.0f);
|
||||
indices.push_back(i);
|
||||
}
|
||||
|
||||
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
|
||||
} else {
|
||||
_stencilMeshesInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_stencilMeshesInitialized) {
|
||||
return [&](gpu::Batch& batch) {
|
||||
for (auto& mesh : _stencilMeshes) {
|
||||
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||
batch.setInputFormat((mesh->getVertexFormat()));
|
||||
batch.setInputStream(0, mesh->getVertexStream());
|
||||
|
||||
// Draw
|
||||
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||
|
||||
#include <graphics/Geometry.h>
|
||||
|
||||
const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only.
|
||||
|
||||
namespace gl {
|
||||
|
@ -67,13 +69,16 @@ public:
|
|||
|
||||
QRectF getPlayAreaRect() override;
|
||||
|
||||
virtual StencilMode getStencilMaskMode() const override{ return StencilMode::MESH; }
|
||||
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
|
||||
|
||||
protected:
|
||||
bool internalActivate() override;
|
||||
void internalDeactivate() override;
|
||||
void updatePresentPose() override;
|
||||
|
||||
void compositeLayers(const gpu::FramebufferPointer&) override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override;
|
||||
void compositeLayers() override;
|
||||
void hmdPresent() override;
|
||||
bool isHmdMounted() const override;
|
||||
void postPreview() override;
|
||||
|
||||
|
@ -94,4 +99,7 @@ private:
|
|||
bool _asyncReprojectionActive { false };
|
||||
|
||||
bool _hmdMounted { false };
|
||||
|
||||
std::array<graphics::MeshPointer, 2> _stencilMeshes;
|
||||
bool _stencilMeshesInitialized { false };
|
||||
};
|
||||
|
|
|
@ -227,6 +227,14 @@
|
|||
"speedSpread": {
|
||||
"tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
|
||||
},
|
||||
"particleShapeType": {
|
||||
"tooltip": "The shape of the surface from which to emit particles.",
|
||||
"jsPropertyName": "shapeType"
|
||||
},
|
||||
"particleCompoundShapeURL": {
|
||||
"tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".",
|
||||
"jsPropertyName": "compoundShapeURL"
|
||||
},
|
||||
"emitDimensions": {
|
||||
"tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
|
||||
},
|
||||
|
|
|
@ -807,6 +807,21 @@ const GROUPS = [
|
|||
decimals: 2,
|
||||
propertyID: "speedSpread",
|
||||
},
|
||||
{
|
||||
label: "Shape Type",
|
||||
type: "dropdown",
|
||||
options: { "box": "Box", "ellipsoid": "Ellipsoid",
|
||||
"cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane",
|
||||
"compound": "Use Compound Shape URL" },
|
||||
propertyID: "particleShapeType",
|
||||
propertyName: "shapeType",
|
||||
},
|
||||
{
|
||||
label: "Compound Shape URL",
|
||||
type: "string",
|
||||
propertyID: "particleCompoundShapeURL",
|
||||
propertyName: "compoundShapeURL",
|
||||
},
|
||||
{
|
||||
label: "Emit Dimensions",
|
||||
type: "vec3",
|
||||
|
|
|
@ -480,7 +480,7 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText
|
|||
if (differenceFileFound) {
|
||||
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/" << _comparisonImageFilename << "\" width = \"576\" height = \"324\" ></td>\n";
|
||||
} else {
|
||||
stream << "\t\t\t\t<td><h2>No Image Found</h2>\n";
|
||||
stream << "\t\t\t\t<td><h2>Image size mismatch</h2>\n";
|
||||
}
|
||||
|
||||
stream << "\t\t\t</tr>\n";
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "Nitpick.h"
|
||||
#include "Platform.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
|
@ -40,9 +41,15 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
|||
|
||||
setWindowTitle("Nitpick - " + nitpickVersion);
|
||||
|
||||
clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone";
|
||||
_ui.clientProfileComboBox->insertItems(0, clientProfiles);
|
||||
_GPUVendors << "Nvidia" << "AMD";
|
||||
_ui.gpuVendorComboBox->insertItems(0, _GPUVendors);
|
||||
|
||||
QString gpuVendor = Platform::getGraphicsCardType().toUpper();
|
||||
if (gpuVendor.contains("NVIDIA")) {
|
||||
_ui.gpuVendorComboBox->setCurrentIndex(0);
|
||||
} else {
|
||||
_ui.gpuVendorComboBox->setCurrentIndex(1);
|
||||
}
|
||||
}
|
||||
|
||||
Nitpick::~Nitpick() {
|
||||
|
@ -126,13 +133,14 @@ void Nitpick::setup() {
|
|||
);
|
||||
}
|
||||
|
||||
void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branch,
|
||||
const QString& user
|
||||
void Nitpick::startTestsEvaluation(
|
||||
const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branch,
|
||||
const QString& user
|
||||
) {
|
||||
_testCreator->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user);
|
||||
_testCreator->startTestsEvaluation(_ui.gpuVendorComboBox, isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user);
|
||||
}
|
||||
|
||||
void Nitpick::on_tabWidget_currentChanged(int index) {
|
||||
|
@ -144,9 +152,11 @@ void Nitpick::on_tabWidget_currentChanged(int index) {
|
|||
#endif
|
||||
_ui.userLineEdit->setDisabled(false);
|
||||
_ui.branchLineEdit->setDisabled(false);
|
||||
_ui.gpuVendorComboBox->setDisabled(false);
|
||||
} else {
|
||||
_ui.userLineEdit->setDisabled(true);
|
||||
_ui.branchLineEdit->setDisabled(true);
|
||||
_ui.gpuVendorComboBox->setDisabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +169,7 @@ void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() {
|
|||
}
|
||||
|
||||
void Nitpick::on_createTestsPushbutton_clicked() {
|
||||
_testCreator->createTests(_ui.clientProfileComboBox->currentText());
|
||||
_testCreator->createTests(_ui.gpuVendorComboBox->currentText());
|
||||
}
|
||||
|
||||
void Nitpick::on_createMDFilePushbutton_clicked() {
|
||||
|
@ -250,7 +260,7 @@ void Nitpick::on_showTaskbarPushbutton_clicked() {
|
|||
}
|
||||
|
||||
void Nitpick::on_evaluateTestsPushbutton_clicked() {
|
||||
_testCreator->startTestsEvaluation(false, false);
|
||||
_testCreator->startTestsEvaluation(_ui.gpuVendorComboBox, false, false);
|
||||
}
|
||||
|
||||
void Nitpick::on_closePushbutton_clicked() {
|
||||
|
|
|
@ -28,11 +28,13 @@ public:
|
|||
|
||||
void setup();
|
||||
|
||||
void startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branch,
|
||||
const QString& user);
|
||||
void startTestsEvaluation(
|
||||
const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branch,
|
||||
const QString& user
|
||||
);
|
||||
|
||||
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
|
||||
|
||||
|
@ -111,7 +113,7 @@ private:
|
|||
|
||||
bool _isRunningFromCommandline{ false };
|
||||
|
||||
QStringList clientProfiles;
|
||||
QStringList _GPUVendors;
|
||||
};
|
||||
|
||||
#endif // hifi_Nitpick_h
|
121
tools/nitpick/src/Platform.cpp
Normal file
121
tools/nitpick/src/Platform.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// Platform.h
|
||||
//
|
||||
// Created by Nissim Hadar on 2 Apr 2019.
|
||||
// Copyright 2013 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 "Platform.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <dxgi1_3.h>
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
#elif defined Q_OS_MAC
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <sstream>
|
||||
#include <QStringList>
|
||||
#endif
|
||||
|
||||
QString Platform::getGraphicsCardType() {
|
||||
#ifdef Q_OS_WIN
|
||||
IDXGIFactory1* pFactory = nullptr;
|
||||
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory));
|
||||
if (hr != S_OK || pFactory == nullptr) {
|
||||
return "Unable to create DXGI";
|
||||
}
|
||||
std::vector<int> validAdapterList;
|
||||
|
||||
using AdapterEntry = std::pair<std::pair<DXGI_ADAPTER_DESC1, LARGE_INTEGER>, std::vector<DXGI_OUTPUT_DESC>>;
|
||||
std::vector<AdapterEntry> adapterToOutputs;
|
||||
// Enumerate adapters and outputs
|
||||
{
|
||||
UINT adapterNum = 0;
|
||||
IDXGIAdapter1* pAdapter = nullptr;
|
||||
while (pFactory->EnumAdapters1(adapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND) {
|
||||
|
||||
// Found an adapter, get descriptor
|
||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
||||
pAdapter->GetDesc1(&adapterDesc);
|
||||
|
||||
LARGE_INTEGER version;
|
||||
hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version);
|
||||
|
||||
std::wstring wDescription (adapterDesc.Description);
|
||||
std::string description(wDescription.begin(), wDescription.end());
|
||||
|
||||
AdapterEntry adapterEntry;
|
||||
adapterEntry.first.first = adapterDesc;
|
||||
adapterEntry.first.second = version;
|
||||
|
||||
|
||||
|
||||
UINT outputNum = 0;
|
||||
IDXGIOutput * pOutput;
|
||||
bool hasOutputConnectedToDesktop = false;
|
||||
while (pAdapter->EnumOutputs(outputNum, &pOutput) != DXGI_ERROR_NOT_FOUND) {
|
||||
|
||||
// FOund an output attached to the adapter, get descriptor
|
||||
DXGI_OUTPUT_DESC outputDesc;
|
||||
pOutput->GetDesc(&outputDesc);
|
||||
|
||||
adapterEntry.second.push_back(outputDesc);
|
||||
|
||||
std::wstring wDeviceName(outputDesc.DeviceName);
|
||||
std::string deviceName(wDeviceName.begin(), wDeviceName.end());
|
||||
|
||||
hasOutputConnectedToDesktop |= (bool)outputDesc.AttachedToDesktop;
|
||||
|
||||
pOutput->Release();
|
||||
outputNum++;
|
||||
}
|
||||
|
||||
adapterToOutputs.push_back(adapterEntry);
|
||||
|
||||
// add this adapter to the valid list if has output
|
||||
if (hasOutputConnectedToDesktop && !adapterEntry.second.empty()) {
|
||||
validAdapterList.push_back(adapterNum);
|
||||
}
|
||||
|
||||
pAdapter->Release();
|
||||
adapterNum++;
|
||||
}
|
||||
}
|
||||
pFactory->Release();
|
||||
|
||||
if (!validAdapterList.empty()) {
|
||||
auto& adapterEntry = adapterToOutputs[validAdapterList.front()];
|
||||
|
||||
std::wstring wDescription(adapterEntry.first.first.Description);
|
||||
std::string description(wDescription.begin(), wDescription.end());
|
||||
return QString(description.c_str());
|
||||
}
|
||||
|
||||
#elif defined Q_OS_MAC
|
||||
FILE* stream = popen("system_profiler SPDisplaysDataType | grep Chipset", "r");
|
||||
|
||||
std::ostringstream hostStream;
|
||||
while (!feof(stream) && !ferror(stream)) {
|
||||
char buf[128];
|
||||
int bytesRead = fread(buf, 1, 128, stream);
|
||||
hostStream.write(buf, bytesRead);
|
||||
}
|
||||
|
||||
QString result = QString::fromStdString(hostStream.str());
|
||||
QStringList parts = result.split('\n');
|
||||
for (int i = 0; i < parts.size(); ++i) {
|
||||
if (parts[i].toLower().contains("radeon") || parts[i].toLower().contains("nvidia")) {
|
||||
return parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
// unkown graphics card
|
||||
return "UNKNOWN";
|
||||
#else
|
||||
return QString("NO IMPLEMENTED");
|
||||
#endif
|
||||
return "";
|
||||
}
|
20
tools/nitpick/src/Platform.h
Normal file
20
tools/nitpick/src/Platform.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Platform.h
|
||||
//
|
||||
// Created by Nissim Hadar on 2 Apr 2019.
|
||||
// Copyright 2013 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_Platform_h
|
||||
#define hifi_Platform_h
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Platform {
|
||||
public:
|
||||
static QString getGraphicsCardType();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -67,7 +67,7 @@ QString TestCreator::zipAndDeleteTestResultsFolder() {
|
|||
return zippedResultsFileName;
|
||||
}
|
||||
|
||||
int TestCreator::compareImageLists() {
|
||||
int TestCreator::compareImageLists(const QString& gpuVendor) {
|
||||
_progressBar->setMinimum(0);
|
||||
_progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
|
||||
_progressBar->setValue(0);
|
||||
|
@ -108,8 +108,16 @@ int TestCreator::compareImageLists() {
|
|||
};
|
||||
|
||||
_mismatchWindow.setTestResult(testResult);
|
||||
|
||||
if (similarityIndex < THRESHOLD_GLOBAL || worstTileValue < THRESHOLD_LOCAL) {
|
||||
|
||||
// Lower threshold for non-Nvidia GPUs
|
||||
double thresholdGlobal{ 0.9995 };
|
||||
double thresholdLocal{ 0.6 };
|
||||
if (gpuVendor != "Nvidia") {
|
||||
thresholdGlobal = 0.97;
|
||||
thresholdLocal = 0.2;
|
||||
}
|
||||
|
||||
if (similarityIndex < thresholdGlobal || worstTileValue < thresholdLocal) {
|
||||
if (!isInteractiveMode) {
|
||||
++numberOfFailures;
|
||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
||||
|
@ -258,11 +266,13 @@ void::TestCreator::appendTestResultsToFile(QString testResultFilename, bool hasF
|
|||
}
|
||||
}
|
||||
|
||||
void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branchFromCommandLine,
|
||||
const QString& userFromCommandLine
|
||||
void TestCreator::startTestsEvaluation(
|
||||
QComboBox *gpuVendor,
|
||||
const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory,
|
||||
const QString& branchFromCommandLine,
|
||||
const QString& userFromCommandLine
|
||||
) {
|
||||
_isRunningFromCommandLine = isRunningFromCommandLine;
|
||||
_isRunningInAutomaticTestRun = isRunningInAutomaticTestRun;
|
||||
|
@ -332,12 +342,12 @@ void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine,
|
|||
}
|
||||
|
||||
_downloader->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this);
|
||||
finishTestsEvaluation();
|
||||
finishTestsEvaluation(gpuVendor->currentText());
|
||||
}
|
||||
|
||||
void TestCreator::finishTestsEvaluation() {
|
||||
void TestCreator::finishTestsEvaluation(const QString& gpuVendor) {
|
||||
// First - compare the pairs of images
|
||||
int numberOfFailures = compareImageLists();
|
||||
int numberOfFailures = compareImageLists(gpuVendor);
|
||||
|
||||
// Next - check text results
|
||||
numberOfFailures += checkTextResults();
|
||||
|
@ -430,8 +440,9 @@ void TestCreator::createTests(const QString& clientProfile) {
|
|||
parent += "/";
|
||||
}
|
||||
|
||||
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
if (!createAllFilesSetup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If user canceled then restore previous selection and return
|
||||
if (_testsRootDirectory == "") {
|
||||
|
@ -881,23 +892,12 @@ void TestCreator::createRecursiveScript(const QString& directory, bool interacti
|
|||
}
|
||||
|
||||
void TestCreator::createTestsOutline() {
|
||||
QString previousSelection = _testDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
_testDirectory =
|
||||
QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user canceled then restore previous selection and return
|
||||
if (_testDirectory == "") {
|
||||
_testDirectory = previousSelection;
|
||||
if (!createAllFilesSetup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString testsOutlineFilename { "testsOutline.md" };
|
||||
QString mdFilename(_testDirectory + "/" + testsOutlineFilename);
|
||||
QString mdFilename(_testsRootDirectory + "/" + testsOutlineFilename);
|
||||
QFile mdFile(mdFilename);
|
||||
if (!mdFile.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
|
||||
|
@ -911,10 +911,10 @@ void TestCreator::createTestsOutline() {
|
|||
stream << "Directories with an appended (*) have an automatic test\n\n";
|
||||
|
||||
// We need to know our current depth, as this isn't given by QDirIterator
|
||||
int rootDepth { _testDirectory.count('/') };
|
||||
int rootDepth { _testsRootDirectory.count('/') };
|
||||
|
||||
// Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file
|
||||
QDirIterator it(_testDirectory, QDirIterator::Subdirectories);
|
||||
QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString directory = it.next();
|
||||
|
||||
|
|
|
@ -45,13 +45,16 @@ class TestCreator {
|
|||
public:
|
||||
TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode);
|
||||
|
||||
void startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory = QString(),
|
||||
const QString& branchFromCommandLine = QString(),
|
||||
const QString& userFromCommandLine = QString());
|
||||
void startTestsEvaluation(
|
||||
QComboBox *gpuVendor,
|
||||
const bool isRunningFromCommandLine,
|
||||
const bool isRunningInAutomaticTestRun,
|
||||
const QString& snapshotDirectory = QString(),
|
||||
const QString& branchFromCommandLine = QString(),
|
||||
const QString& userFromCommandLine = QString()
|
||||
);
|
||||
|
||||
void finishTestsEvaluation();
|
||||
void finishTestsEvaluation(const QString& gpuVendor);
|
||||
|
||||
void createTests(const QString& clientProfile);
|
||||
|
||||
|
@ -79,7 +82,7 @@ public:
|
|||
void createRecursiveScript();
|
||||
void createRecursiveScript(const QString& directory, bool interactiveMode);
|
||||
|
||||
int compareImageLists();
|
||||
int compareImageLists(const QString& gpuVendor);
|
||||
int checkTextResults();
|
||||
|
||||
QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory);
|
||||
|
@ -124,9 +127,6 @@ private:
|
|||
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
||||
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
|
||||
|
||||
const double THRESHOLD_GLOBAL{ 0.9995 };
|
||||
const double THRESHOLD_LOCAL { 0.6 };
|
||||
|
||||
QDir _imageDirectory;
|
||||
|
||||
MismatchWindow _mismatchWindow;
|
||||
|
|
|
@ -60,5 +60,5 @@ const double R_Y = 0.212655f;
|
|||
const double G_Y = 0.715158f;
|
||||
const double B_Y = 0.072187f;
|
||||
|
||||
const QString nitpickVersion{ "v3.1.5" };
|
||||
const QString nitpickVersion{ "v3.2.0" };
|
||||
#endif // hifi_common_h
|
|
@ -56,7 +56,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>40</y>
|
||||
<y>120</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>180</y>
|
||||
<y>200</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>180</y>
|
||||
<y>200</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -94,7 +94,7 @@
|
|||
<widget class="QPushButton" name="createTestsOutlinePushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<x>320</x>
|
||||
<y>120</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
|
@ -108,7 +108,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>300</y>
|
||||
<y>360</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -121,7 +121,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>300</y>
|
||||
<y>360</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -134,7 +134,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>240</y>
|
||||
<y>280</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -147,7 +147,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>240</y>
|
||||
<y>280</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -156,16 +156,6 @@
|
|||
<string>Create all testAuto scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="clientProfileComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>40</y>
|
||||
<width>120</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
|
@ -927,8 +917,8 @@
|
|||
<rect>
|
||||
<x>330</x>
|
||||
<y>190</y>
|
||||
<width>181</width>
|
||||
<height>51</height>
|
||||
<width>180</width>
|
||||
<height>50</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -1186,6 +1176,39 @@
|
|||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="gpuVendorComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>450</x>
|
||||
<y>60</y>
|
||||
<width>180</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>505</x>
|
||||
<y>40</y>
|
||||
<width>81</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>GPU Vendor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <StatTracker.h>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <QtCore/QCommandLineParser>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
#include <TextureBaker.h>
|
||||
|
||||
#include "BakerCLI.h"
|
||||
|
|
Loading…
Reference in a new issue