diff --git a/CMakeLists.txt b/CMakeLists.txt index 9712b2d32e..4527e06418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,10 +15,6 @@ if (WIN32) cmake_policy(SET CMP0020 NEW) endif (WIN32) -if (POLICY CMP0028) - cmake_policy(SET CMP0028 OLD) -endif () - if (POLICY CMP0043) cmake_policy(SET CMP0043 OLD) endif () @@ -83,7 +79,7 @@ endif(WIN32) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3") - # GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations + # GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations add_definitions(-DGLM_FORCE_PURE) endif() endif() diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index c44fdf74ff..e50c7180d1 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -184,6 +184,9 @@ void Agent::run() { // make sure we hear about connected nodes so we can grab an ATP script if a request is pending connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); + // make sure we hear about dissappearing nodes so we can clear the entity tree if an entity server goes away + connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &Agent::nodeKilled); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer }); @@ -259,6 +262,13 @@ void Agent::nodeActivated(SharedNodePointer activatedNode) { } } +void Agent::nodeKilled(SharedNodePointer killedNode) { + if (killedNode->getType() == NodeType::EntityServer) { + // an entity server has gone away, ask the headless viewer to clear its tree + _entityViewer.clear(); + } +} + void Agent::negotiateAudioFormat() { auto nodeList = DependencyManager::get(); auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index da60a07367..b10f72db9a 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -77,6 +77,7 @@ private slots: void handleSelectedAudioFormat(QSharedPointer message); void nodeActivated(SharedNodePointer activatedNode); + void nodeKilled(SharedNodePointer killedNode); void processAgentAvatar(); void processAgentAvatarAudio(); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index af5f2c904e..db97da751f 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -935,39 +935,7 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m // so here we just store a special file at our persist path // and then force a stop of the server so that it can pick it up when it relaunches if (!_persistAbsoluteFilePath.isEmpty()) { - - // before we restart the server and make it try and load this data, let's make sure it is valid - auto compressedOctree = message->getMessage(); - QByteArray jsonOctree; - - // assume we have GZipped content - bool wasCompressed = gunzip(compressedOctree, jsonOctree); - if (!wasCompressed) { - // the source was not compressed, assume we were sent regular JSON data - jsonOctree = compressedOctree; - } - - // check the JSON data to verify it is an object - if (QJsonDocument::fromJson(jsonOctree).isObject()) { - if (!wasCompressed) { - // source was not compressed, we compress it before we write it locally - gzip(jsonOctree, compressedOctree); - } - - // write the compressed octree data to a special file - auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); - QFile replacementFile(replacementFilePath); - if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { - // we've now written our replacement file, time to take the server down so it can - // process it when it comes back up - qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; - setFinished(true); - } else { - qWarning() << "Could not write replacement octree data to file - refusing to process"; - } - } else { - qDebug() << "Received replacement octree file that is invalid - refusing to process"; - } + replaceContentFromMessageData(message->getMessage()); } else { qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; } @@ -977,6 +945,68 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m } } +// Message->getMessage() contains a QByteArray representation of the URL to download from +void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer message) { + qInfo() << "Received request to replace content from a url"; + if (!_isFinished && !_isShuttingDown) { + // This call comes from Interface, so we skip our domain server check + // but confirm that we have permissions to replace content sets + if (DependencyManager::get()->getThisNodeCanReplaceContent()) { + if (!_persistAbsoluteFilePath.isEmpty()) { + // Convert message data into our URL + QString url(message->getMessage()); + QUrl modelsURL = QUrl(url, QUrl::StrictMode); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request(modelsURL); + QNetworkReply* reply = networkAccessManager.get(request); + connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() { + QNetworkReply::NetworkError networkError = reply->error(); + if (networkError == QNetworkReply::NoError) { + QByteArray contents = reply->readAll(); + replaceContentFromMessageData(contents); + } else { + qDebug() << "Error downloading JSON from specified file"; + } + }); + } else { + qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; + } + } + } +} + +void OctreeServer::replaceContentFromMessageData(QByteArray content) { + //Assume we have compressed data + auto compressedOctree = content; + QByteArray jsonOctree; + + bool wasCompressed = gunzip(compressedOctree, jsonOctree); + if (!wasCompressed) { + // the source was not compressed, assume we were sent regular JSON data + jsonOctree = compressedOctree; + } + // check the JSON data to verify it is an object + if (QJsonDocument::fromJson(jsonOctree).isObject()) { + if (!wasCompressed) { + // source was not compressed, we compress it before we write it locally + gzip(jsonOctree, compressedOctree); + } + // write the compressed octree data to a special file + auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); + QFile replacementFile(replacementFilePath); + if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { + // we've now written our replacement file, time to take the server down so it can + // process it when it comes back up + qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; + setFinished(true); + } else { + qWarning() << "Could not write replacement octree data to file - refusing to process"; + } + } else { + qDebug() << "Received replacement octree file that is invalid - refusing to process"; + } +} + bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) { result = false; // assume it doesn't exist bool optionAvailable = false; @@ -1202,6 +1232,7 @@ void OctreeServer::domainSettingsRequestComplete() { packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); + packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); readConfiguration(); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 8db8d845de..5043ea681c 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -137,6 +137,7 @@ private slots: void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeFileReplacement(QSharedPointer message); + void handleOctreeFileReplacementFromURL(QSharedPointer message); void removeSendThread(); protected: @@ -161,6 +162,8 @@ protected: UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + void replaceContentFromMessageData(QByteArray content); + int _argc; const char** _argv; char** _parsedArgV; diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 74f7e10859..71d7b94597 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -2,57 +2,30 @@ set(EXTERNAL_NAME tbb) include(ExternalProject) -if (ANDROID) - - find_program(NDK_BUILD_COMMAND NAMES ndk-build DOC "Path to the ndk-build command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) +if (WIN32) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_win_slim.zip) + set(DOWNLOAD_MD5 065934458e3db88397f3d10e7eea536c) elseif (APPLE) - find_program(MAKE_COMMAND NAMES make DOC "Path to the make command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb2017_20170604oss_mac_slim.tar.gz) + set(DOWNLOAD_MD5 62bde626b396f8e1a85c6a8ded1d8105) +elseif (ANDROID) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_and_slim.tar.gz) + set(DOWNLOAD_MD5 04d50b64e1d81245a1be5f75f34d64c7) else () - if (WIN32) - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) - set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) - else () - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz) - set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5) - endif () - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL ${DOWNLOAD_URL} - URL_MD5 ${DOWNLOAD_MD5} - BUILD_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - ) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz) + set(DOWNLOAD_MD5 2a5c721f40fa3503ffc12c18dd00011c) endif () +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + BUILD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON +) + # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") @@ -70,22 +43,32 @@ if (APPLE) change-install-name COMMENT "Calling install_name_tool on TBB libraries to fix install name for dylib linking" COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_TBB_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install + DEPENDEES download WORKING_DIRECTORY LOG 1 ) - elseif (WIN32) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/vc12" CACHE PATH "Path to TBB DLLs") + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") else() - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/vc12" CACHE PATH "Path to TBB DLLs") + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") endif() + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + else() + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + endif() + set(_LIB_EXT "lib") elseif (ANDROID) - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/android") set(_LIB_PREFIX "lib") set(_LIB_EXT "so") elseif (UNIX) @@ -103,15 +86,15 @@ elseif (UNIX) OUTPUT_VARIABLE GCC_VERSION ) - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(STATUS "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - - endif () if (DEFINED _TBB_LIB_DIR) diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 6f35b76f1d..fea244873c 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -111,14 +111,14 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "Interface") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") set(CONSOLE_SHORTCUT_NAME "Sandbox") else () - set(INTERFACE_SHORTCUT_NAME "Interface - ${BUILD_VERSION}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}") set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}") endif () - set(INTERFACE_HF_SHORTCUT_NAME "High Fidelity ${INTERFACE_SHORTCUT_NAME}") + set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}") set(CONSOLE_HF_SHORTCUT_NAME "High Fidelity ${CONSOLE_SHORTCUT_NAME}") set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "High Fidelity") diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index 1ccdcd792d..12d4deec36 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -1,6 +1,6 @@ -# +# # FindTBB.cmake -# +# # Try to find the Intel Threading Building Blocks library # # You can provide a TBB_ROOT_DIR which contains lib and include directories @@ -16,7 +16,7 @@ # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("tbb") @@ -34,31 +34,43 @@ elseif (UNIX AND NOT ANDROID) else() set(_TBB_ARCH_DIR "ia32") endif() - + execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ) - - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(FATAL_ERROR "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - + elseif (WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_TBB_ARCH_DIR "intel64") else() set(_TBB_ARCH_DIR "ia32") endif() - - set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/vc12") - - find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/vc12" HINTS ${TBB_SEARCH_DIRS}) - + + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") + else() + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") + endif() + + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}") + + find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}" HINTS ${TBB_SEARCH_DIRS}) + elseif (ANDROID) set(_TBB_DEFAULT_INSTALL_DIR "/tbb") set(_TBB_LIB_NAME "tbb") diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c1eff76d78..8d0e949ff3 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.7, + "version": 1.8, "settings": [ { "name": "metaverse", @@ -112,7 +112,6 @@ "label": "Adult (18+)" } ] - }, { "name": "hosts", @@ -161,15 +160,15 @@ "label": "HTTP Password", "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", - "password_placeholder" : "******", + "password_placeholder": "******", "value-hidden": true }, { - "name": "verify_http_password", - "label": "Verify HTTP Password", - "type": "password", - "help": "Must match the password entered above for change to be saved.", - "value-hidden": true + "name": "verify_http_password", + "label": "Verify HTTP Password", + "type": "password", + "help": "Must match the password entered above for change to be saved.", + "value-hidden": true }, { "name": "maximum_user_capacity", @@ -208,21 +207,19 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which types of users can have which domain-wide permissions.", + "help": "Indicate which types of users can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, - "groups": [ { "label": "Type of User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -276,9 +273,15 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ], - "non-deletable-row-key": "permissions_id", "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] }, @@ -291,18 +294,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -381,6 +382,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -393,18 +401,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Blacklist Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -480,6 +486,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -488,18 +501,16 @@ "type": "table", "caption": "Permissions for Specific Users", "can_add_new_rows": true, - "groups": [ { "label": "User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -553,6 +564,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -567,11 +585,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -625,6 +642,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -639,11 +663,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -697,6 +720,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -711,11 +741,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -769,6 +798,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] } @@ -784,7 +820,6 @@ "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", "can_add_new_rows": true, - "columns": [ { "name": "url", @@ -946,7 +981,6 @@ "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, "can_add_new_rows": true, - "key": { "name": "name", "label": "Name", @@ -999,7 +1033,6 @@ "numbered": true, "can_order": true, "can_add_new_rows": true, - "columns": [ { "name": "source", @@ -1028,7 +1061,6 @@ "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "can_add_new_rows": true, - "columns": [ { "name": "zone", @@ -1063,7 +1095,9 @@ { "name": "audio_buffer", "label": "Audio Buffers", - "assignment-types": [0], + "assignment-types": [ + 0 + ], "settings": [ { "name": "dynamic_jitter_buffer", @@ -1082,35 +1116,37 @@ "advanced": true }, { - "name": "max_frames_over_desired", - "deprecated": true + "name": "max_frames_over_desired", + "deprecated": true }, { - "name": "window_starve_threshold", - "deprecated": true + "name": "window_starve_threshold", + "deprecated": true }, { - "name": "window_seconds_for_desired_calc_on_too_many_starves", - "deprecated": true + "name": "window_seconds_for_desired_calc_on_too_many_starves", + "deprecated": true }, { - "name": "window_seconds_for_desired_reduction", - "deprecated": true + "name": "window_seconds_for_desired_reduction", + "deprecated": true }, { - "name": "use_stdev_for_desired_calc", - "deprecated": true + "name": "use_stdev_for_desired_calc", + "deprecated": true }, { - "name": "repetition_with_fade", - "deprecated": true + "name": "repetition_with_fade", + "deprecated": true } ] }, { "name": "entity_server_settings", "label": "Entity Server Settings", - "assignment-types": [6], + "assignment-types": [ + 6 + ], "settings": [ { "name": "maxTmpLifetime", @@ -1167,13 +1203,32 @@ "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, "can_add_new_rows": true, - - "default": [ - {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, - {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, - {"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4}, - {"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12} - ], + "default": [ + { + "Name": "Half Hourly Rolling", + "backupInterval": 1800, + "format": ".backup.halfhourly.%N", + "maxBackupVersions": 5 + }, + { + "Name": "Daily Rolling", + "backupInterval": 86400, + "format": ".backup.daily.%N", + "maxBackupVersions": 7 + }, + { + "Name": "Weekly Rolling", + "backupInterval": 604800, + "format": ".backup.weekly.%N", + "maxBackupVersions": 4 + }, + { + "Name": "Thirty Day Rolling", + "backupInterval": 2592000, + "format": ".backup.thirtyday.%N", + "maxBackupVersions": 12 + } + ], "columns": [ { "name": "Name", @@ -1309,7 +1364,9 @@ { "name": "avatar_mixer", "label": "Avatar Mixer", - "assignment-types": [1], + "assignment-types": [ + 1 + ], "settings": [ { "name": "max_node_send_bandwidth", @@ -1362,7 +1419,10 @@ { "name": "downstream_servers", "label": "Receiving Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1402,7 +1462,10 @@ { "name": "upstream_servers", "label": "Broadcasting Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1442,4 +1505,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index fc595be67e..6951a90261 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -269,6 +269,7 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; } else { // this node is an agent const QHostAddress& addr = node->getLocalSocket().getAddress(); @@ -357,6 +358,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; newNode->setPermissions(userPerms); return newNode; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7a2cfa645a..d93126f2c7 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -112,6 +112,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; + const QString EDITORS_CAN_REPLACE_CONTENT_KEYPATH = "security.editors_can_replace_content"; qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" @@ -294,6 +295,13 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // persist the new config so the user config file has the correctly merged config persistToFile(); } + + if (oldVersion < 1.8) { + unpackPermissions(); + // This was prior to addition of domain content replacement, add that to localhost permissions by default + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent); + packPermissions(); + } } unpackPermissions(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 81c8a44baf..4ed95b59f7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -183,6 +183,12 @@ if (WIN32) add_dependency_external_projects(steamworks) endif() +# include OPENSSL +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index f377e02e5f..54d2467f78 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -131,6 +131,6 @@ { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" }, - { "from": "Keyboard.RightMouseClicked", "to": "Actions.ContextMenu" } + { "from": "Keyboard.Tab", "to": "Actions.ContextMenu" } ] } diff --git a/interface/resources/html/img/tablet-help-gamepad.jpg b/interface/resources/html/img/tablet-help-gamepad.jpg index 5abb38d66b..3f084ecc4d 100644 Binary files a/interface/resources/html/img/tablet-help-gamepad.jpg and b/interface/resources/html/img/tablet-help-gamepad.jpg differ diff --git a/interface/resources/html/img/tablet-help-keyboard.jpg b/interface/resources/html/img/tablet-help-keyboard.jpg index 40c6017561..80f19fd372 100644 Binary files a/interface/resources/html/img/tablet-help-keyboard.jpg and b/interface/resources/html/img/tablet-help-keyboard.jpg differ diff --git a/interface/resources/html/img/tablet-help-oculus.jpg b/interface/resources/html/img/tablet-help-oculus.jpg index b4f54396e0..1f493039d3 100644 Binary files a/interface/resources/html/img/tablet-help-oculus.jpg and b/interface/resources/html/img/tablet-help-oculus.jpg differ diff --git a/interface/resources/html/img/tablet-help-vive.jpg b/interface/resources/html/img/tablet-help-vive.jpg index 98e57eef47..2f87daf835 100644 Binary files a/interface/resources/html/img/tablet-help-vive.jpg and b/interface/resources/html/img/tablet-help-vive.jpg differ diff --git a/interface/resources/images/fadeMask.png b/interface/resources/images/fadeMask.png new file mode 100644 index 0000000000..9c342ba788 Binary files /dev/null and b/interface/resources/images/fadeMask.png differ diff --git a/interface/resources/qml/Web3DOverlay.qml b/interface/resources/qml/Web3DOverlay.qml index e0689e614d..a1fa2d2641 100644 --- a/interface/resources/qml/Web3DOverlay.qml +++ b/interface/resources/qml/Web3DOverlay.qml @@ -15,6 +15,11 @@ import "controls" as Controls Controls.WebView { + // This is for JS/QML communication, which is unused in a Web3DOverlay, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + function onWebEventReceived(event) { if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 8bce092947..53f66fa67c 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -27,6 +27,7 @@ TreeView { model: treeModel selection: ItemSelectionModel { + id: selectionModel model: treeModel } @@ -215,6 +216,10 @@ TreeView { onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) + onClicked: { + selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect); + } + onActivated: { var path = scriptsModel.data(index, 0x100) if (path) { diff --git a/interface/resources/qml/controls-uit/WebGlyphButton.qml b/interface/resources/qml/controls-uit/WebGlyphButton.qml new file mode 100644 index 0000000000..15524e4188 --- /dev/null +++ b/interface/resources/qml/controls-uit/WebGlyphButton.qml @@ -0,0 +1,48 @@ +// +// GlyphButton.qml +// +// Created by Vlad Stelmahovsky on 2017-06-21 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" + +Original.Button { + id: control + + property int colorScheme: hifi.colorSchemes.light + property string glyph: "" + property int size: 32 + //colors + readonly property color normalColor: "#AFAFAF" + readonly property color hoverColor: "#00B4EF" + readonly property color clickedColor: "#FFFFFF" + readonly property color disabledColor: "#575757" + + style: ButtonStyle { + background: Item {} + + + label: HiFiGlyphs { + color: control.enabled ? (control.pressed ? control.clickedColor : + (control.hovered ? control.hoverColor : control.normalColor)) : + control.disabledColor + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors { + // Tweak horizontal alignment so that it looks right. + left: parent.left + leftMargin: -0.5 + } + text: control.glyph + size: control.size + } + } +} diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 3dcf747113..ca7226a6ab 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -41,6 +41,11 @@ FocusScope { // when they're opened. signal showDesktop(); + // This is for JS/QML communication, which is unused in the Desktop, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + // Allows QML/JS to find the desktop through the parent chain property bool desktopRoot: true diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml new file mode 100644 index 0000000000..bafa518eb9 --- /dev/null +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -0,0 +1,158 @@ +// +// LetterboxMessage.qml +// qml/hifi +// +// Created by Dante Ruiz on 7/21/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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../styles-uit" + +Item { + property alias text: popupText.text + property alias headerGlyph: headerGlyph.text + property alias headerText: headerText.text + property alias headerGlyphSize: headerGlyph.size + property real popupRadius: hifi.dimensions.borderRadius + property real headerTextPixelSize: 22 + property real popupTextPixelSize: 16 + property real headerTextMargin: -5 + property real headerGlyphMargin: -15 + property bool isDesktop: false + FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; } + FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; } + visible: false + id: letterbox + anchors.fill: parent + Rectangle { + id: textContainer; + width: parent.width + height: parent.height + anchors.centerIn: parent + radius: popupRadius + color: "white" + Item { + id: contentContainer + width: parent.width - 50 + height: childrenRect.height + anchors.centerIn: parent + Item { + id: popupHeaderContainer + visible: headerText.text !== "" || headerGlyph.text !== "" + height: 30 + // Anchors + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + // Header Glyph + HiFiGlyphs { + id: headerGlyph + visible: headerGlyph.text !== "" + // Size + height: parent.height + // Anchors + anchors.left: parent.left + anchors.leftMargin: headerGlyphMargin + // Text Size + size: headerTextPixelSize*2.5 + // Style + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + color: hifi.colors.darkGray + } + // Header Text + Text { + id: headerText + visible: headerText.text !== "" + // Size + + height: parent.height + // Anchors + anchors.left: headerGlyph.right + anchors.leftMargin: headerTextMargin + // Text Size + font.pixelSize: headerTextPixelSize + // Style + font.family: ralewaySemiBold.name + color: hifi.colors.darkGray + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + textFormat: Text.StyledText + } + } + // Popup Text + Text { + id: popupText + // Size + width: parent.width + // Anchors + anchors.top: popupHeaderContainer.visible ? popupHeaderContainer.bottom : parent.top + anchors.topMargin: popupHeaderContainer.visible ? 15 : 0 + anchors.left: parent.left + anchors.right: parent.right + // Text alignment + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHLeft + // Style + font.pixelSize: popupTextPixelSize + font.family: ralewayRegular.name + color: hifi.colors.darkGray + wrapMode: Text.WordWrap + textFormat: Text.StyledText + onLinkActivated: { + Qt.openUrlExternally(link) + } + } + } + } + // Left gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: textContainer.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Right gray MouseArea + MouseArea { + anchors.left: textContainer.left; + anchors.right: parent.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Top gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.bottom: textContainer.top; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } + // Bottom gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: textContainer.bottom; + anchors.bottom: parent.bottom; + acceptedButtons: Qt.LeftButton + onClicked: { + letterbox.visible = false + } + } +} diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index fa9d7aa6f0..6f41154d4d 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -17,9 +17,12 @@ Item { property alias text: popupText.text property alias headerGlyph: headerGlyph.text property alias headerText: headerText.text + property alias headerGlyphSize: headerGlyph.size property real popupRadius: hifi.dimensions.borderRadius property real headerTextPixelSize: 22 property real popupTextPixelSize: 16 + property real headerTextMargin: -5 + property real headerGlyphMargin: -15 FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; } FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; } visible: false @@ -59,7 +62,7 @@ Item { height: parent.height // Anchors anchors.left: parent.left - anchors.leftMargin: -15 + anchors.leftMargin: headerGlyphMargin // Text Size size: headerTextPixelSize*2.5 // Style @@ -75,7 +78,7 @@ Item { height: parent.height // Anchors anchors.left: headerGlyph.right - anchors.leftMargin: -5 + anchors.leftMargin: headerTextMargin // Text Size font.pixelSize: headerTextPixelSize // Style diff --git a/interface/resources/qml/hifi/SkyboxChanger.qml b/interface/resources/qml/hifi/SkyboxChanger.qml new file mode 100644 index 0000000000..a4798ba959 --- /dev/null +++ b/interface/resources/qml/hifi/SkyboxChanger.qml @@ -0,0 +1,173 @@ +// +// skyboxchanger.qml +// +// +// Created by Cain Kilgore on 9th August 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 +// + +import QtQuick.Layouts 1.3 + +Rectangle { + id: root; + + color: hifi.colors.baseGray; + + Item { + id: titleBarContainer; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + RalewaySemiBold { + id: titleBarText; + text: "Skybox Changer"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + RalewaySemiBold { + id: titleBarDesc; + text: "Click an image to choose a new Skybox."; + wrapMode: Text.Wrap + // Text size + size: 14; + // Anchors + anchors.fill: parent; + anchors.top: titleBarText.bottom + anchors.leftMargin: 16; + anchors.rightMargin: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + + // This RowLayout could be a GridLayout instead for further expandability. + // As this SkyboxChanger task only required 6 images, implementing GridLayout wasn't necessary. + // In the future if this is to be expanded to add more Skyboxes, it might be worth changing this. + RowLayout { + id: row1 + anchors.top: titleBarContainer.bottom + anchors.left: parent.left + anchors.leftMargin: 30 + Layout.fillWidth: true + anchors.topMargin: 30 + spacing: 10 + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_1.jpg" + clip: true + id: preview1 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/1.jpg'}); + } + } + Layout.fillWidth: true + } + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_2.jpg" + clip: true + id: preview2 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/2.png'}); + } + } + } + } + RowLayout { + id: row2 + anchors.top: row1.bottom + anchors.topMargin: 10 + anchors.left: parent.left + Layout.fillWidth: true + anchors.leftMargin: 30 + spacing: 10 + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_3.jpg" + clip: true + id: preview3 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/3.jpg'}); + } + } + } + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_4.jpg" + clip: true + id: preview4 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/4.jpg'}); + } + } + } + } + RowLayout { + id: row3 + anchors.top: row2.bottom + anchors.topMargin: 10 + anchors.left: parent.left + Layout.fillWidth: true + anchors.leftMargin: 30 + spacing: 10 + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_5.jpg" + clip: true + id: preview5 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/5.png'}); + } + } + } + Image { + width: 200; height: 200 + fillMode: Image.Stretch + source: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/thumbnails/thumb_6.jpg" + clip: true + id: preview6 + MouseArea { + anchors.fill: parent + onClicked: { + sendToScript({method: 'changeSkybox', url: 'http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxes/6.jpg'}); + } + } + } + } + + signal sendToScript(var message); + +} diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml new file mode 100644 index 0000000000..f639586668 --- /dev/null +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -0,0 +1,253 @@ +// +// WebBrowser.qml +// +// +// Created by Vlad Stelmahovsky on 06/22/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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.5 as QQControls +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 + +import QtWebEngine 1.2 +import QtWebChannel 1.0 + +import "../styles-uit" +import "../controls-uit" as HifiControls +import "../windows" +import "../controls" + +Rectangle { + id: root; + + HifiConstants { id: hifi; } + + property string title: ""; + signal sendToScript(var message); + property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false + property bool keyboardRaised: false + property bool punctuationMode: false + + + color: hifi.colors.baseGray; + + // only show the title if loaded through a "loader" + + Column { + spacing: 2 + width: parent.width; + + RowLayout { + width: parent.width; + height: 48 + + HifiControls.WebGlyphButton { + enabled: webEngineView.canGoBack + glyph: hifi.glyphs.backward; + anchors.verticalCenter: parent.verticalCenter; + size: 38; + onClicked: { + webEngineView.goBack() + } + } + + HifiControls.WebGlyphButton { + enabled: webEngineView.canGoForward + glyph: hifi.glyphs.forward; + anchors.verticalCenter: parent.verticalCenter; + size: 38; + onClicked: { + webEngineView.goForward() + } + } + + QQControls.TextField { + id: addressBar + + Image { + anchors.verticalCenter: addressBar.verticalCenter; + x: 5 + z: 2 + id: faviconImage + width: 16; height: 16 + sourceSize: Qt.size(width, height) + source: webEngineView.icon + } + + HifiControls.WebGlyphButton { + glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; + anchors.verticalCenter: parent.verticalCenter; + width: hifi.dimensions.controlLineHeight + z: 2 + x: addressBar.width - 28 + onClicked: { + if (webEngineView.loading) { + webEngineView.stop() + } else { + reloadTimer.start() + } + } + } + + style: TextFieldStyle { + padding { + left: 26; + right: 26 + } + } + focus: true + Layout.fillWidth: true + text: webEngineView.url + onAccepted: webEngineView.url = text + } + HifiControls.WebGlyphButton { + checkable: true + //only QtWebEngine 1.3 + //checked: webEngineView.audioMuted + glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted + anchors.verticalCenter: parent.verticalCenter; + width: hifi.dimensions.controlLineHeight + onClicked: { + webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute) + } + } + } + + QQControls.ProgressBar { + id: loadProgressBar + style: ProgressBarStyle { + background: Rectangle { + color: "#6A6A6A" + } + progress: Rectangle{ + color: "#00B4EF" + } + } + + width: parent.width; + minimumValue: 0 + maximumValue: 100 + value: webEngineView.loadProgress + height: 2 + } + + HifiControls.BaseWebView { + id: webEngineView + focus: true + objectName: "tabletWebEngineView" + + url: "http://www.highfidelity.com" + property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 + + width: parent.width; + height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight + + profile: HFTabletWebEngineProfile; + + property string userScriptUrl: "" + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webEngineView.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + settings.autoLoadImages: true + settings.javascriptEnabled: true + settings.errorPageEnabled: true + settings.pluginsEnabled: true + settings.fullScreenSupportEnabled: false + //from WebEngine 1.3 + // settings.autoLoadIconsForPage: false + // settings.touchIconsEnabled: false + + onCertificateError: { + error.defer(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onNewViewRequested: { + if (!request.userInitiated) { + print("Warning: Blocked a popup window."); + } + } + + onRenderProcessTerminated: { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + + print("Render process exited with code " + exitCode + " " + status); + reloadTimer.running = true; + } + + onWindowCloseRequested: { + } + + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() + } + } + } + + HifiControls.Keyboard { + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } +} diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml new file mode 100644 index 0000000000..b9d15b61e4 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -0,0 +1,482 @@ +// +// Checkout.qml +// qml/hifi/commerce +// +// Checkout +// +// Created by Zach Fox on 2017-08-07 +// 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: checkoutRoot; + property bool inventoryReceived: false; + property bool balanceReceived: false; + property string itemId: ""; + property string itemHref: ""; + property int balanceAfterPurchase: 0; + property bool alreadyOwned: false; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + onBuyResult: { + if (failureMessage.length) { + buyButton.text = "Buy Failed"; + buyButton.enabled = false; + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + } + } + onBalanceResult: { + if (failureMessage.length) { + console.log("Failed to get balance", failureMessage); + } else { + balanceReceived = true; + hfcBalanceText.text = balance; + balanceAfterPurchase = balance - parseInt(itemPriceText.text, 10); + } + } + onInventoryResult: { + if (failureMessage.length) { + console.log("Failed to get inventory", failureMessage); + } else { + inventoryReceived = true; + if (inventoryContains(inventory.assets, itemId)) { + alreadyOwned = true; + } else { + alreadyOwned = false; + } + } + } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: checkoutRoot.itemHref; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: checkoutRoot.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Checkout"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: securityImage.right; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // ITEM DESCRIPTION START + // + Item { + id: itemDescriptionContainer; + // Size + width: checkoutRoot.width; + height: childrenRect.height + 20; + // Anchors + anchors.left: parent.left; + anchors.top: titleBarContainer.bottom; + + // Item Name text + Item { + id: itemNameContainer; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemNameTextLabel; + text: "Item Name:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemNameText; + // Text size + size: itemNameTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemNameTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + + // Item Author text + Item { + id: itemAuthorContainer; + // Anchors + anchors.top: itemNameContainer.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemAuthorTextLabel; + text: "Item Author:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemAuthorText; + // Text size + size: itemAuthorTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemAuthorTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // HFC Balance text + Item { + id: hfcBalanceContainer; + // Anchors + anchors.top: itemAuthorContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: hfcBalanceTextLabel; + text: "HFC Balance:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: hfcBalanceText; + text: "--"; + // Text size + size: hfcBalanceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: hfcBalanceTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Item Price text + Item { + id: itemPriceContainer; + // Anchors + anchors.top: hfcBalanceContainer.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemPriceTextLabel; + text: "Item Price:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemPriceText; + // Text size + size: itemPriceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemPriceTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // HFC "Balance After Purchase" text + Item { + id: hfcBalanceAfterPurchaseContainer; + // Anchors + anchors.top: itemPriceContainer.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: hfcBalanceAfterPurchaseTextLabel; + text: "HFC Balance After Purchase:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: hfcBalanceAfterPurchaseText; + text: balanceAfterPurchase; + // Text size + size: hfcBalanceAfterPurchaseTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: hfcBalanceAfterPurchaseTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + } + // + // ITEM DESCRIPTION END + // + + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + // Size + width: checkoutRoot.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } + } + + // "Buy" button + HifiControlsUit.Button { + id: buyButton; + enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; + onClicked: { + if (!alreadyOwned) { + commerce.buy(itemId, parseInt(itemPriceText.text)); + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + } + } + } + } + // + // ACTION BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript, in this case the Marketplaces JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + case 'updateCheckoutQML': + itemId = message.params.itemId; + itemNameText.text = message.params.itemName; + itemAuthorText.text = message.params.itemAuthor; + itemPriceText.text = message.params.itemPrice; + itemHref = message.params.itemHref; + commerce.balance(); + commerce.inventory(); + commerce.getSecurityImage(); + break; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + function inventoryContains(inventoryJson, id) { + for (var idx = 0; idx < inventoryJson.length; idx++) { + if(inventoryJson[idx].id === id) { + return true; + } + } + return false; + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml new file mode 100644 index 0000000000..d7ffae7c3c --- /dev/null +++ b/interface/resources/qml/hifi/commerce/Inventory.qml @@ -0,0 +1,321 @@ +// +// Inventory.qml +// qml/hifi/commerce +// +// Inventory +// +// Created by Zach Fox on 2017-08-10 +// 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: inventoryRoot; + property string referrerURL: ""; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + onBalanceResult: { + if (failureMessage.length) { + console.log("Failed to get balance", failureMessage); + } else { + hfcBalanceText.text = balance; + } + } + onInventoryResult: { + if (failureMessage.length) { + console.log("Failed to get inventory", failureMessage); + } else { + inventoryContentsList.model = inventory.assets; + } + } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: inventoryRoot.referrerURL; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Inventory"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: securityImage.right; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // "Change Security Image" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 200; + text: "Change Security Image" + onClicked: { + securityImageSelection.isManuallyChangingSecurityImage = true; + securityImageSelection.visible = true; + } + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // HFC BALANCE START + // + Item { + id: hfcBalanceContainer; + // Size + width: inventoryRoot.width; + height: childrenRect.height + 20; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 4; + + RalewaySemiBold { + id: hfcBalanceTextLabel; + text: "HFC Balance:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: hfcBalanceText; + text: "--"; + // Text size + size: hfcBalanceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: hfcBalanceTextLabel.right; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + // + // HFC BALANCE END + // + + // + // INVENTORY CONTENTS START + // + Item { + id: inventoryContentsContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.top: hfcBalanceContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + RalewaySemiBold { + id: inventoryContentsLabel; + text: "Inventory:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Text size + size: 24; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + ListView { + id: inventoryContentsList; + clip: true; + // Anchors + anchors.top: inventoryContentsLabel.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width; + delegate: Item { + width: parent.width; + height: 30; + RalewayRegular { + id: thisItemId; + // Text size + size: 20; + // Style + color: hifi.colors.blueAccent; + text: modelData.title; + // Alignment + horizontalAlignment: Text.AlignHLeft; + } + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + sendToScript({method: 'inventory_itemClicked', itemId: modelData.id}); + } + onEntered: { + thisItemId.color = hifi.colors.blueHighlight; + } + onExited: { + thisItemId.color = hifi.colors.blueAccent; + } + } + } + } + } + // + // INVENTORY CONTENTS END + // + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + // Size + width: inventoryRoot.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Back" button + HifiControlsUit.Button { + id: backButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Back" + onClicked: { + sendToScript({method: 'inventory_backClicked', referrerURL: referrerURL}); + } + } + } + // + // ACTION BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript, in this case the Marketplaces JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + case 'updateInventory': + referrerURL = message.referrerURL; + commerce.balance(); + commerce.inventory(); + commerce.getSecurityImage(); + break; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml new file mode 100644 index 0000000000..2fbf28683f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml @@ -0,0 +1,42 @@ +// +// SecurityImageModel.qml +// qml/hifi/commerce +// +// SecurityImageModel +// +// Created by Zach Fox on 2017-08-15 +// 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 +// + +import QtQuick 2.5 + +ListModel { + id: root; + ListElement{ + sourcePath: "images/01cat.jpg" + securityImageEnumValue: 1; + } + ListElement{ + sourcePath: "images/02car.jpg" + securityImageEnumValue: 2; + } + ListElement{ + sourcePath: "images/03dog.jpg" + securityImageEnumValue: 3; + } + ListElement{ + sourcePath: "images/04stars.jpg" + securityImageEnumValue: 4; + } + ListElement{ + sourcePath: "images/05plane.jpg" + securityImageEnumValue: 5; + } + ListElement{ + sourcePath: "images/06gingerbread.jpg" + securityImageEnumValue: 6; + } +} diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml new file mode 100644 index 0000000000..7775f1ff9c --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml @@ -0,0 +1,271 @@ +// +// SecurityImageSelection.qml +// qml/hifi/commerce +// +// SecurityImageSelection +// +// Created by Zach Fox on 2017-08-15 +// 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: securityImageSelectionRoot; + property string referrerURL: ""; + property bool isManuallyChangingSecurityImage: false; + anchors.fill: parent; + // Style + color: hifi.colors.baseGray; + z:999; // On top of everything else + visible: false; + + Hifi.QmlCommerce { + id: commerce; + onSecurityImageResult: { + if (!isManuallyChangingSecurityImage) { + securityImageSelectionRoot.visible = (imageID == 0); + } + if (imageID > 0) { + for (var itr = 0; itr < gridModel.count; itr++) { + var thisValue = gridModel.get(itr).securityImageEnumValue; + if (thisValue === imageID) { + securityImageGrid.currentIndex = itr; + break; + } + } + } + } + } + + Component.onCompleted: { + commerce.getSecurityImage(); + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: securityImageSelectionRoot.width; + height: 30; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Select a Security Image"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // EXPLANATION START + // + Item { + id: explanationContainer; + // Size + width: securityImageSelectionRoot.width; + height: 85; + // Anchors + anchors.top: titleBarContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: explanationText; + text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!
"; + // Text size + size: 16; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.lightGrayText; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // EXPLANATION END + // + + // + // SECURITY IMAGE GRID START + // + Item { + id: securityImageGridContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + anchors.top: explanationContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + SecurityImageModel { + id: gridModel; + } + + GridView { + id: securityImageGrid; + clip: true; + // Anchors + anchors.fill: parent; + currentIndex: -1; + cellWidth: width / 2; + cellHeight: height / 3; + model: gridModel; + delegate: Item { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + Item { + anchors.fill: parent; + Image { + width: parent.width - 8; + height: parent.height - 8; + source: sourcePath; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + } + MouseArea { + anchors.fill: parent; + onClicked: { + securityImageGrid.currentIndex = index; + } + } + } + highlight: Rectangle { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } + } + } + // + // SECURITY IMAGE GRID END + // + + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + // Size + width: securityImageSelectionRoot.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) { + sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL}); + } else { + securityImageSelectionRoot.visible = false; + } + } + } + + // "Confirm" button + HifiControlsUit.Button { + id: confirmButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: "Confirm"; + onClicked: { + securityImageSelectionRoot.isManuallyChangingSecurityImage = false; + commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue); + } + } + } + // + // ACTION BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + signal sendToScript(var message); + + function getImagePathFromImageID(imageID) { + return (imageID ? gridModel.get(imageID - 1).sourcePath : ""); + } + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/images/01cat.jpg new file mode 100644 index 0000000000..6e7897cb82 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/01cat.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg new file mode 100644 index 0000000000..5dd8091e57 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/02car.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/images/03dog.jpg new file mode 100644 index 0000000000..4a85b80c0c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/03dog.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg new file mode 100644 index 0000000000..8f2bf62f83 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/04stars.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg new file mode 100644 index 0000000000..6504459d8b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/05plane.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg new file mode 100644 index 0000000000..54c37faa2f Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg differ diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 12e8de3bfc..76484cf8c7 100644 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -28,6 +28,11 @@ ScrollingWindow { HifiConstants { id: hifi } + // This is for JS/QML communication, which is unused in the AttachmentsDialog, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + property var settings: Settings { category: "AttachmentsDialog" property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index da9ffdb07e..952cc03733 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -16,6 +16,7 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" +import "../" ScrollingWindow { id: root @@ -28,10 +29,11 @@ ScrollingWindow { minSize: Qt.vector2d(424, 300) HifiConstants { id: hifi } - + property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } + property bool developerMenuEnabled: false property bool isHMD: false Settings { @@ -39,6 +41,28 @@ ScrollingWindow { property alias x: root.x property alias y: root.y } + + Component { + id: letterBoxMessage + Window { + implicitWidth: 400 + implicitHeight: 300 + minSize: Qt.vector2d(424, 300) + DesktopLetterboxMessage { + visible: true + headerGlyph: hifi.glyphs.lock + headerText: "Developer Mode only" + text: ( "In order to edit, delete or reload this script," + + " turn on Developer Mode by going to:" + + " Menu > Settings > Developer Menus") + popupRadius: 0 + headerGlyphSize: 20 + headerTextMargin: 2 + headerGlyphMargin: -3 + } + } + } + Timer { id: refreshTimer @@ -47,6 +71,15 @@ ScrollingWindow { running: false onTriggered: updateRunningScripts(); } + + + Timer { + id: checkMenu + interval: 1000 + repeat: true + running: false + onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + } Component { id: listModelBuilder @@ -64,6 +97,8 @@ ScrollingWindow { Component.onCompleted: { isHMD = HMD.active; updateRunningScripts(); + developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + checkMenu.restart(); } function updateRunningScripts() { @@ -110,7 +145,17 @@ ScrollingWindow { function reloadAll() { console.log("Reload all scripts"); - scripts.reloadAllScripts(); + if (!developerMenuEnabled) { + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url, true); + } + } + } else { + scripts.reloadAllScripts(); + } } function loadDefaults() { @@ -120,7 +165,22 @@ ScrollingWindow { function stopAll() { console.log("Stop all scripts"); - scripts.stopAllScripts(); + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url); + } + } + } + + + function canEditScript(script) { + if ((script === "controllerScripts.js") || (script === "defaultScripts.js")) { + return developerMenuEnabled; + } + + return true; } Column { @@ -146,6 +206,14 @@ ScrollingWindow { color: hifi.buttons.red onClicked: stopAll() } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + visible: root.developerMenuEnabled; + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer { @@ -162,6 +230,7 @@ ScrollingWindow { expandSelectedRow: true itemDelegate: Item { + property bool canEdit: canEditScript(styleData.value); anchors { left: parent ? parent.left : undefined leftMargin: hifi.dimensions.tablePadding @@ -185,8 +254,9 @@ ScrollingWindow { HiFiGlyphs { id: reloadButton - text: hifi.glyphs.reloadSmall + text: ((canEditScript(styleData.value)) ? hifi.glyphs.reload : hifi.glyphs.lock) color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + size: 21 anchors { top: parent.top right: stopButton.left @@ -195,7 +265,13 @@ ScrollingWindow { MouseArea { id: reloadButtonArea anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) + onClicked: { + if (canEdit) { + reloadScript(model.url) + } else { + letterBoxMessage.createObject(desktop) + } + } } } @@ -203,6 +279,7 @@ ScrollingWindow { id: stopButton text: hifi.glyphs.closeSmall color: stopButtonArea.pressed ? hifi.colors.white : parent.color + visible: canEditScript(styleData.value) anchors { top: parent.top right: parent.right @@ -211,7 +288,11 @@ ScrollingWindow { MouseArea { id: stopButtonArea anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) + onClicked: { + if (canEdit) { + stopScript(model.url); + } + } } } @@ -264,13 +345,6 @@ ScrollingWindow { height: 26 onClickedQueued: ApplicationInterface.loadDialog() } - - HifiControls.Button { - text: "Load Defaults" - color: hifi.buttons.black - height: 26 - onClicked: loadDefaults() - } } HifiControls.VerticalSpacer {} diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 11643ae1f1..5677cc92a1 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -16,6 +16,7 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" +import "../" Rectangle { id: root @@ -26,26 +27,90 @@ Rectangle { property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } + property bool developerMenuEnabled: false property bool isHMD: false color: hifi.colors.baseGray + + LetterboxMessage { + id: letterBoxMessage + z: 999 + visible: false + } + + function letterBox(glyph, text, message) { + letterBoxMessage.headerGlyph = glyph; + letterBoxMessage.headerText = text; + letterBoxMessage.text = message; + letterBoxMessage.visible = true; + letterBoxMessage.popupRadius = 0; + letterBoxMessage.headerGlyphSize = 20 + letterBoxMessage.headerTextMargin = 2 + letterBoxMessage.headerGlyphMargin = -3 + } + + Timer { + id: refreshTimer + interval: 100 + repeat: false + running: false + onTriggered: updateRunningScripts(); + } + + Timer { + id: checkMenu + interval: 1000 + repeat: true + running: false + onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + } + + Component { + id: listModelBuilder + ListModel {} + } + Connections { target: ScriptDiscoveryService - onScriptCountChanged: updateRunningScripts(); + onScriptCountChanged: { + runningScriptsModel = listModelBuilder.createObject(root); + refreshTimer.restart(); + } } Component.onCompleted: { isHMD = HMD.active; updateRunningScripts(); + developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus"); + checkMenu.restart(); } function updateRunningScripts() { - var runningScripts = ScriptDiscoveryService.getRunning(); - runningScriptsModel.clear() - for (var i = 0; i < runningScripts.length; ++i) { - runningScriptsModel.append(runningScripts[i]); + function simplify(path) { + // trim URI querystring/fragment + path = (path+'').replace(/[#?].*$/,''); + // normalize separators and grab last path segment (ie: just the filename) + path = path.replace(/\\/g, '/').split('/').pop(); + // return lowercased because we want to sort mnemonically + return path.toLowerCase(); } + var runningScripts = ScriptDiscoveryService.getRunning(); + runningScripts.sort(function(a,b) { + a = simplify(a.path); + b = simplify(b.path); + return a < b ? -1 : a > b ? 1 : 0; + }); + // Calling `runningScriptsModel.clear()` here instead of creating a new object + // triggers some kind of weird heap corruption deep inside Qt. So instead of + // modifying the model in place, possibly triggering behaviors in the table + // instead we create a new `ListModel`, populate it and update the + // existing model atomically. + var newRunningScriptsModel = listModelBuilder.createObject(root); + for (var i = 0; i < runningScripts.length; ++i) { + newRunningScriptsModel.append(runningScripts[i]); + } + runningScriptsModel = newRunningScriptsModel; } function loadScript(script) { @@ -65,7 +130,17 @@ Rectangle { function reloadAll() { console.log("Reload all scripts"); - scripts.reloadAllScripts(); + if (!developerMenuEnabled) { + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url, true); + } + } + } else { + scripts.reloadAllScripts(); + } } function loadDefaults() { @@ -75,7 +150,22 @@ Rectangle { function stopAll() { console.log("Stop all scripts"); - scripts.stopAllScripts(); + for (var index = 0; index < runningScriptsModel.count; index++) { + var url = runningScriptsModel.get(index).url; + console.log(url); + var fileName = url.substring(url.lastIndexOf('/')+1); + if (canEditScript(fileName)) { + scripts.stopScript(url); + } + } + } + + function canEditScript(script) { + if ((script === "controllerScripts.js") || (script === "defaultScripts.js")) { + return developerMenuEnabled; + } + + return true; } Flickable { @@ -110,6 +200,14 @@ Rectangle { color: hifi.buttons.red onClicked: stopAll() } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + visible: root.developerMenuEnabled; + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer { @@ -125,6 +223,7 @@ Rectangle { expandSelectedRow: true itemDelegate: Item { + property bool canEdit: canEditScript(styleData.value); anchors { left: parent ? parent.left : undefined leftMargin: hifi.dimensions.tablePadding @@ -148,8 +247,9 @@ Rectangle { HiFiGlyphs { id: reloadButton - text: hifi.glyphs.reloadSmall + text: ((canEditScript(styleData.value)) ? hifi.glyphs.reload : hifi.glyphs.lock) color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + size: 21 anchors { top: parent.top right: stopButton.left @@ -158,7 +258,17 @@ Rectangle { MouseArea { id: reloadButtonArea anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) + onClicked: { + if (canEdit) { + reloadScript(model.url) + } else { + letterBox(hifi.glyphs.lock, + "Developer Mode only", + "In order to edit, delete or reload this script," + + " turn on Developer Mode by going to:" + + " Menu > Settings > Developer Menus"); + } + } } } @@ -166,6 +276,7 @@ Rectangle { id: stopButton text: hifi.glyphs.closeSmall color: stopButtonArea.pressed ? hifi.colors.white : parent.color + visible: canEditScript(styleData.value) anchors { top: parent.top right: parent.right @@ -174,7 +285,11 @@ Rectangle { MouseArea { id: stopButtonArea anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) + onClicked: { + if (canEdit) { + stopScript(model.url) + } + } } } @@ -250,13 +365,6 @@ Rectangle { onTriggered: ApplicationInterface.loadDialog(); } } - - HifiControls.Button { - text: "Load Defaults" - color: hifi.buttons.black - height: 26 - onClicked: loadDefaults() - } } HifiControls.VerticalSpacer {} diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 5e7e0f5709..a45390b3d0 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -116,6 +116,7 @@ Item { anchors.fill: parent hoverEnabled: true enabled: true + preventStealing: true onClicked: { console.log("Tablet Button Clicked!"); if (tabletButton.inDebugMode) { diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 1556a9c0c0..4a26d11128 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -337,5 +337,6 @@ Item { readonly property string playback_play: "\ue01d" readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e5fb9949db..616b6914e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -193,6 +193,9 @@ #include #include #include +#include "commerce/Ledger.h" +#include "commerce/Wallet.h" +#include "commerce/QmlCommerce.h" // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -222,6 +225,7 @@ static const int MIN_PROCESSING_THREAD_POOL_SIZE = 1; static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; static const QString SVO_JSON_EXTENSION = ".svo.json"; +static const QString JSON_GZ_EXTENSION = ".json.gz"; static const QString JSON_EXTENSION = ".json"; static const QString JS_EXTENSION = ".js"; static const QString FST_EXTENSION = ".fst"; @@ -255,6 +259,8 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; +static const QString DOMAIN_SPAWNING_POINT = "/0, -10, 0"; + const QHash Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, { SVO_JSON_EXTENSION, &Application::importSVOFromURL }, @@ -262,6 +268,7 @@ const QHash Application::_acceptedExtensi { JSON_EXTENSION, &Application::importJSONFromURL }, { JS_EXTENSION, &Application::askToLoadScript }, { FST_EXTENSION, &Application::askToSetAvatarUrl }, + { JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent }, { ZIP_EXTENSION, &Application::importFromZIP } }; @@ -598,6 +605,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -707,7 +716,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); - _window->setWindowTitle("Interface"); + _window->setWindowTitle("High Fidelity Interface"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us @@ -955,6 +964,53 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); + // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. + DependencyManager::get()->initializeShapePipelines(); + + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + static const QString TESTER = "HIFI_TESTER"; + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "version", applicationVersion() }, + { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["sl_version"] }, + { "gl_renderer", glContextData["renderer"] }, + { "ideal_thread_count", QThread::idealThreadCount() } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + + ProcessorInfo procInfo; + if (getProcessorInfo(procInfo)) { + properties["processor_core_count"] = procInfo.numProcessorCores; + properties["logical_processor_count"] = procInfo.numLogicalProcessors; + properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; + properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; + properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; + } + + // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; // once the settings have been loaded, check if we need to flip the default for UserActivityLogger @@ -2057,6 +2113,7 @@ void Application::initializeUi() { LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); + QmlCommerce::registerType(); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); @@ -2758,7 +2815,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) // if they come from the High Fidelity Marketplace Assets CDN - QUrl jsonURL { urlString }; if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { @@ -6140,6 +6196,55 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) { return true; } +bool Application::askToReplaceDomainContent(const QString& url) { + QString methodDetails; + if (DependencyManager::get()->getThisNodeCanReplaceContent()) { + QUrl originURL { url }; + if (originURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { + // Create a confirmation dialog when this call is made + const int MAX_CHARACTERS_PER_LINE = 90; + static const QString infoText = simpleWordWrap("Your domain's content will be replaced with a new content set. " + "If you want to save what you have now, create a backup before proceeding. For more information about backing up " + "and restoring content, visit the documentation page at: ", MAX_CHARACTERS_PER_LINE) + + "\nhttps://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content"; + + bool agreeToReplaceContent = false; // assume false + agreeToReplaceContent = QMessageBox::Yes == OffscreenUi::question("Are you sure you want to replace this domain's content set?", + infoText, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (agreeToReplaceContent) { + // Given confirmation, send request to domain server to replace content + qCDebug(interfaceapp) << "Attempting to replace domain content: " << url; + QByteArray urlData(url.toUtf8()); + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { + return node->getType() == NodeType::EntityServer && node->getActiveSocket(); + }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) { + auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); + octreeFilePacket->write(urlData); + limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode); + }); + DependencyManager::get()->handleLookupString(DOMAIN_SPAWNING_POINT); + methodDetails = "SuccessfulRequestToReplaceContent"; + } else { + methodDetails = "UserDeclinedToReplaceContent"; + } + } else { + methodDetails = "ContentSetDidNotOriginateFromMarketplace"; + } + } else { + methodDetails = "UserDoesNotHavePermissionToReplaceContent"; + OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content", + QMessageBox::Ok, QMessageBox::Ok); + } + QJsonObject messageProperties = { + { "status", methodDetails }, + { "content_set_url", url } + }; + UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); + return true; +} + void Application::displayAvatarAttachmentWarning(const QString& message) const { auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure"); OffscreenUi::warning(avatarAttachmentWarningTitle, message); @@ -6806,6 +6911,12 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa }); } +void Application::takeSecondaryCameraSnapshot() { + postLambdaEvent([this] { + Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot()); + }); +} + void Application::shareSnapshot(const QString& path, const QUrl& href) { postLambdaEvent([path, href] { // not much to do here, everything is done in snapshot code... diff --git a/interface/src/Application.h b/interface/src/Application.h index f8eb393f9e..752d6657fb 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -274,6 +274,7 @@ public: float getAverageSimsPerSecond() const { return _simCounter.rate(); } void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f); + void takeSecondaryCameraSnapshot(); void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } @@ -297,6 +298,7 @@ public: QUuid getTabletFrameID() const; // may be an entity or an overlay void setAvatarOverrideUrl(const QUrl& url, bool save); + void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } @@ -430,6 +432,8 @@ private slots: void displayAvatarAttachmentWarning(const QString& message) const; bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; + bool askToReplaceDomainContent(const QString& url); + void setSessionUUID(const QUuid& sessionUUID) const; void domainChanged(const QString& domainHostname); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 56bd3eb749..2c4a515736 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -555,6 +555,8 @@ Menu::Menu() { avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKChains, 0, false, avatar.get(), SLOT(setEnableDebugDrawIKChains(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderDetailedCollision, 0, false, + avatar.get(), SLOT(setEnableDebugDrawDetailedCollision(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a81ef9ac86..4e21cfa4ac 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -162,6 +162,7 @@ namespace MenuOption { const QString RenderIKTargets = "Show IK Targets"; const QString RenderIKConstraints = "Show IK Constraints"; const QString RenderIKChains = "Show IK Chains"; + const QString RenderDetailedCollision = "Show Detailed Collision"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts..."; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bd545c64e0..3d48540a64 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -273,19 +273,15 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { return; } - const float SHRINK_RATE = 0.15f; - const float MIN_FADE_SCALE = MIN_AVATAR_SCALE; - QReadLocker locker(&_hashLock); QVector::iterator avatarItr = _avatarsToFade.begin(); + const render::ScenePointer& scene = qApp->getMain3DScene(); while (avatarItr != _avatarsToFade.end()) { auto avatar = std::static_pointer_cast(*avatarItr); - avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE); - avatar->animateScaleChanges(deltaTime); - if (avatar->getTargetScale() <= MIN_FADE_SCALE) { + avatar->updateFadingStatus(scene); + if (!avatar->isFading()) { // fading to zero is such a rare event we push a unique transaction for each if (avatar->isInScene()) { - const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; avatar->removeFromScene(*avatarItr, scene, transaction); scene->enqueueTransaction(transaction); @@ -323,6 +319,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // remove from node sets, if present DependencyManager::get()->removeFromIgnoreMuteSets(avatar->getSessionUUID()); DependencyManager::get()->avatarDisconnected(avatar->getSessionUUID()); + avatar->fadeOut(qApp->getMain3DScene(), removalReason); } _avatarsToFade.push_back(removedAvatar); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 36295e87b8..30c6a85470 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1060,6 +1060,10 @@ void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { _enableDebugDrawIKConstraints = isEnabled; } +void MyAvatar::setEnableDebugDrawDetailedCollision(bool isEnabled) { + _enableDebugDrawDetailedCollision = isEnabled; +} + void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { _enableDebugDrawIKChains = isEnabled; } @@ -1805,6 +1809,37 @@ void MyAvatar::postUpdate(float deltaTime) { AnimPose postUpdateRoomPose(_sensorToWorldMatrix); updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose); + + if (_enableDebugDrawDetailedCollision) { + AnimPose rigToWorldPose(glm::vec3(1.0f), getRotation() * Quaternions::Y_180, getPosition()); + const int NUM_DEBUG_COLORS = 8; + const glm::vec4 DEBUG_COLORS[NUM_DEBUG_COLORS] = { + glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), + glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), + glm::vec4(0.25f, 0.25f, 1.0f, 1.0f), + glm::vec4(1.0f, 1.0f, 0.0f, 1.0f), + glm::vec4(0.25f, 1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.25f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.65f, 0.0f, 1.0f) // Orange you glad I added this color? + }; + + if (_skeletonModel && _skeletonModel->isLoaded()) { + const Rig& rig = _skeletonModel->getRig(); + const FBXGeometry& geometry = _skeletonModel->getFBXGeometry(); + for (int i = 0; i < rig.getJointStateCount(); i++) { + AnimPose jointPose; + rig.getAbsoluteJointPoseInRigFrame(i, jointPose); + const FBXJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; + const AnimPose pose = rigToWorldPose * jointPose; + for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) { + glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]); + glm::vec3 pointB = pose.xformPoint(shapeInfo.debugLines[2 * j + 1]); + DebugDraw::getInstance().drawRay(pointA, pointB, DEBUG_COLORS[i % NUM_DEBUG_COLORS]); + } + } + } + } } void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { @@ -1898,8 +1933,8 @@ void MyAvatar::updateOrientation(float deltaTime) { totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI)); } - // Use head/HMD roll to turn while walking or flying. - if (qApp->isHMDMode() && _hmdRollControlEnabled) { + // Use head/HMD roll to turn while walking or flying, but not when standing still + if (qApp->isHMDMode() && _hmdRollControlEnabled && hasDriveInput()) { // Turn with head roll. const float MIN_CONTROL_SPEED = 0.01f; float speed = glm::length(getVelocity()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index dc4357be52..c7fe309894 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -553,6 +553,7 @@ public slots: void setEnableDebugDrawIKTargets(bool isEnabled); void setEnableDebugDrawIKConstraints(bool isEnabled); void setEnableDebugDrawIKChains(bool isEnabled); + void setEnableDebugDrawDetailedCollision(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); @@ -757,6 +758,7 @@ private: bool _enableDebugDrawIKTargets { false }; bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; + bool _enableDebugDrawDetailedCollision { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6d468c3f30..89e4368515 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -124,12 +124,26 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } - params.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius(); - params.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight(); - params.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset(); - params.isTalking = head->getTimeWithoutTalking() <= 1.5f; + // pass detailed torso k-dops to rig. + int hipsJoint = _rig.indexOfJoint("Hips"); + if (hipsJoint >= 0) { + params.hipsShapeInfo = geometry.joints[hipsJoint].shapeInfo; + } + int spineJoint = _rig.indexOfJoint("Spine"); + if (spineJoint >= 0) { + params.spineShapeInfo = geometry.joints[spineJoint].shapeInfo; + } + int spine1Joint = _rig.indexOfJoint("Spine1"); + if (spine1Joint >= 0) { + params.spine1ShapeInfo = geometry.joints[spine1Joint].shapeInfo; + } + int spine2Joint = _rig.indexOfJoint("Spine2"); + if (spine2Joint >= 0) { + params.spine2ShapeInfo = geometry.joints[spine2Joint].shapeInfo; + } + _rig.updateFromControllerParameters(params, deltaTime); Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState()); diff --git a/interface/src/commerce/CommerceLogging.cpp b/interface/src/commerce/CommerceLogging.cpp new file mode 100644 index 0000000000..65102e9699 --- /dev/null +++ b/interface/src/commerce/CommerceLogging.cpp @@ -0,0 +1,14 @@ +// +// CommerceLogging.cpp +// interface/src/commerce +// +// Created by Howard Stearns on 8/9/17. +// 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 +// + +#include "CommerceLogging.h" + +Q_LOGGING_CATEGORY(commerce, "hifi.commerce") diff --git a/interface/src/commerce/CommerceLogging.h b/interface/src/commerce/CommerceLogging.h new file mode 100644 index 0000000000..d641977872 --- /dev/null +++ b/interface/src/commerce/CommerceLogging.h @@ -0,0 +1,19 @@ +// +// CommerceLogging.h +// interface/src/commerce +// +// Created by Howard Stearns on 8/9/17. +// 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_CommerceLogging_h +#define hifi_CommerceLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(commerce) + +#endif // hifi_CommerceLogging_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp new file mode 100644 index 0000000000..8d7d47aca0 --- /dev/null +++ b/interface/src/commerce/Ledger.cpp @@ -0,0 +1,81 @@ +// +// Ledger.cpp +// interface/src/commerce +// +// Created by Howard Stearns on 8/4/17. +// 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 +// + +#include +#include +#include "AccountManager.h" +#include "Wallet.h" +#include "Ledger.h" +#include "CommerceLogging.h" + +void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) { + QJsonObject transaction; + transaction["hfc_key"] = hfc_key; + transaction["hfc"] = cost; + transaction["asset_id"] = asset_id; + transaction["inventory_key"] = inventory_key; + transaction["inventory_buyer_username"] = buyerUsername; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + + auto wallet = DependencyManager::get(); + QString signature = wallet->signWithKey(transactionString, hfc_key); + QJsonObject request; + request["transaction"] = QString(transactionString); + request["signature"] = signature; + + qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); + // FIXME: talk to server instead + if (_inventory.contains(asset_id)) { + // This is here more for testing than as a definition of semantics. + // When we have popcerts, you will certainly be able to buy a new instance of an item that you already own a different instance of. + // I'm not sure what the server should do for now in this project's MVP. + return emit buyResult("Already owned."); + } + if (initializedBalance() < cost) { + return emit buyResult("Insufficient funds."); + } + _balance -= cost; + QJsonObject inventoryAdditionObject; + inventoryAdditionObject["id"] = asset_id; + inventoryAdditionObject["title"] = "Test Title"; + inventoryAdditionObject["preview"] = "https://www.aspca.org/sites/default/files/cat-care_cat-nutrition-tips_overweight_body4_left.jpg"; + _inventory.push_back(inventoryAdditionObject); + emit buyResult(""); +} + +bool Ledger::receiveAt(const QString& hfc_key) { + auto accountManager = DependencyManager::get(); + if (!accountManager->isLoggedIn()) { + qCWarning(commerce) << "Cannot set receiveAt when not logged in."; + emit receiveAtResult("Not logged in"); + return false; // We know right away that we will fail, so tell the caller. + } + auto username = accountManager->getAccountInfo().getUsername(); + qCInfo(commerce) << "Setting default receiving key for" << username; + emit receiveAtResult(""); // FIXME: talk to server instead. + return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. +} + +void Ledger::balance(const QStringList& keys) { + // FIXME: talk to server instead + qCInfo(commerce) << "Balance:" << initializedBalance(); + emit balanceResult(_balance, ""); +} + +void Ledger::inventory(const QStringList& keys) { + // FIXME: talk to server instead + QJsonObject inventoryObject; + inventoryObject.insert("success", true); + inventoryObject.insert("assets", _inventory); + qCInfo(commerce) << "Inventory:" << inventoryObject; + emit inventoryResult(inventoryObject, ""); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h new file mode 100644 index 0000000000..74ed8c1ab3 --- /dev/null +++ b/interface/src/commerce/Ledger.h @@ -0,0 +1,44 @@ +// +// Ledger.h +// interface/src/commerce +// +// Bottlenecks all interaction with the blockchain or other ledger system. +// +// Created by Howard Stearns on 8/4/17. +// 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_Ledger_h +#define hifi_Ledger_h + +#include +#include +#include + +class Ledger : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = ""); + bool receiveAt(const QString& hfc_key); + void balance(const QStringList& keys); + void inventory(const QStringList& keys); + +signals: + void buyResult(const QString& failureReason); + void receiveAtResult(const QString& failureReason); + void balanceResult(int balance, const QString& failureReason); + void inventoryResult(QJsonObject inventory, const QString& failureReason); + +private: + // These in-memory caches is temporary, until we start sending things to the server. + int _balance{ -1 }; + QJsonArray _inventory{}; + int initializedBalance() { if (_balance < 0) _balance = 100; return _balance; } +}; + +#endif // hifi_Ledger_h diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp new file mode 100644 index 0000000000..573740727f --- /dev/null +++ b/interface/src/commerce/QmlCommerce.cpp @@ -0,0 +1,62 @@ +// +// Commerce.cpp +// interface/src/commerce +// +// Created by Howard Stearns on 8/4/17. +// 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 +// + +#include "QmlCommerce.h" +#include "Application.h" +#include "DependencyManager.h" +#include "Ledger.h" +#include "Wallet.h" + +HIFI_QML_DEF(QmlCommerce) + +QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); + connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); + connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); + connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); +} + +void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + return emit buyResult("Uninitialized Wallet."); + } + QString key = keys[0]; + // For now, we receive at the same key that pays for it. + ledger->buy(key, cost, assetId, key, buyerUsername); + // FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing. + balance(); + inventory(); +} + +void QmlCommerce::balance() { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + ledger->balance(wallet->listPublicKeys()); +} +void QmlCommerce::inventory() { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + ledger->inventory(wallet->listPublicKeys()); +} + +void QmlCommerce::chooseSecurityImage(uint imageID) { + auto wallet = DependencyManager::get(); + wallet->chooseSecurityImage(imageID); +} +void QmlCommerce::getSecurityImage() { + auto wallet = DependencyManager::get(); + wallet->getSecurityImage(); +} \ No newline at end of file diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h new file mode 100644 index 0000000000..5b702bfeff --- /dev/null +++ b/interface/src/commerce/QmlCommerce.h @@ -0,0 +1,43 @@ +// +// Commerce.h +// interface/src/commerce +// +// Guard for safe use of Commerce (Wallet, Ledger) by authorized QML. +// +// Created by Howard Stearns on 8/4/17. +// 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 +// + +#pragma once +#ifndef hifi_QmlCommerce_h +#define hifi_QmlCommerce_h + +#include + +class QmlCommerce : public OffscreenQmlDialog { + Q_OBJECT + HIFI_QML_DECL + +public: + QmlCommerce(QQuickItem* parent = nullptr); + +signals: + void buyResult(const QString& failureMessage); + // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and + // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). + void balanceResult(int balance, const QString& failureMessage); + void inventoryResult(QJsonObject inventory, const QString& failureMessage); + void securityImageResult(uint imageID); + +protected: + Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); + Q_INVOKABLE void balance(); + Q_INVOKABLE void inventory(); + Q_INVOKABLE void chooseSecurityImage(uint imageID); + Q_INVOKABLE void getSecurityImage(); +}; + +#endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp new file mode 100644 index 0000000000..f47b174d88 --- /dev/null +++ b/interface/src/commerce/Wallet.cpp @@ -0,0 +1,276 @@ +// +// Wallet.cpp +// interface/src/commerce +// +// Created by Howard Stearns on 8/4/17. +// 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 +// + +#include "CommerceLogging.h" +#include "Ledger.h" +#include "Wallet.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +static const char* KEY_FILE = "hifikey"; + +void initialize() { + static bool initialized = false; + if (!initialized) { + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + initialized = true; + } +} + +QString keyFilePath() { + return PathUtils::getAppDataFilePath(KEY_FILE); +} + +// for now the callback function just returns the same string. Later we can hook +// this to the gui (some thought required) +int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { + // just return a hardcoded pwd for now + static const char* pwd = "pwd"; + strcpy(password, pwd); + return static_cast(strlen(pwd)); +} + +// BEGIN copied code - this will be removed/changed at some point soon +// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. +// We will have a different implementation in practice, but this gives us a start for now +QPair generateRSAKeypair() { + + RSA* keyPair = RSA_new(); + BIGNUM* exponent = BN_new(); + QPair retval; + + const unsigned long RSA_KEY_EXPONENT = 65537; + BN_set_word(exponent, RSA_KEY_EXPONENT); + + // seed the random number generator before we call RSA_generate_key_ex + srand(time(NULL)); + + const int RSA_KEY_BITS = 2048; + + if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) { + qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error(); + + // we're going to bust out of here but first we cleanup the BIGNUM + BN_free(exponent); + return retval; + } + + // we don't need the BIGNUM anymore so clean that up + BN_free(exponent); + + // grab the public key and private key from the file + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER); + + unsigned char* privateKeyDER = NULL; + int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER); + + if (publicKeyLength <= 0 || privateKeyLength <= 0) { + qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error(); + + + // cleanup the RSA struct + RSA_free(keyPair); + + // cleanup the public and private key DER data, if required + if (publicKeyLength > 0) { + OPENSSL_free(publicKeyDER); + } + + if (privateKeyLength > 0) { + OPENSSL_free(privateKeyDER); + } + + return retval; + } + + + + // now lets persist them to files + // TODO: figure out a scheme for multiple keys, etc... + FILE* fp; + if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) { + if (!PEM_write_RSAPublicKey(fp, keyPair)) { + fclose(fp); + qCDebug(commerce) << "failed to write public key"; + return retval; + } + + if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { + fclose(fp); + qCDebug(commerce) << "failed to write private key"; + return retval; + } + fclose(fp); + } + + RSA_free(keyPair); + + // prepare the return values + retval.first = new QByteArray(reinterpret_cast(publicKeyDER), publicKeyLength ), + retval.second = new QByteArray(reinterpret_cast(privateKeyDER), privateKeyLength ); + + // cleanup the publicKeyDER and publicKeyDER data + OPENSSL_free(publicKeyDER); + OPENSSL_free(privateKeyDER); + return retval; +} +// END copied code (which will soon change) + +// the public key can just go into a byte array +QByteArray readPublicKey(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ((fp = fopen(filename, "r"))) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) { + // file read successfully + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER); + // TODO: check for 0 length? + + // cleanup + RSA_free(key); + fclose(fp); + + qCDebug(commerce) << "parsed public key file successfully"; + + QByteArray retval((char*)publicKeyDER, publicKeyLength); + OPENSSL_free(publicKeyDER); + return retval; + } else { + qCDebug(commerce) << "couldn't parse" << filename; + } + fclose(fp); + } else { + qCDebug(commerce) << "couldn't open" << filename; + } + return QByteArray(); +} + +// the private key should be read/copied into heap memory. For now, we need the RSA struct +// so I'll return that. Note we need to RSA_free(key) later!!! +RSA* readPrivateKey(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ((fp = fopen(filename, "r"))) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) { + // cleanup + fclose(fp); + + qCDebug(commerce) << "parsed private key file successfully"; + + } else { + qCDebug(commerce) << "couldn't parse" << filename; + } + fclose(fp); + } else { + qCDebug(commerce) << "couldn't open" << filename; + } + return key; +} + +bool Wallet::createIfNeeded() { + if (_publicKeys.count() > 0) return false; + + // FIXME: initialize OpenSSL elsewhere soon + initialize(); + + // try to read existing keys if they exist... + auto publicKey = readPublicKey(keyFilePath().toStdString().c_str()); + if (publicKey.size() > 0) { + if (auto key = readPrivateKey(keyFilePath().toStdString().c_str()) ) { + qCDebug(commerce) << "read private key"; + RSA_free(key); + // K -- add the public key since we have a legit private key associated with it + _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); + return false; + } + } + qCInfo(commerce) << "Creating wallet."; + return generateKeyPair(); +} + +bool Wallet::generateKeyPair() { + qCInfo(commerce) << "Generating keypair."; + auto keyPair = generateRSAKeypair(); + + _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64())); + qCDebug(commerce) << "public key:" << _publicKeys.last(); + + // It's arguable whether we want to change the receiveAt every time, but: + // 1. It's certainly needed the first time, when createIfNeeded answers true. + // 2. It is maximally private, and we can step back from that later if desired. + // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. + auto ledger = DependencyManager::get(); + return ledger->receiveAt(_publicKeys.last()); +} +QStringList Wallet::listPublicKeys() { + qCInfo(commerce) << "Enumerating public keys."; + createIfNeeded(); + return _publicKeys; +} + +// for now a copy of how we sign in libraries/networking/src/DataServerAccountInfo - +// we sha256 the text, read the private key from disk (for now!), and return the signed +// sha256. Note later with multiple keys, we may need the key parameter (or something +// similar) so I left it alone for now. Also this will probably change when we move +// away from RSA keys anyways. Note that since this returns a QString, we better avoid +// the horror of code pages and so on (changing the bytes) by just returning a base64 +// encoded string representing the signature (suitable for http, etc...) +QString Wallet::signWithKey(const QByteArray& text, const QString& key) { + qCInfo(commerce) << "Signing text."; + RSA* rsaPrivateKey = NULL; + if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) { + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + + QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256); + + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); + + if (encryptReturn != -1) { + return QUrl::toPercentEncoding(signature.toBase64()); + } + } + return QString(); +} + + +void Wallet::chooseSecurityImage(uint imageID) { + _chosenSecurityImage = (SecurityImage)imageID; + emit securityImageResult(imageID); +} +void Wallet::getSecurityImage() { + emit securityImageResult(_chosenSecurityImage); +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h new file mode 100644 index 0000000000..a1c7c7752b --- /dev/null +++ b/interface/src/commerce/Wallet.h @@ -0,0 +1,54 @@ +// +// Wallet.h +// interface/src/commerce +// +// API for secure keypair management +// +// Created by Howard Stearns on 8/4/17. +// 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_Wallet_h +#define hifi_Wallet_h + +#include + +class Wallet : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + // These are currently blocking calls, although they might take a moment. + bool createIfNeeded(); + bool generateKeyPair(); + QStringList listPublicKeys(); + QString signWithKey(const QByteArray& text, const QString& key); + void chooseSecurityImage(uint imageID); + void getSecurityImage(); + +signals: + void securityImageResult(uint imageID); + +protected: + // ALWAYS add SecurityImage enum values to the END of the enum. + // They must be in the same order as the images are listed in + // SecurityImageSelection.qml + enum SecurityImage { + NONE = 0, + Cat, + Car, + Dog, + Stars, + Plane, + Gingerbread + }; + +private: + QStringList _publicKeys{}; + SecurityImage _chosenSecurityImage = SecurityImage::NONE; +}; + +#endif // hifi_Wallet_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 84f4cbbbd8..277989439c 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -294,6 +294,10 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f qApp->takeSnapshot(notify, includeAnimated, aspectRatio); } +void WindowScriptingInterface::takeSecondaryCameraSnapshot() { + qApp->takeSecondaryCameraSnapshot(); +} + void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) { qApp->shareSnapshot(path, href); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index f8ed20f42f..28f1bafa5d 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -60,6 +60,7 @@ public slots: void showAssetServer(const QString& upload = ""); void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); + void takeSecondaryCameraSnapshot(); void makeConnection(bool success, const QString& userNameOrError); void displayAnnouncement(const QString& message); void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c9e59ada23..1c3df3210c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -52,7 +52,7 @@ void setupPreferences() { { auto getter = [=]()->QString { return myAvatar->getFullAvatarURLFromPreferences().toString(); }; - auto setter = [=](const QString& value) { myAvatar->useFullAvatarURL(value, ""); }; + auto setter = [=](const QString& value) { myAvatar->useFullAvatarURL(value, ""); qApp->clearAvatarOverrideUrl(); }; auto preference = new AvatarPreference(AVATAR_BASICS, "Appearance", getter, setter); preferences->addPreference(preference); } @@ -315,21 +315,21 @@ void setupPreferences() { static const QString RENDER("Graphics"); auto renderConfig = qApp->getRenderEngine()->getConfiguration(); if (renderConfig) { - auto ambientOcclusionConfig = renderConfig->getConfig(); - if (ambientOcclusionConfig) { - auto getter = [ambientOcclusionConfig]()->QString { return ambientOcclusionConfig->getPreset(); }; - auto setter = [ambientOcclusionConfig](QString preset) { ambientOcclusionConfig->setPreset(preset); }; + auto mainViewAmbientOcclusionConfig = renderConfig->getConfig("RenderMainView.AmbientOcclusion"); + if (mainViewAmbientOcclusionConfig) { + auto getter = [mainViewAmbientOcclusionConfig]()->QString { return mainViewAmbientOcclusionConfig->getPreset(); }; + auto setter = [mainViewAmbientOcclusionConfig](QString preset) { mainViewAmbientOcclusionConfig->setPreset(preset); }; auto preference = new ComboBoxPreference(RENDER, "Ambient occlusion", getter, setter); - preference->setItems(ambientOcclusionConfig->getPresetList()); + preference->setItems(mainViewAmbientOcclusionConfig->getPresetList()); preferences->addPreference(preference); } - auto shadowConfig = renderConfig->getConfig(); - if (shadowConfig) { - auto getter = [shadowConfig]()->QString { return shadowConfig->getPreset(); }; - auto setter = [shadowConfig](QString preset) { shadowConfig->setPreset(preset); }; + auto mainViewShadowConfig = renderConfig->getConfig("RenderMainView.RenderShadowTask"); + if (mainViewShadowConfig) { + auto getter = [mainViewShadowConfig]()->QString { return mainViewShadowConfig->getPreset(); }; + auto setter = [mainViewShadowConfig](QString preset) { mainViewShadowConfig->setPreset(preset); }; auto preference = new ComboBoxPreference(RENDER, "Shadows", getter, setter); - preference->setItems(shadowConfig->getPresetList()); + preference->setItems(mainViewShadowConfig->getPresetList()); preferences->addPreference(preference); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 77b9424d2f..46fb2df007 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -55,6 +55,8 @@ ContextOverlayInterface::ContextOverlayInterface() { _contextOverlayJustClicked = false; } }); + auto entityScriptingInterface = DependencyManager::get().data(); + connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity); } static const uint32_t LEFT_HAND_HW_ID = 1; @@ -250,7 +252,8 @@ void ContextOverlayInterface::openMarketplace() { auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); // construct the url to the marketplace item QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID; - tablet->gotoWebScreen(url); + QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js"; + tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH); _hmdScriptingInterface->openTablet(); _isInMarketplaceInspectionMode = true; } @@ -277,3 +280,9 @@ void ContextOverlayInterface::disableEntityHighlight(const EntityItemID& entityI } }); } + +void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { + if (_currentEntityWithContextOverlay == entityID) { + destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent()); + } +} diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index ba9cb68575..b386de08cc 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -80,6 +80,7 @@ private: void enableEntityHighlight(const EntityItemID& entityItemID); void disableEntityHighlight(const EntityItemID& entityItemID); + void deletingEntity(const EntityItemID& entityItemID); }; #endif // hifi_ContextOverlayInterface_h diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 900c79fc3f..d8fe0e6bb8 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -32,6 +32,8 @@ Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) : _length = line3DOverlay->getLength(); _endParentID = line3DOverlay->getEndParentID(); _endParentJointIndex = line3DOverlay->getEndJointIndex(); + _glow = line3DOverlay->getGlow(); + _glowWidth = line3DOverlay->getGlowWidth(); } Line3DOverlay::~Line3DOverlay() { @@ -138,11 +140,9 @@ void Line3DOverlay::render(RenderArgs* args) { // TODO: add support for color to renderDashedLine() geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); geometryCache->renderDashedLine(*batch, start, end, colorv4, _geometryCacheID); - } else if (_glow > 0.0f) { - geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _glowWidth, _geometryCacheID); } else { - geometryCache->bindSimpleProgram(*batch, false, false, false, true, true); - geometryCache->renderLine(*batch, start, end, colorv4, _geometryCacheID); + // renderGlowLine handles both glow = 0 and glow > 0 cases + geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _glowWidth, _geometryCacheID); } } } @@ -228,9 +228,9 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { } } - auto glowWidth = properties["glow"]; + auto glowWidth = properties["glowWidth"]; if (glowWidth.isValid()) { - setGlow(glowWidth.toFloat()); + setGlowWidth(glowWidth.toFloat()); } } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index b6c7dcbd46..fc9371935c 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -96,7 +96,7 @@ void Overlays::cleanupOverlaysToDelete() { } while (!_overlaysToDelete.isEmpty()); } - if (transaction._removedItems.size() > 0) { + if (transaction.hasRemovedItems()) { scene->enqueueTransaction(transaction); } } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 8e58b648e6..40af679a13 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -38,6 +38,7 @@ #include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" +#include "scripting/MenuScriptingInterface.h" #include #include #include "FileDialogHelper.h" @@ -191,6 +192,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 21f98d3e01..1b91737364 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -168,6 +168,8 @@ void Rig::destroyAnimGraph() { void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); + _geometryToRigTransform = modelOffset * geometry.offset; + _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); @@ -1099,35 +1101,139 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos } } -void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, - const AnimPose& leftHandPose, const AnimPose& rightHandPose, - float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset) { +const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); +const int DOP14_COUNT = 14; +const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { + Vectors::UNIT_X, + -Vectors::UNIT_X, + Vectors::UNIT_Y, + -Vectors::UNIT_Y, + Vectors::UNIT_Z, + -Vectors::UNIT_Z, + glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) +}; - // Use this capsule to represent the avatar body. - int hipsIndex = indexOfJoint("Hips"); - glm::vec3 hipsTrans; - if (hipsIndex >= 0) { - hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans(); +// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. +// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward +// such that it lies on the surface of the kdop. +static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const FBXJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { + + // transform point into local space of jointShape. + glm::vec3 localPoint = shapePose.inverse().xformPoint(point); + + // Only works for 14-dop shape infos. + assert(shapeInfo.dots.size() == DOP14_COUNT); + if (shapeInfo.dots.size() != DOP14_COUNT) { + return false; } - const glm::vec3 bodyCapsuleCenter = hipsTrans - bodyCapsuleLocalOffset; - const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, bodyCapsuleHalfHeight, 0); - const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, bodyCapsuleHalfHeight, 0); + glm::vec3 minDisplacement(FLT_MAX); + float minDisplacementLen = FLT_MAX; + glm::vec3 p = localPoint - shapeInfo.avgPoint; + float pLen = glm::length(p); + if (pLen > 0.0f) { + int slabCount = 0; + for (int i = 0; i < DOP14_COUNT; i++) { + float dot = glm::dot(p, DOP14_NORMALS[i]); + if (dot > 0.0f && dot < shapeInfo.dots[i]) { + slabCount++; + float distToPlane = pLen * (shapeInfo.dots[i] / dot); + float displacementLen = distToPlane - pLen; + + // keep track of the smallest displacement + if (displacementLen < minDisplacementLen) { + minDisplacementLen = displacementLen; + minDisplacement = (p / pLen) * displacementLen; + } + } + } + if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) { + // we are within the k-dop so push the point along the minimum displacement found + displacementOut = shapePose.xformVectorFast(minDisplacement); + return true; + } else { + // point is outside of kdop + return false; + } + } else { + // point is directly on top of shapeInfo.avgPoint. + // push the point out along the x axis. + displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]); + return true; + } +} + +glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const { + glm::vec3 position = handPosition; + glm::vec3 displacement; + int hipsJoint = indexOfJoint("Hips"); + if (hipsJoint >= 0) { + AnimPose hipsPose; + if (getAbsoluteJointPoseInRigFrame(hipsJoint, hipsPose)) { + if (findPointKDopDisplacement(position, hipsPose, hipsShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spineJoint = indexOfJoint("Spine"); + if (spineJoint >= 0) { + AnimPose spinePose; + if (getAbsoluteJointPoseInRigFrame(spineJoint, spinePose)) { + if (findPointKDopDisplacement(position, spinePose, spineShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spine1Joint = indexOfJoint("Spine1"); + if (spine1Joint >= 0) { + AnimPose spine1Pose; + if (getAbsoluteJointPoseInRigFrame(spine1Joint, spine1Pose)) { + if (findPointKDopDisplacement(position, spine1Pose, spine1ShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spine2Joint = indexOfJoint("Spine2"); + if (spine2Joint >= 0) { + AnimPose spine2Pose; + if (getAbsoluteJointPoseInRigFrame(spine2Joint, spine2Pose)) { + if (findPointKDopDisplacement(position, spine2Pose, spine2ShapeInfo, displacement)) { + position += displacement; + } + } + } + + return position; +} + +void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { - const float HAND_RADIUS = 0.05f; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; + int hipsIndex = indexOfJoint("Hips"); + if (leftHandEnabled) { glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); if (!hipsEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } + // prevent the hand IK targets from intersecting the torso + handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("leftHandPosition", handPosition); @@ -1173,11 +1279,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab glm::quat handRotation = rightHandPose.rot(); if (!hipsEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } + // prevent the hand IK targets from intersecting the torso + handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("rightHandPosition", handPosition); @@ -1414,7 +1517,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, leftArmEnabled, rightArmEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.bodyCapsuleRadius, params.bodyCapsuleHalfHeight, params.bodyCapsuleLocalOffset); + params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo); updateFeet(leftFootEnabled, rightFootEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]); @@ -1660,59 +1763,11 @@ void Rig::computeAvatarBoundingCapsule( return; } - AnimInverseKinematics ikNode("boundingShape"); - ikNode.setSkeleton(_animSkeleton); - - ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", - "leftHandType", "leftHandWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", - "rightHandType", "rightHandWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", - "leftFootType", "leftFootWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", - "rightFootType", "rightFootWeight", 1.0f, {}, - QString(), QString(), QString()); glm::vec3 hipsPosition(0.0f); int hipsIndex = indexOfJoint("Hips"); if (hipsIndex >= 0) { hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans()); } - AnimVariantMap animVars; - animVars.setRigToGeometryTransform(_rigToGeometryTransform); - glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X); - animVars.set("leftHandPosition", hipsPosition); - animVars.set("leftHandRotation", handRotation); - animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightHandPosition", hipsPosition); - animVars.set("rightHandRotation", handRotation); - animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - - int rightFootIndex = indexOfJoint("RightFoot"); - int leftFootIndex = indexOfJoint("LeftFoot"); - if (rightFootIndex != -1 && leftFootIndex != -1) { - glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f); - glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition); - glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X); - animVars.set("leftFootPosition", footPosition); - animVars.set("leftFootRotation", footRotation); - animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightFootPosition", footPosition); - animVars.set("rightFootRotation", footRotation); - animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); - } - - // call overlay twice: once to verify AnimPoseVec joints and again to do the IK - AnimNode::Triggers triggersOut; - AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform); - float dt = 1.0f; // the value of this does not matter - ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); - AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); - - // convert relative poses to absolute - _animSkeleton->convertRelativePosesToAbsolute(finalPoses); // compute bounding box that encloses all points Extents totalExtents; @@ -1723,15 +1778,15 @@ void Rig::computeAvatarBoundingCapsule( // even if they do not have legs (default robot) totalExtents.addPoint(glm::vec3(0.0f)); - // HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints + // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. int index = indexOfJoint("Head"); while (index != -1) { const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; - AnimPose pose = finalPoses[index]; + AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index); if (shapeInfo.points.size() > 0) { - for (int j = 0; j < shapeInfo.points.size(); ++j) { - totalExtents.addPoint((pose * shapeInfo.points[j])); + for (auto& point : shapeInfo.points) { + totalExtents.addPoint((pose * point)); } } index = _animSkeleton->getParentIndex(index); @@ -1745,7 +1800,6 @@ void Rig::computeAvatarBoundingCapsule( radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); heightOut = diagonal.y - 2.0f * radiusOut; - glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans(); - glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); - localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition); + glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); + localOffsetOut = capsuleCenter - hipsPosition; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7e1504e461..ca55635250 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -75,9 +75,10 @@ public: AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space bool secondaryControllerActiveFlags[NumSecondaryControllerTypes]; bool isTalking; - float bodyCapsuleRadius; - float bodyCapsuleHalfHeight; - glm::vec3 bodyCapsuleLocalOffset; + FBXJointShapeInfo hipsShapeInfo; + FBXJointShapeInfo spineShapeInfo; + FBXJointShapeInfo spine1ShapeInfo; + FBXJointShapeInfo spine2ShapeInfo; }; struct EyeParameters { @@ -249,7 +250,8 @@ protected: void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset); + const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); @@ -257,6 +259,8 @@ protected: glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; + glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index bc02da1cc4..27bab687d5 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -160,6 +160,7 @@ AudioClient::AudioClient() : AbstractAudioInterface(), _gate(this), _audioInput(NULL), + _dummyAudioInput(NULL), _desiredInputFormat(), _inputFormat(), _numInputCallbackBytes(0), @@ -617,12 +618,11 @@ void AudioClient::start() { void AudioClient::stop() { - // "switch" to invalid devices in order to shut down the state - qCDebug(audioclient) << "AudioClient::stop(), about to call switchInputToAudioDevice(null)"; - switchInputToAudioDevice(QAudioDeviceInfo()); + qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down"; + switchInputToAudioDevice(QAudioDeviceInfo(), true); - qCDebug(audioclient) << "AudioClient::stop(), about to call switchOutputToAudioDevice(null)"; - switchOutputToAudioDevice(QAudioDeviceInfo()); + qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down"; + switchOutputToAudioDevice(QAudioDeviceInfo(), true); } void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { @@ -1125,6 +1125,15 @@ void AudioClient::handleMicAudioInput() { } } +void AudioClient::handleDummyAudioInput() { + const int numNetworkBytes = _isStereoInput + ? AudioConstants::NETWORK_FRAME_BYTES_STEREO + : AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + + QByteArray audioBuffer(numNetworkBytes, 0); // silent + handleAudioInput(audioBuffer); +} + void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { QByteArray audioBuffer(audio); handleAudioInput(audioBuffer); @@ -1401,7 +1410,7 @@ void AudioClient::outputFormatChanged() { _receivedAudioStream.outputFormatChanged(_outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT); } -bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { +bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo, bool isShutdownRequest) { qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -1423,16 +1432,29 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn _inputDeviceInfo = QAudioDeviceInfo(); } + if (_dummyAudioInput) { + _dummyAudioInput->stop(); + + _dummyAudioInput->deleteLater(); + _dummyAudioInput = NULL; + } + if (_inputToNetworkResampler) { // if we were using an input to network resampler, delete it here delete _inputToNetworkResampler; _inputToNetworkResampler = NULL; } + if (_audioGate) { delete _audioGate; _audioGate = nullptr; } + if (isShutdownRequest) { + qCDebug(audioclient) << "The audio input device has shut down."; + return true; + } + if (!inputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; _inputDeviceInfo = inputDeviceInfo; @@ -1480,11 +1502,34 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn supportedFormat = true; } else { qCDebug(audioclient) << "Error starting audio input -" << _audioInput->error(); + _audioInput->deleteLater(); + _audioInput = NULL; } } } } + // If there is no working input device, use the dummy input device. + // It generates audio callbacks on a timer to simulate a mic stream of silent packets. + // This enables clients without a mic to still receive an audio stream from the mixer. + if (!_audioInput) { + qCDebug(audioclient) << "Audio input device is not available, using dummy input."; + _inputDeviceInfo = QAudioDeviceInfo(); + emit deviceChanged(QAudio::AudioInput, _inputDeviceInfo); + + _inputFormat = _desiredInputFormat; + qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; + qCDebug(audioclient) << "No resampling required for audio input to match desired network format."; + + _audioGate = new AudioGate(_desiredInputFormat.sampleRate(), _desiredInputFormat.channelCount()); + qCDebug(audioclient) << "Noise gate created with" << _desiredInputFormat.channelCount() << "channels."; + + // generate audio callbacks at the network sample rate + _dummyAudioInput = new QTimer(this); + connect(_dummyAudioInput, SIGNAL(timeout()), this, SLOT(handleDummyAudioInput())); + _dummyAudioInput->start((int)(AudioConstants::NETWORK_FRAME_MSECS + 0.5f)); + } + return supportedFormat; } @@ -1518,9 +1563,8 @@ void AudioClient::outputNotify() { } } -bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { +bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo, bool isShutdownRequest) { qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; - bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -1564,6 +1608,11 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice _localToOutputResampler = NULL; } + if (isShutdownRequest) { + qCDebug(audioclient) << "The audio output device has shut down."; + return true; + } + if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputDeviceInfo = outputDeviceInfo; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 1c26b1bb08..26a07235d5 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -170,6 +170,7 @@ public slots: void sendDownstreamAudioStatsPacket() { _stats.publish(); } void handleMicAudioInput(); + void handleDummyAudioInput(); void handleRecordedAudioInput(const QByteArray& audio); void reset(); void audioMixerKilled(); @@ -277,6 +278,7 @@ private: Mutex _injectorsMutex; QAudioInput* _audioInput; + QTimer* _dummyAudioInput; QAudioFormat _desiredInputFormat; QAudioFormat _inputFormat; QIODevice* _inputDevice; @@ -350,8 +352,8 @@ private: void handleLocalEchoAndReverb(QByteArray& inputByteArray); - bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo); - bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo); + bool switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo, bool isShutdownRequest = false); + bool switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo, bool isShutdownRequest = false); // Callback acceleration dependent calculations int calculateNumberOfInputCallbackBytes(const QAudioFormat& format) const; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d775fe05f0..c619656cd9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "Logging.h" @@ -61,7 +62,7 @@ namespace render { template <> uint32_t metaFetchMetaSubItems(const AvatarSharedPointer& avatar, ItemIDs& subItems) { auto avatarPtr = static_pointer_cast(avatar); if (avatarPtr->getSkeletonModel()) { - auto metaSubItems = avatarPtr->getSkeletonModel()->fetchRenderItemIDs(); + auto& metaSubItems = avatarPtr->getSkeletonModel()->fetchRenderItemIDs(); subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end()); return (uint32_t) metaSubItems.size(); } @@ -493,6 +494,48 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, transaction); } + + _mustFadeIn = true; +} + +void Avatar::fadeIn(render::ScenePointer scene) { + render::Transaction transaction; + fade(transaction, render::Transition::USER_ENTER_DOMAIN); + scene->enqueueTransaction(transaction); +} + +void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) { + render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN; + render::Transaction transaction; + + if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) { + transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER; + } + else if (reason == KillAvatarReason::TheirAvatarEnteredYourBubble) { + transitionType = render::Transition::BUBBLE_ISECT_OWNER; + } + fade(transaction, transitionType); + scene->enqueueTransaction(transaction); +} + +void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) { + transaction.addTransitionToItem(_renderItemID, type); + for (auto& attachmentModel : _attachmentModels) { + for (auto itemId : attachmentModel->fetchRenderItemIDs()) { + transaction.addTransitionToItem(itemId, type, _renderItemID); + } + } + _isFading = true; +} + +void Avatar::updateFadingStatus(render::ScenePointer scene) { + render::Transaction transaction; + transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) { + if (transition == nullptr || transition->isFinished) { + _isFading = false; + } + }); + scene->enqueueTransaction(transaction); } void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { @@ -629,6 +672,8 @@ void Avatar::render(RenderArgs* renderArgs) { } void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { + bool canTryFade{ false }; + _attachmentsToDelete.clear(); // check to see if when we added our models to the scene they were ready, if they were not ready, then @@ -637,6 +682,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); + canTryFade = true; } for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { @@ -645,6 +691,12 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { } } + if (_mustFadeIn && canTryFade) { + // Do it now to be sure all the sub items are ready and the fade is sent to them too + fade(transaction, render::Transition::USER_ENTER_DOMAIN); + _mustFadeIn = false; + } + for (auto attachmentModelToRemove : _attachmentsToRemove) { attachmentModelToRemove->removeFromScene(scene, transaction); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 2c75012209..5b64d79484 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -246,6 +246,11 @@ public: void addPhysicsFlags(uint32_t flags); bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; } + void fadeIn(render::ScenePointer scene); + void fadeOut(render::ScenePointer scene, KillAvatarReason reason); + bool isFading() const { return _isFading; } + void updateFadingStatus(render::ScenePointer scene); + public slots: // FIXME - these should be migrated to use Pose data instead @@ -297,6 +302,8 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + void fade(render::Transaction& transaction, render::Transition::Type type); + glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; } void measureMotionDerivatives(float deltaTime); @@ -344,6 +351,8 @@ private: bool _initialized { false }; bool _isLookAtTarget { false }; bool _isAnimatingScale { false }; + bool _mustFadeIn { false }; + bool _isFading { false }; static int _jointConesID; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0e936c49e0..322dbe9be5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -544,7 +544,7 @@ public: Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); - void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } + Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state void setKeyState(KeyState s) { _keyState = s; } diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index a4777b087b..6a19a34727 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -34,3 +34,7 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) { QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const { return QImage(); } + +QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const { + return QImage(); +} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 8d01539e8a..97b71b5780 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -19,6 +19,7 @@ public: bool hasFocus() const override; void submitFrame(const gpu::FramePointer& newFrame) override; QImage getScreenshot(float aspectRatio = 0.0f) const override; + QImage getSecondaryCameraScreenshot() const override; void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {}; private: static const QString NAME; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e1259fc5fc..7144e401e4 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -775,6 +775,19 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { return screenshot.mirrored(false, true); } +QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const { + auto textureCache = DependencyManager::get(); + auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer(); + gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight()); + + auto glBackend = const_cast(*this).getGLBackend(); + QImage screenshot(region.z, region.w, QImage::Format_ARGB32); + withMainThreadContext([&] { + glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); + }); + return screenshot.mirrored(false, true); +} + glm::uvec2 OpenGLDisplayPlugin::getSurfacePixels() const { uvec2 result; auto window = _container->getPrimaryWidget(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 2f93fa630d..2080fa5ea6 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -60,6 +60,7 @@ public: virtual bool setDisplayTexture(const QString& name) override; virtual bool onDisplayTextureReset() { return false; }; QImage getScreenshot(float aspectRatio = 0.0f) const override; + QImage getSecondaryCameraScreenshot() const override; float presentRate() const override; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index d55352d31e..a8eca41077 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -675,7 +675,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { PickRay ray = _viewState->computePickRay(event->x(), event->y()); - bool precisionPicking = true; // for mouse moves we do precision picking + bool precisionPicking = false; // for mouse moves we do not do precision picking RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index f8d155600a..d44ec04e9f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -130,3 +130,42 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); }); } + +bool SimplerRenderableEntitySupport::addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) { + _myItem = scene->allocateID(); + + auto renderData = std::make_shared(self, _myItem); + auto renderPayload = std::make_shared(renderData); + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(self, statusGetters); + renderPayload->addStatusGetters(statusGetters); + + transaction.resetItem(_myItem, renderPayload); + + return true; +} + +void SimplerRenderableEntitySupport::removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) { + transaction.removeItem(_myItem); + render::Item::clearID(_myItem); +} + +void SimplerRenderableEntitySupport::notifyChanged() { + if (!render::Item::isValidID(_myItem)) { + return; + } + + render::Transaction transaction; + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + + if (scene) { + transaction.updateItem(_myItem, [](RenderableEntityItemProxy& data) { + }); + + scene->enqueueTransaction(transaction); + } + else { + qCWarning(entitiesrenderer) << "SimpleRenderableEntityItem::notifyChanged(), Unexpected null scene, possibly during application shutdown"; + } +} diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index c848b10f6a..93cd524acd 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -70,44 +70,13 @@ namespace render { // Mixin class for implementing basic single item rendering class SimplerRenderableEntitySupport : public RenderableEntityInterface { public: - bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override { - _myItem = scene->allocateID(); - auto renderData = std::make_shared(self, _myItem); - auto renderPayload = std::make_shared(renderData); + bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override; + void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override; + void notifyChanged(); - render::Item::Status::Getters statusGetters; - makeEntityItemStatusGetters(self, statusGetters); - renderPayload->addStatusGetters(statusGetters); +protected: - transaction.resetItem(_myItem, renderPayload); - - return true; - } - - void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override { - transaction.removeItem(_myItem); - render::Item::clearID(_myItem); - } - - void notifyChanged() { - if (!render::Item::isValidID(_myItem)) { - return; - } - - render::Transaction transaction; - render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); - - if (scene) { - transaction.updateItem(_myItem, [](RenderableEntityItemProxy& data) { - }); - - scene->enqueueTransaction(transaction); - } else { - qCWarning(entitiesrenderer) << "SimpleRenderableEntityItem::notifyChanged(), Unexpected null scene, possibly during application shutdown"; - } - } -private: render::ItemID _myItem { render::Item::INVALID_ITEM_ID }; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ce821c4656..5914ceb9e1 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "EntityTreeRenderer.h" @@ -218,8 +219,9 @@ namespace render { } template <> uint32_t metaFetchMetaSubItems(const RenderableModelEntityItemMeta::Pointer& payload, ItemIDs& subItems) { auto modelEntity = std::static_pointer_cast(payload->entity); - if (modelEntity->hasModel()) { - auto metaSubItems = modelEntity->getModelNotSafe()->fetchRenderItemIDs(); + auto model = modelEntity->getModelNotSafe(); + if (model && modelEntity->hasModel()) { + auto& metaSubItems = model->fetchRenderItemIDs(); subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end()); return (uint32_t) metaSubItems.size(); } @@ -242,6 +244,12 @@ bool RenderableModelEntityItem::addToScene(const EntityItemPointer& self, const // note: we don't mind if the model fails to add, we'll retry (in render()) until it succeeds _model->addToScene(scene, transaction, statusGetters); +#ifdef MODEL_ENTITY_USE_FADE_EFFECT + if (!_hasTransitioned) { + transaction.addTransitionToItem(_myMetaItem, render::Transition::ELEMENT_ENTER_DOMAIN); + _hasTransitioned = true; + } +#endif } // we've successfully added _myMetaItem so we always return true @@ -374,6 +382,7 @@ void RenderableModelEntityItem::updateModelBounds() { // the per frame simulation/update that might be required if the models properties changed. void RenderableModelEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RMEIrender"); + assert(getType() == EntityTypes::Model); // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating @@ -500,6 +509,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) { makeEntityItemStatusGetters(getThisPointer(), statusGetters); _model->addToScene(scene, transaction, statusGetters); +#ifdef MODEL_ENTITY_USE_FADE_EFFECT + if (!_hasTransitioned) { + transaction.addTransitionToItem(_myMetaItem, render::Transition::ELEMENT_ENTER_DOMAIN); + _hasTransitioned = true; + } +#endif scene->enqueueTransaction(transaction); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index a5ba7bedda..63c212891f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -16,11 +16,14 @@ #include #include -#include + +#include "RenderableEntityItem.h" class Model; class EntityTreeRenderer; +//#define MODEL_ENTITY_USE_FADE_EFFECT + class RenderableModelEntityItem : public ModelEntityItem, RenderableEntityInterface { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -133,7 +136,9 @@ private: QVariantMap _originalTextures; bool _originalTexturesRead = false; bool _dimensionsInitialized = true; - +#ifdef MODEL_ENTITY_USE_FADE_EFFECT + bool _hasTransitioned{ false }; +#endif AnimationPropertyGroup _renderAnimationProperties; render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID }; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index d2bf8e3532..86c547dd2d 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -17,22 +17,101 @@ #include #include +//#define POLYLINE_ENTITY_USE_FADE_EFFECT +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT +# include +#endif + #include "RenderablePolyLineEntityItem.h" #include "paintStroke_vert.h" #include "paintStroke_frag.h" +#include "paintStroke_fade_vert.h" +#include "paintStroke_fade_frag.h" +uint8_t PolyLinePayload::CUSTOM_PIPELINE_NUMBER = 0; + +gpu::PipelinePointer PolyLinePayload::_pipeline; +gpu::PipelinePointer PolyLinePayload::_fadePipeline; + +const int32_t PolyLinePayload::PAINTSTROKE_TEXTURE_SLOT; +const int32_t PolyLinePayload::PAINTSTROKE_UNIFORM_SLOT; + +render::ShapePipelinePointer PolyLinePayload::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { + if (!_pipeline) { + auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); + auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + auto fadeVS = gpu::Shader::createVertex(std::string(paintStroke_fade_vert)); + auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); + gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); +#endif + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT+1)); + gpu::Shader::makeProgram(*fadeProgram, slotBindings); +#endif + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::Pipeline::create(program, state); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + _fadePipeline = gpu::Pipeline::create(fadeProgram, state); +#endif + } + +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + if (key.isFaded()) { + auto fadeEffect = DependencyManager::get(); + return std::make_shared(_fadePipeline, nullptr, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + } else { +#endif + return std::make_shared(_pipeline, nullptr, nullptr, nullptr); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + } +#endif +} + +namespace render { + template <> const ItemKey payloadGetKey(const PolyLinePayload::Pointer& payload) { + return payloadGetKey(std::static_pointer_cast(payload)); + } + template <> const Item::Bound payloadGetBound(const PolyLinePayload::Pointer& payload) { + return payloadGetBound(std::static_pointer_cast(payload)); + } + template <> void payloadRender(const PolyLinePayload::Pointer& payload, RenderArgs* args) { + payloadRender(std::static_pointer_cast(payload), args); + } + template <> uint32_t metaFetchMetaSubItems(const PolyLinePayload::Pointer& payload, ItemIDs& subItems) { + return metaFetchMetaSubItems(std::static_pointer_cast(payload), subItems); + } + + template <> const ShapeKey shapeGetShapeKey(const PolyLinePayload::Pointer& payload) { + auto shapeKey = ShapeKey::Builder().withCustom(PolyLinePayload::CUSTOM_PIPELINE_NUMBER); + return shapeKey.build(); + } +} struct PolyLineUniforms { glm::vec3 color; }; - - EntityItemPointer RenderablePolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderablePolyLineEntityItem(entityID) }; entity->setProperties(properties); + + // As we create the first PolyLine entity, let's register its special shapePipeline factory: + PolyLinePayload::registerShapePipeline(); + return entity; } @@ -45,12 +124,9 @@ _numVertices(0) _uniformBuffer = std::make_shared(sizeof(PolyLineUniforms), (const gpu::Byte*) &uniforms); } -gpu::PipelinePointer RenderablePolyLineEntityItem::_pipeline; gpu::Stream::FormatPointer RenderablePolyLineEntityItem::_format; -const int32_t RenderablePolyLineEntityItem::PAINTSTROKE_TEXTURE_SLOT; -const int32_t RenderablePolyLineEntityItem::PAINTSTROKE_UNIFORM_SLOT; -void RenderablePolyLineEntityItem::createPipeline() { +void RenderablePolyLineEntityItem::createStreamFormat() { static const int NORMAL_OFFSET = 12; static const int TEXTURE_OFFSET = 24; @@ -58,23 +134,6 @@ void RenderablePolyLineEntityItem::createPipeline() { _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); _format->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), NORMAL_OFFSET); _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEXTURE_OFFSET); - - auto VS = gpu::Shader::createVertex(std::string(paintStroke_vert)); - auto PS = gpu::Shader::createPixel(std::string(paintStroke_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(VS, PS); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - PrepareStencil::testMask(*state); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::Pipeline::create(program, state); } void RenderablePolyLineEntityItem::updateGeometry() { @@ -158,10 +217,7 @@ void RenderablePolyLineEntityItem::updateVertices() { } -void RenderablePolyLineEntityItem::update(const quint64& now) { - PolyLineUniforms uniforms; - uniforms.color = toGlm(getXColor()); - memcpy(&_uniformBuffer.edit(), &uniforms, sizeof(PolyLineUniforms)); +void RenderablePolyLineEntityItem::updateMesh() { if (_pointsChanged || _strokeWidthsChanged || _normalsChanged) { QWriteLocker lock(&_quadReadWriteLock); _empty = (_points.size() < 2 || _normals.size() < 2 || _strokeWidths.size() < 2); @@ -170,18 +226,46 @@ void RenderablePolyLineEntityItem::update(const quint64& now) { updateGeometry(); } } +} +void RenderablePolyLineEntityItem::update(const quint64& now) { + PolyLineUniforms uniforms; + uniforms.color = toGlm(getXColor()); + memcpy(&_uniformBuffer.edit(), &uniforms, sizeof(PolyLineUniforms)); + updateMesh(); +} + +bool RenderablePolyLineEntityItem::addToScene(const EntityItemPointer& self, + const render::ScenePointer& scene, + render::Transaction& transaction) { + _myItem = scene->allocateID(); + + auto renderData = std::make_shared(self, _myItem); + auto renderPayload = std::make_shared(renderData); + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(self, statusGetters); + renderPayload->addStatusGetters(statusGetters); + + transaction.resetItem(_myItem, renderPayload); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + transaction.addTransitionToItem(_myItem, render::Transition::ELEMENT_ENTER_DOMAIN); +#endif + updateMesh(); + + return true; } void RenderablePolyLineEntityItem::render(RenderArgs* args) { +#ifndef POLYLINE_ENTITY_USE_FADE_EFFECT checkFading(); - +#endif if (_empty) { return; } - if (!_pipeline) { - createPipeline(); + if (!_format) { + createStreamFormat(); } if (!_texture || _texturesChangedFlag) { @@ -199,24 +283,26 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) { Transform transform = Transform(); transform.setTranslation(getPosition()); transform.setRotation(getRotation()); - batch.setUniformBuffer(PAINTSTROKE_UNIFORM_SLOT, _uniformBuffer); + batch.setUniformBuffer(PolyLinePayload::PAINTSTROKE_UNIFORM_SLOT, _uniformBuffer); batch.setModelTransform(transform); - batch.setPipeline(_pipeline); if (_texture->isLoaded()) { - batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); + batch.setResourceTexture(PolyLinePayload::PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); } else { - batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, nullptr); + batch.setResourceTexture(PolyLinePayload::PAINTSTROKE_TEXTURE_SLOT, nullptr); } batch.setInputFormat(_format); batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); +#ifndef POLYLINE_ENTITY_USE_FADE_EFFECT if (_isFading) { batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); - } else { + } + else { batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } +#endif batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); -}; +} diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 8a62a761e0..4dda6da489 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -22,15 +22,48 @@ #include +class PolyLinePayload : public RenderableEntityItemProxy { +public: + + static uint8_t CUSTOM_PIPELINE_NUMBER; + static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key); + static void registerShapePipeline() { + if (!CUSTOM_PIPELINE_NUMBER) { + CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); + } + } + static gpu::PipelinePointer _pipeline; + static gpu::PipelinePointer _fadePipeline; + + static const int32_t PAINTSTROKE_TEXTURE_SLOT{ 0 }; + static const int32_t PAINTSTROKE_UNIFORM_SLOT{ 0 }; + + PolyLinePayload(const EntityItemPointer& entity, render::ItemID metaID) + : RenderableEntityItemProxy(entity, metaID) {} + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + +}; + +namespace render { + template <> const ItemKey payloadGetKey(const PolyLinePayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const PolyLinePayload::Pointer& payload); + template <> void payloadRender(const PolyLinePayload::Pointer& payload, RenderArgs* args); + template <> uint32_t metaFetchMetaSubItems(const PolyLinePayload::Pointer& payload, ItemIDs& subItems); + template <> const ShapeKey shapeGetShapeKey(const PolyLinePayload::Pointer& payload); +} + class RenderablePolyLineEntityItem : public PolyLineEntityItem, public SimplerRenderableEntitySupport { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - static void createPipeline(); RenderablePolyLineEntityItem(const EntityItemID& entityItemID); virtual void render(RenderArgs* args) override; virtual void update(const quint64& now) override; virtual bool needsToCallUpdate() const override { return true; } + virtual bool addToScene(const EntityItemPointer& self, + const render::ScenePointer& scene, + render::Transaction& transaction) override; bool isTransparent() override { return true; } @@ -38,15 +71,15 @@ public: NetworkTexturePointer _texture; - static gpu::PipelinePointer _pipeline; static gpu::Stream::FormatPointer _format; - static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; - static const int32_t PAINTSTROKE_UNIFORM_SLOT { 0 }; - protected: void updateGeometry(); void updateVertices(); + void updateMesh(); + + static void createStreamFormat(); + gpu::BufferPointer _verticesBuffer; gpu::BufferView _uniformBuffer; unsigned int _numVertices; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 6687d4e721..566a7cd488 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -20,6 +20,10 @@ #include #include "ModelScriptingInterface.h" +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT +# include +#endif + #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdouble-promotion" @@ -56,6 +60,10 @@ #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" +#include "polyvox_fade_vert.h" +#include "polyvox_fade_frag.h" + +#include "RenderablePolyVoxEntityItem.h" #include "EntityEditPacketSender.h" #include "PhysicalEntitySimulation.h" @@ -803,6 +811,12 @@ bool RenderablePolyVoxEntityItem::addToScene(const EntityItemPointer& self, renderPayload->addStatusGetters(statusGetters); transaction.resetItem(_myItem, renderPayload); +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT + if (_mesh && _mesh->getIndexBuffer()._buffer) { + transaction.addTransitionToItem(_myItem, render::Transition::ELEMENT_ENTER_DOMAIN); + _hasTransitioned = true; + } +#endif return true; } @@ -816,44 +830,64 @@ void RenderablePolyVoxEntityItem::removeFromScene(const EntityItemPointer& self, uint8_t PolyVoxPayload::CUSTOM_PIPELINE_NUMBER = 0; -std::shared_ptr PolyVoxPayload::_pipeline; -std::shared_ptr PolyVoxPayload::_wireframePipeline; +gpu::PipelinePointer PolyVoxPayload::_pipelines[2] = { nullptr, nullptr }; +gpu::PipelinePointer PolyVoxPayload::_wireframePipelines[2] = { nullptr, nullptr }; render::ShapePipelinePointer PolyVoxPayload::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { - if (!_pipeline) { - gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert)); - gpu::ShaderPointer pixelShader = gpu::Shader::createPixel(std::string(polyvox_frag)); + if (!_pipelines[0]) { + gpu::ShaderPointer vertexShaders[2] = { gpu::Shader::createVertex(std::string(polyvox_vert)), gpu::Shader::createVertex(std::string(polyvox_fade_vert)) }; + gpu::ShaderPointer pixelShaders[2] = { gpu::Shader::createPixel(std::string(polyvox_frag)), gpu::Shader::createPixel(std::string(polyvox_fade_frag)) }; gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), PolyVoxPayload::MATERIAL_GPU_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("xMap"), 0)); slotBindings.insert(gpu::Shader::Binding(std::string("yMap"), 1)); slotBindings.insert(gpu::Shader::Binding(std::string("zMap"), 2)); - - gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); - gpu::Shader::makeProgram(*program, slotBindings); +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), 3)); +#endif auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMaskDrawShape(*state); - _pipeline = gpu::Pipeline::create(program, state); - auto wireframeState = std::make_shared(); wireframeState->setCullMode(gpu::State::CULL_BACK); wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL); wireframeState->setFillMode(gpu::State::FILL_LINE); PrepareStencil::testMaskDrawShape(*wireframeState); - _wireframePipeline = gpu::Pipeline::create(program, wireframeState); + // Two sets of pipelines: normal and fading + for (auto i = 0; i < 2; i++) { + gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShaders[i], pixelShaders[i]); + gpu::Shader::makeProgram(*program, slotBindings); + + _pipelines[i] = gpu::Pipeline::create(program, state); + _wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState); + } } - if (key.isWireframe()) { - return std::make_shared(_wireframePipeline, nullptr, nullptr, nullptr); +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT + if (key.isFaded()) { + const auto& fadeEffect = DependencyManager::get(); + if (key.isWireframe()) { + return std::make_shared(_wireframePipelines[1], nullptr, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + } + else { + return std::make_shared(_pipelines[1], nullptr, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + } } else { - return std::make_shared(_pipeline, nullptr, nullptr, nullptr); +#endif + if (key.isWireframe()) { + return std::make_shared(_wireframePipelines[0], nullptr, nullptr, nullptr); + } + else { + return std::make_shared(_pipelines[0], nullptr, nullptr, nullptr); + } +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT } +#endif } namespace render { @@ -1368,6 +1402,16 @@ void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) { if (neighborsNeedUpdate) { bonkNeighbors(); } + +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT + if (!_hasTransitioned) { + render::Transaction transaction; + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + transaction.addTransitionToItem(_myItem, render::Transition::ELEMENT_ENTER_DOMAIN); + scene->enqueueTransaction(transaction); + _hasTransitioned = true; + } +#endif } void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { @@ -1631,6 +1675,7 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() { void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) { EntityItem::locationChanged(tellPhysics); + if (!render::Item::isValidID(_myItem)) { return; } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 8f20a7a298..5741931ea2 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -26,6 +26,8 @@ #include "RenderableEntityItem.h" +//#define POLYVOX_ENTITY_USE_FADE_EFFECT + class PolyVoxPayload { public: @@ -38,8 +40,8 @@ public: } static const int MATERIAL_GPU_SLOT = 3; - static std::shared_ptr _pipeline; - static std::shared_ptr _wireframePipeline; + static gpu::PipelinePointer _pipelines[2]; + static gpu::PipelinePointer _wireframePipelines[2]; PolyVoxPayload(EntityItemPointer owner) : _owner(owner), _bounds(AABox()) { } typedef render::Payload Payload; @@ -161,7 +163,7 @@ public: void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); } - // Transparent polyvox didn't seem to be working so disable for now + // Transparent polyvox didn't seem to be working so disable for now. bool isTransparent() override { return false; } bool getMeshes(MeshProxyList& result) override; @@ -177,7 +179,9 @@ private: gpu::Stream::FormatPointer _vertexFormat; bool _meshDirty { true }; // does collision-shape need to be recomputed? bool _meshReady { false }; - +#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT + bool _hasTransitioned{ false }; +#endif NetworkTexturePointer _xTexture; NetworkTexturePointer _yTexture; NetworkTexturePointer _zTexture; @@ -217,4 +221,5 @@ private: bool inUserBounds(const PolyVox::SimpleVolume* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle, int x, int y, int z); + #endif // hifi_RenderablePolyVoxEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 3f9497741f..ddebf07d66 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -20,6 +20,11 @@ #include #include +//#define SHAPE_ENTITY_USE_FADE_EFFECT +#ifdef SHAPE_ENTITY_USE_FADE_EFFECT +# include +#endif + // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; @@ -73,6 +78,9 @@ void RenderableShapeEntityItem::setUserData(const QString& value) { } bool RenderableShapeEntityItem::isTransparent() { +#ifdef SHAPE_ENTITY_USE_FADE_EFFECT + return getLocalRenderAlpha() < 1.0f; +#else if (_procedural && _procedural->isFading()) { float isFading = Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) < 1.0f; _procedural->setIsFading(isFading); @@ -80,6 +88,51 @@ bool RenderableShapeEntityItem::isTransparent() { } else { return getLocalRenderAlpha() < 1.0f || EntityItem::isTransparent(); } +#endif +} + +namespace render { + template <> const ItemKey payloadGetKey(const ShapePayload::Pointer& payload) { + return payloadGetKey(std::static_pointer_cast(payload)); + } + template <> const Item::Bound payloadGetBound(const ShapePayload::Pointer& payload) { + return payloadGetBound(std::static_pointer_cast(payload)); + } + template <> void payloadRender(const ShapePayload::Pointer& payload, RenderArgs* args) { + payloadRender(std::static_pointer_cast(payload), args); + } + template <> uint32_t metaFetchMetaSubItems(const ShapePayload::Pointer& payload, ItemIDs& subItems) { + return metaFetchMetaSubItems(std::static_pointer_cast(payload), subItems); + } + + template <> const ShapeKey shapeGetShapeKey(const ShapePayload::Pointer& payload) { + auto shapeKey = ShapeKey::Builder(); +#ifdef SHAPE_ENTITY_USE_FADE_EFFECT + shapeKey.withCustom(GeometryCache::CUSTOM_PIPELINE_NUMBER); +#endif + auto entity = payload->_entity; + if (entity->getLocalRenderAlpha() < 1.f) { + shapeKey.withTranslucent(); + } + return shapeKey.build(); + } +} + +bool RenderableShapeEntityItem::addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) { + _myItem = scene->allocateID(); + + auto renderData = std::make_shared(self, _myItem); + auto renderPayload = std::make_shared(renderData); + + render::Item::Status::Getters statusGetters; + makeEntityItemStatusGetters(self, statusGetters); + renderPayload->addStatusGetters(statusGetters); + + transaction.resetItem(_myItem, renderPayload); +#ifdef SHAPE_ENTITY_USE_FADE_EFFECT + transaction.addTransitionToItem(_myItem, render::Transition::ELEMENT_ENTER_DOMAIN); +#endif + return true; } void RenderableShapeEntityItem::computeShapeInfo(ShapeInfo& info) { @@ -160,7 +213,9 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); //Q_ASSERT(getType() == EntityTypes::Shape); Q_ASSERT(args->_batch); +#ifndef SHAPE_ENTITY_USE_FADE_EFFECT checkFading(); +#endif if (!_procedural) { _procedural.reset(new Procedural(getUserData())); @@ -188,7 +243,9 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { if (_procedural->ready()) { _procedural->prepare(batch, getPosition(), getDimensions(), getOrientation()); auto outColor = _procedural->getColor(color); +#ifndef SHAPE_ENTITY_USE_FADE_EFFECT outColor.a *= _procedural->isFading() ? Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) : 1.0f; +#endif batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { DependencyManager::get()->renderWireShape(batch, MAPPING[_shape]); @@ -197,15 +254,46 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { } } else { // FIXME, support instanced multi-shape rendering using multidraw indirect - color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; auto geometryCache = DependencyManager::get(); - auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); +#ifdef SHAPE_ENTITY_USE_FADE_EFFECT + auto shapeKey = render::ShapeKey(args->_itemShapeKey); + assert(args->_shapePipeline != nullptr); + + if (shapeKey.isFaded()) { + auto fadeEffect = DependencyManager::get(); + auto fadeCategory = fadeEffect->getLastCategory(); + auto fadeThreshold = fadeEffect->getLastThreshold(); + auto fadeNoiseOffset = fadeEffect->getLastNoiseOffset(); + auto fadeBaseOffset = fadeEffect->getLastBaseOffset(); + auto fadeBaseInvSize = fadeEffect->getLastBaseInvSize(); + + if (shapeKey.isWireframe()) { + geometryCache->renderWireFadeShapeInstance(args, batch, MAPPING[_shape], color, fadeCategory, fadeThreshold, + fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, args->_shapePipeline); + } + else { + geometryCache->renderSolidFadeShapeInstance(args, batch, MAPPING[_shape], color, fadeCategory, fadeThreshold, + fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, args->_shapePipeline); + } + } else { + if (shapeKey.isWireframe()) { + geometryCache->renderWireShapeInstance(args, batch, MAPPING[_shape], color, args->_shapePipeline); + } + else { + geometryCache->renderSolidShapeInstance(args, batch, MAPPING[_shape], color, args->_shapePipeline); + } + } +#else + color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { geometryCache->renderWireShapeInstance(args, batch, MAPPING[_shape], color, pipeline); - } else { + } + else { geometryCache->renderSolidShapeInstance(args, batch, MAPPING[_shape], color, pipeline); } +#endif } static const auto triCount = DependencyManager::get()->getShapeTriangleCount(MAPPING[_shape]); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index d603cdedef..9fb8c96a4c 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -14,6 +14,23 @@ #include "RenderableEntityItem.h" +class ShapePayload : public RenderableEntityItemProxy { +public: + ShapePayload(const EntityItemPointer& entity, render::ItemID metaID) + : RenderableEntityItemProxy(entity, metaID) {} + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + +}; + +namespace render { + template <> const ItemKey payloadGetKey(const ShapePayload::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ShapePayload::Pointer& payload); + template <> void payloadRender(const ShapePayload::Pointer& payload, RenderArgs* args); + template <> uint32_t metaFetchMetaSubItems(const ShapePayload::Pointer& payload, ItemIDs& subItems); + template <> const ShapeKey shapeGetShapeKey(const ShapePayload::Pointer& payload); +} + class RenderableShapeEntityItem : public ShapeEntityItem, private SimplerRenderableEntitySupport { using Pointer = std::shared_ptr; static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -23,6 +40,7 @@ public: static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties); RenderableShapeEntityItem(const EntityItemID& entityItemID) : ShapeEntityItem(entityItemID) {} + bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override; void render(RenderArgs* args) override; void setUserData(const QString& value) override; diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf new file mode 100644 index 0000000000..cc037aeac4 --- /dev/null +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -0,0 +1,53 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// paintStroke_fade.slf +// fragment shader +// +// Created by Olivier Prat on 19/07/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 interpolatedNormal; +in vec2 varTexcoord; +in vec4 varColor; +in vec4 _worldPosition; + +struct PolyLineUniforms { + vec3 color; +}; + +uniform polyLineBuffer { + PolyLineUniforms polyline; +}; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + vec4 texel = texture(originalTexture, varTexcoord); + int frontCondition = 1 -int(gl_FrontFacing) * 2; + vec3 color = varColor.rgb; + packDeferredFragmentTranslucent( + interpolatedNormal * frontCondition, + texel.a * varColor.a, + polyline.color * texel.rgb + fadeEmissive, + vec3(0.01, 0.01, 0.01), + 10.0); +} diff --git a/libraries/entities-renderer/src/paintStroke_fade.slv b/libraries/entities-renderer/src/paintStroke_fade.slv new file mode 100644 index 0000000000..9f10fa5d91 --- /dev/null +++ b/libraries/entities-renderer/src/paintStroke_fade.slv @@ -0,0 +1,43 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// paintStroke_fade.slv +// vertex shader +// +// Created by Olivier Prat on 19/07/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +// the interpolated normal +out vec3 interpolatedNormal; + +//the diffuse texture +out vec2 varTexcoord; + +out vec4 varColor; +out vec4 _worldPosition; + +void main(void) { + + varTexcoord = inTexCoord0.st; + + // pass along the diffuse color + varColor = colorToLinearRGBA(inColor); + + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, interpolatedNormal)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf new file mode 100644 index 0000000000..7af43be53f --- /dev/null +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -0,0 +1,64 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// polyvox_fade.frag +// fragment shader +// +// Created by Olivier Prat on 2017-06-08 +// 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 +// + +<@include model/Material.slh@> +<@include DeferredBufferWrite.slh@> + +<@include Fade.slh@> + +in vec3 _normal; +in vec4 _position; +in vec4 _worldPosition; +in vec4 _worldFadePosition; + +uniform sampler2D xMap; +uniform sampler2D yMap; +uniform sampler2D zMap; +uniform vec3 voxelVolumeSize; + +// Declare after all samplers to prevent sampler location mix up with voxel shading (sampler locations are hardcoded in RenderablePolyVoxEntityItem) +<$declareFadeFragment()$> + +void main(void) { + vec3 emissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldFadePosition.xyz, emissive); + + vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); + worldNormal = normalize(worldNormal); + + float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; + float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; + float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; + + vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY)); + vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ)); + vec4 yzDiffuse = texture(zMap, vec2(inPositionZ, -inPositionY)); + + vec3 xyDiffuseScaled = xyDiffuse.rgb * abs(worldNormal.z); + vec3 xzDiffuseScaled = xzDiffuse.rgb * abs(worldNormal.y); + vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x); + vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0); + + packDeferredFragment( + _normal, + 1.0, + vec3(diffuse), + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE+emissive, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); +} diff --git a/libraries/entities-renderer/src/polyvox_fade.slv b/libraries/entities-renderer/src/polyvox_fade.slv new file mode 100644 index 0000000000..506b5d16e7 --- /dev/null +++ b/libraries/entities-renderer/src/polyvox_fade.slv @@ -0,0 +1,33 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// polyvox_fade.vert +// vertex shader +// +// Created by Seth Alves on 2015-8-3 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +out vec4 _position; +out vec4 _worldPosition; +out vec4 _worldFadePosition; +out vec3 _normal; + +void main(void) { + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldPos(obj, inPosition, _worldFadePosition)$> + _worldPosition = inPosition; +} diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index e53405c989..6482acf944 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -29,7 +29,6 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame, _animationLoop->getFirstFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame, _animationLoop->getLastFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold, _animationLoop->getHold); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation, _animationLoop->getAllowTranslation); } else { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); @@ -38,8 +37,9 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); } + + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); } void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { @@ -58,7 +58,6 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, _animationLoop->setFirstFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, _animationLoop->setLastFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, _animationLoop->setHold); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, allowTranslation, bool, _animationLoop->setAllowTranslation); // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, _animationLoop->setFPS, _animationLoop->getFPS); @@ -73,7 +72,6 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, firstFrame, float, setFirstFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, lastFrame, float, setLastFrame); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, hold, bool, setHold); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, allowTranslation, bool, setAllowTranslation); // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFPS, float, setFPS, getFPS); @@ -81,6 +79,8 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationFrameIndex, float, setCurrentFrame, getCurrentFrame); } + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, allowTranslation, bool, setAllowTranslation); + } void AnimationPropertyGroup::merge(const AnimationPropertyGroup& other) { @@ -93,6 +93,7 @@ void AnimationPropertyGroup::merge(const AnimationPropertyGroup& other) { _firstFrame = _animationLoop->getFirstFrame(); _lastFrame = _animationLoop->getLastFrame(); _hold = _animationLoop->getHold(); + _allowTranslation = getAllowTranslation(); } else { COPY_PROPERTY_IF_CHANGED(fps); COPY_PROPERTY_IF_CHANGED(currentFrame); @@ -101,7 +102,9 @@ void AnimationPropertyGroup::merge(const AnimationPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(firstFrame); COPY_PROPERTY_IF_CHANGED(lastFrame); COPY_PROPERTY_IF_CHANGED(hold); + COPY_PROPERTY_IF_CHANGED(allowTranslation); } + } void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) { @@ -116,7 +119,7 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) { float lastFrame = _animationLoop ? _animationLoop->getLastFrame() : getLastFrame(); bool loop = _animationLoop ? _animationLoop->getLoop() : getLoop(); bool hold = _animationLoop ? _animationLoop->getHold() : getHold(); - bool allowTranslation = _animationLoop ? _animationLoop->getAllowTranslation() : getAllowTranslation(); + bool allowTranslation = getAllowTranslation(); QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); QJsonObject settingsAsJsonObject = settingsAsJson.object(); @@ -163,7 +166,6 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) { _animationLoop->setLastFrame(lastFrame); _animationLoop->setLoop(loop); _animationLoop->setHold(hold); - _animationLoop->setAllowTranslation(allowTranslation); } else { setFPS(fps); setCurrentFrame(currentFrame); @@ -172,8 +174,9 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) { setLastFrame(lastFrame); setLoop(loop); setHold(hold); - setAllowTranslation(allowTranslation); } + + setAllowTranslation(allowTranslation); } @@ -215,7 +218,6 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold()); - APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, _animationLoop->getAllowTranslation()); } else { APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame()); @@ -224,9 +226,10 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold()); - APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, getAllowTranslation()); } + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, getAllowTranslation()); + return true; } @@ -238,6 +241,7 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF bool somethingChanged = false; READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setURL); + READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation); if (_animationLoop) { READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, _animationLoop->setFPS); @@ -247,7 +251,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold); - READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, _animationLoop->setAllowTranslation); } else { READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame); @@ -256,7 +259,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); - READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation); } DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_URL, URL); @@ -268,7 +270,7 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_LAST_FRAME, LastFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_HOLD, Hold); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_ALLOW_TRANSLATION, AllowTranslation); - + processedBytes += bytesRead; Q_UNUSED(somethingChanged); @@ -305,6 +307,8 @@ EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const { void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, URL, getURL); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, AllowTranslation, getAllowTranslation); + if (_animationLoop) { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FPS, _animationLoop->getFPS); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, CurrentFrame, _animationLoop->getCurrentFrame); @@ -313,7 +317,6 @@ void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) con COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, _animationLoop->getFirstFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, _animationLoop->getLastFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, _animationLoop->getHold); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, AllowTranslation, _animationLoop->getAllowTranslation); } else { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FPS, getFPS); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, CurrentFrame, getCurrentFrame); @@ -322,7 +325,6 @@ void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) con COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FirstFrame, getFirstFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, LastFrame, getLastFrame); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Hold, getHold); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, AllowTranslation, getAllowTranslation); } } @@ -330,6 +332,8 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie bool somethingChanged = false; SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, URL, url, setURL); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, AllowTranslation, allowTranslation, setAllowTranslation); + if (_animationLoop) { SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FPS, fps, _animationLoop->setFPS); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, CurrentFrame, currentFrame, _animationLoop->setCurrentFrame); @@ -338,7 +342,6 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, _animationLoop->setFirstFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, _animationLoop->setLastFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, _animationLoop->setHold); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, AllowTranslation, allowTranslation, _animationLoop->setAllowTranslation); } else { SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FPS, fps, setFPS); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, CurrentFrame, currentFrame, setCurrentFrame); @@ -347,7 +350,6 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FirstFrame, firstFrame, setFirstFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, LastFrame, lastFrame, setLastFrame); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Hold, hold, setHold); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, AllowTranslation, allowTranslation, setAllowTranslation); } return somethingChanged; @@ -380,6 +382,8 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, getURL()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, getAllowTranslation()); + if (_animationLoop) { APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, _animationLoop->getFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, _animationLoop->getCurrentFrame()); @@ -388,7 +392,6 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, _animationLoop->getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, _animationLoop->getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, _animationLoop->getHold()); - APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, _animationLoop->getAllowTranslation()); } else { APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame()); @@ -397,7 +400,6 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, getFirstFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, getLastFrame()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getHold()); - APPEND_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, getAllowTranslation()); } } @@ -410,6 +412,7 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setURL); + READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation); if (_animationLoop) { // apply new properties to our associated AnimationLoop @@ -420,7 +423,6 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, _animationLoop->setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, _animationLoop->setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, _animationLoop->setHold); - READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, _animationLoop->setAllowTranslation); } else { READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame); @@ -429,7 +431,6 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char READ_ENTITY_PROPERTY(PROP_ANIMATION_FIRST_FRAME, float, setFirstFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_LAST_FRAME, float, setLastFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); - READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation); } return bytesRead; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 9daee95655..4dcd01b8cb 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -152,7 +152,7 @@ protected: bool isAnimatingSomething() const; rgbColor _color; - QString _modelURL; + QString _modelURL; QUrl _parsedModelURL; QString _compoundShapeURL; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 6d4f586c52..cd313dbd05 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1682,8 +1682,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS int newIndex = it.value(); // remember vertices with at least 1/4 weight - const float EXPANSION_WEIGHT_THRESHOLD = 0.99f; - if (weight > EXPANSION_WEIGHT_THRESHOLD) { + const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; + if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); points.push_back(extractTranslation(vertexTransform) * clusterScale); @@ -1788,6 +1788,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS avgPoint += points[j]; } avgPoint /= (float)points.size(); + joint.shapeInfo.avgPoint = avgPoint; // compute a k-Dop bounding volume for (uint32_t j = 0; j < cardinalDirections.size(); ++j) { @@ -1803,8 +1804,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } joint.shapeInfo.points.push_back(avgPoint + maxDot * cardinalDirections[j]); + joint.shapeInfo.dots.push_back(maxDot); joint.shapeInfo.points.push_back(avgPoint + minDot * cardinalDirections[j]); + joint.shapeInfo.dots.push_back(-minDot); } + generateBoundryLinesForDop14(joint.shapeInfo.dots, joint.shapeInfo.avgPoint, joint.shapeInfo.debugLines); } } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index f73088e7a1..170bbbf366 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -56,7 +56,10 @@ public: struct FBXJointShapeInfo { // same units and frame as FBXJoint.translation - QVector points; + glm::vec3 avgPoint; + std::vector dots; + std::vector points; + std::vector debugLines; }; /// A single joint (transformation node) extracted from an FBX document. diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index eac74fbdf9..f138244fa2 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -455,7 +455,7 @@ void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { } updatePipeline(); - glUniform1f( + glUniform1i( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._int); (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp index ffc8140b25..0c1b6880cb 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp @@ -485,9 +485,15 @@ void GLBackend::makeProgramBindings(ShaderObject& shaderObject) { glBindAttribLocation(glprogram, gpu::Stream::TANGENT, "inTangent"); } - loc = glGetAttribLocation(glprogram, "inTexCoord1"); - if (loc >= 0 && loc != gpu::Stream::TEXCOORD1) { - glBindAttribLocation(glprogram, gpu::Stream::TEXCOORD1, "inTexCoord1"); + char attribName[] = "inTexCoordn"; + for (auto i = 0; i < 4; i++) { + auto streamId = gpu::Stream::TEXCOORD1 + i; + + attribName[strlen(attribName) - 1] = '1' + i; + loc = glGetAttribLocation(glprogram, attribName); + if (loc >= 0 && loc != streamId) { + glBindAttribLocation(glprogram, streamId, attribName); + } } loc = glGetAttribLocation(glprogram, "inSkinClusterIndex"); diff --git a/libraries/gpu/src/gpu/Inputs.slh b/libraries/gpu/src/gpu/Inputs.slh index 843d1059f2..109762136e 100644 --- a/libraries/gpu/src/gpu/Inputs.slh +++ b/libraries/gpu/src/gpu/Inputs.slh @@ -18,4 +18,7 @@ layout(location = 4) in vec4 inTangent; layout(location = 5) in ivec4 inSkinClusterIndex; layout(location = 6) in vec4 inSkinClusterWeight; layout(location = 7) in vec4 inTexCoord1; +layout(location = 8) in vec4 inTexCoord2; +layout(location = 9) in vec4 inTexCoord3; +layout(location = 10) in vec4 inTexCoord4; <@endif@> diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index cdb972d8bf..427af1e78d 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -36,8 +36,14 @@ const ElementArray& getDefaultElements() { //SKIN_CLUSTER_WEIGHT = 6, Element::VEC4F_XYZW, //TEXCOORD1 = 7, - Element::VEC2F_UV - }}; + Element::VEC2F_UV, + //TEXCOORD2 = 7, + Element::VEC4F_XYZW, + //TEXCOORD3 = 7, + Element::VEC4F_XYZW, + //TEXCOORD4 = 7, + Element::VEC4F_XYZW + }}; return defaultElements; } diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 0642131edf..336e34ecb4 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -38,8 +38,11 @@ public: SKIN_CLUSTER_INDEX = 5, SKIN_CLUSTER_WEIGHT = 6, TEXCOORD1 = 7, - NUM_INPUT_SLOTS = TEXCOORD1 + 1, + TEXCOORD2 = 8, + TEXCOORD3 = 9, + TEXCOORD4 = 10, + NUM_INPUT_SLOTS, DRAW_CALL_INFO = 15, // Reserve last input slot for draw call infos }; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index e03ec5e771..1d094d923c 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -167,6 +167,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { newPermissions.can(NodePermissions::Permission::canKick)) { emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick)); } + if (originalPermissions.can(NodePermissions::Permission::canReplaceDomainContent) != + newPermissions.can(NodePermissions::Permission::canReplaceDomainContent)) { + emit canReplaceContentChanged(_permissions.can(NodePermissions::Permission::canReplaceDomainContent)); + } } void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 5d602cc0c0..f4ec47636b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -115,7 +115,8 @@ public: bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } - + bool getThisNodeCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); } + quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort); @@ -329,6 +330,7 @@ signals: void canRezTmpChanged(bool canRezTmp); void canWriteAssetsChanged(bool canWriteAssets); void canKickChanged(bool canKick); + void canReplaceContentChanged(bool canReplaceContent); protected slots: void connectedForLocalSocketTest(); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 4d09f077bd..4451ba4abe 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -74,6 +74,7 @@ public: bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } + bool getCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); } void parseIgnoreRequestMessage(QSharedPointer message); void addIgnoredNode(const QUuid& otherNodeID); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index a1d4fc182e..cc5df515aa 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -45,6 +45,7 @@ NodePermissions::NodePermissions(QMap perms) { permissions |= perms["id_can_connect_past_max_capacity"].toBool() ? Permission::canConnectPastMaxCapacity : Permission::none; permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none; + permissions |= perms["id_can_replace_content"].toBool() ? Permission::canReplaceDomainContent : Permission::none; } QVariant NodePermissions::toVariant(QHash groupRanks) { @@ -65,6 +66,7 @@ QVariant NodePermissions::toVariant(QHash groupRanks) { values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer); values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity); values["id_can_kick"] = can(Permission::canKick); + values["id_can_replace_content"] = can(Permission::canReplaceDomainContent); return QVariant(values); } @@ -128,6 +130,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) { if (perms.can(NodePermissions::Permission::canKick)) { debug << " kick"; } + if (perms.can(NodePermissions::Permission::canReplaceDomainContent)) { + debug << " can_replace_content"; + } debug.nospace() << "]"; return debug.nospace(); } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6fa005e360..129d7e5c08 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -77,7 +77,8 @@ public: canRezTemporaryEntities = 8, canWriteToAssetServer = 16, canConnectPastMaxCapacity = 32, - canKick = 64 + canKick = 64, + canReplaceDomainContent = 128 }; Q_DECLARE_FLAGS(Permissions, Permission) Permissions permissions; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e56666607f..0e5475f570 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -121,6 +121,7 @@ public: ReplicatedAvatarIdentity, ReplicatedKillAvatar, ReplicatedBulkAvatarData, + OctreeFileReplacementFromUrl, NUM_PACKET_TYPE }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9e18ee534d..d7531e66a7 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -183,6 +183,7 @@ public: // Fetch the most recently displayed image as a QImage virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0; + virtual QImage getSecondaryCameraScreenshot() const = 0; // will query the underlying hmd api to compute the most recent head pose virtual bool beginFrameRender(uint32_t frameIndex) { return true; } diff --git a/libraries/render-utils/src/Fade.slh b/libraries/render-utils/src/Fade.slh new file mode 100644 index 0000000000..b85a824ca3 --- /dev/null +++ b/libraries/render-utils/src/Fade.slh @@ -0,0 +1,169 @@ +// Generated on <$_SCRIBE_DATE$> +// +// Created by Olivier Prat on 04/12/17. +// 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 +// +<@if not FADE_SLH@> +<@def FADE_SLH@> + +<@func declareFadeFragmentCommon()@> + + +#define CATEGORY_COUNT 5 + +<@include Fade_shared.slh@> + +layout(std140) uniform fadeParametersBuffer { + FadeParameters fadeParameters[CATEGORY_COUNT]; +}; +uniform sampler2D fadeMaskMap; + +struct FadeObjectParams { + int category; + float threshold; + vec3 noiseOffset; + vec3 baseOffset; + vec3 baseInvSize; +}; + +vec2 hash2D(vec3 position) { + return position.xy* vec2(0.1677, 0.221765) + position.z*0.561; +} + +float noise3D(vec3 position) { + float n = textureLod(fadeMaskMap, hash2D(position), 0).r; + return pow(n, 1.0/2.2); // Remove sRGB. Need to fix this later directly in the texture +} + +float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) { + // Do tri-linear interpolation + vec3 noisePosition = position * fadeParameters[params.category]._noiseInvSizeAndLevel.xyz + params.noiseOffset; + vec3 noisePositionFloored = floor(noisePosition); + vec3 noisePositionFraction = fract(noisePosition); + + noisePositionFraction = noisePositionFraction*noisePositionFraction*(3 - 2*noisePositionFraction); + + float noiseLowXLowYLowZ = noise3D(noisePositionFloored); + float noiseLowXHighYLowZ = noise3D(noisePositionFloored+vec3(0,1,0)); + float noiseHighXLowYLowZ = noise3D(noisePositionFloored+vec3(1,0,0)); + float noiseHighXHighYLowZ = noise3D(noisePositionFloored+vec3(1,1,0)); + float noiseLowXLowYHighZ = noise3D(noisePositionFloored+vec3(0,0,1)); + float noiseLowXHighYHighZ = noise3D(noisePositionFloored+vec3(0,1,1)); + float noiseHighXLowYHighZ = noise3D(noisePositionFloored+vec3(1,0,1)); + float noiseHighXHighYHighZ = noise3D(noisePositionFloored+vec3(1,1,1)); + vec4 maskLowZ = vec4(noiseLowXLowYLowZ, noiseLowXHighYLowZ, noiseHighXLowYLowZ, noiseHighXHighYLowZ); + vec4 maskHighZ = vec4(noiseLowXLowYHighZ, noiseLowXHighYHighZ, noiseHighXLowYHighZ, noiseHighXHighYHighZ); + vec4 maskXY = mix(maskLowZ, maskHighZ, noisePositionFraction.z); + vec2 maskY = mix(maskXY.xy, maskXY.zw, noisePositionFraction.x); + + float noise = mix(maskY.x, maskY.y, noisePositionFraction.y); + noise -= 0.5; // Center on value 0 + return noise * fadeParameters[params.category]._noiseInvSizeAndLevel.w; +} + +float evalFadeBaseGradient(FadeObjectParams params, vec3 position) { + float gradient = length((position - params.baseOffset) * params.baseInvSize.xyz); + gradient = gradient-0.5; // Center on value 0.5 + gradient *= fadeParameters[params.category]._baseLevel; + return gradient; +} + +float evalFadeGradient(FadeObjectParams params, vec3 position) { + float baseGradient = evalFadeBaseGradient(params, position); + float noiseGradient = evalFadeNoiseGradient(params, position); + float gradient = noiseGradient+baseGradient+0.5; + + return gradient; +} + +float evalFadeAlpha(FadeObjectParams params, vec3 position) { + return evalFadeGradient(params, position)-params.threshold; +} + +void applyFadeClip(FadeObjectParams params, vec3 position) { + if (evalFadeAlpha(params, position) < 0) { + discard; + } +} + +void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) { + float alpha = evalFadeAlpha(params, position); + if (fadeParameters[params.category]._isInverted!=0) { + alpha = -alpha; + } + + if (alpha < 0) { + discard; + } + + float edgeMask = alpha * fadeParameters[params.category]._edgeWidthInvWidth.y; + float edgeAlpha = 1.0-clamp(edgeMask, 0, 1); + + edgeMask = step(edgeMask, 1.f); + edgeAlpha *= edgeAlpha; // Square to have a nice ease out + vec4 color = mix(fadeParameters[params.category]._innerEdgeColor, fadeParameters[params.category]._outerEdgeColor, edgeAlpha); + emissive = color.rgb * edgeMask * color.a; +} + +<@endfunc@> + +<@func declareFadeFragmentUniform()@> + +uniform int fadeCategory; +uniform vec3 fadeNoiseOffset; +uniform vec3 fadeBaseOffset; +uniform vec3 fadeBaseInvSize; +uniform float fadeThreshold; + +<@endfunc@> + +<@func fetchFadeObjectParams(fadeParams)@> + <$fadeParams$>.category = fadeCategory; + <$fadeParams$>.threshold = fadeThreshold; + <$fadeParams$>.noiseOffset = fadeNoiseOffset; + <$fadeParams$>.baseOffset = fadeBaseOffset; + <$fadeParams$>.baseInvSize = fadeBaseInvSize; +<@endfunc@> + +<@func declareFadeFragmentVertexInput()@> + +in vec4 _fadeData1; +in vec4 _fadeData2; +in vec4 _fadeData3; + +<@endfunc@> + +<@func fetchFadeObjectParamsInstanced(fadeParams)@> + <$fadeParams$>.category = int(_fadeData1.w); + <$fadeParams$>.threshold = _fadeData2.w; + <$fadeParams$>.noiseOffset = _fadeData1.xyz; + <$fadeParams$>.baseOffset = _fadeData2.xyz; + <$fadeParams$>.baseInvSize = _fadeData3.xyz; +<@endfunc@> + +<@func declareFadeFragment()@> +<$declareFadeFragmentCommon()$> +<$declareFadeFragmentUniform()$> +<@endfunc@> + +<@func declareFadeFragmentInstanced()@> +<$declareFadeFragmentCommon()$> +<$declareFadeFragmentVertexInput()$> +<@endfunc@> + +<@func declareFadeVertexInstanced()@> +out vec4 _fadeData1; +out vec4 _fadeData2; +out vec4 _fadeData3; +<@endfunc@> + +<@func passThroughFadeObjectParams()@> + _fadeData1 = inTexCoord2; + _fadeData2 = inTexCoord3; + _fadeData3 = inTexCoord4; +<@endfunc@> + +<@endif@> \ No newline at end of file diff --git a/libraries/render-utils/src/FadeEffect.cpp b/libraries/render-utils/src/FadeEffect.cpp new file mode 100644 index 0000000000..c94fe717f1 --- /dev/null +++ b/libraries/render-utils/src/FadeEffect.cpp @@ -0,0 +1,104 @@ +// +// FadeEffect.cpp + +// Created by Olivier Prat on 17/07/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 +// +#include "FadeEffect.h" +#include "FadeEffectJobs.h" +#include "TextureCache.h" + +#include "render/TransitionStage.h" + +#include + +FadeEffect::FadeEffect() { + auto texturePath = PathUtils::resourcesPath() + "images/fadeMask.png"; + _maskMap = DependencyManager::get()->getImageTexture(texturePath, image::TextureUsage::STRICT_TEXTURE); +} + +void FadeEffect::build(render::Task::TaskConcept& task, const task::Varying& editableItems) { + auto editedFadeCategory = task.addJob("Fade"); + auto& fadeJob = task._jobs.back(); + _configurations = fadeJob.get().getConfigurationBuffer(); + + const auto fadeEditInput = FadeEditJob::Input(editableItems, editedFadeCategory).asVarying(); + task.addJob("FadeEdit", fadeEditInput); +} + +render::ShapePipeline::BatchSetter FadeEffect::getBatchSetter() const { + return [this](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args*) { + auto program = shapePipeline.pipeline->getProgram(); + auto maskMapLocation = program->getTextures().findLocation("fadeMaskMap"); + auto bufferLocation = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + batch.setResourceTexture(maskMapLocation, _maskMap); + batch.setUniformBuffer(bufferLocation, _configurations); + }; +} + +render::ShapePipeline::ItemSetter FadeEffect::getItemUniformSetter() const { + return [](const render::ShapePipeline& shapePipeline, render::Args* args, const render::Item& item) { + if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { + auto scene = args->_scene; + auto batch = args->_batch; + auto transitionStage = scene->getStage(render::TransitionStage::getName()); + auto& transitionState = transitionStage->getTransition(item.getTransitionId()); + auto program = shapePipeline.pipeline->getProgram(); + auto& uniforms = program->getUniforms(); + auto fadeNoiseOffsetLocation = uniforms.findLocation("fadeNoiseOffset"); + auto fadeBaseOffsetLocation = uniforms.findLocation("fadeBaseOffset"); + auto fadeBaseInvSizeLocation = uniforms.findLocation("fadeBaseInvSize"); + auto fadeThresholdLocation = uniforms.findLocation("fadeThreshold"); + auto fadeCategoryLocation = uniforms.findLocation("fadeCategory"); + + if (fadeNoiseOffsetLocation >= 0 || fadeBaseInvSizeLocation >= 0 || fadeBaseOffsetLocation >= 0 || fadeThresholdLocation >= 0 || fadeCategoryLocation >= 0) { + const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType]; + + batch->_glUniform1i(fadeCategoryLocation, fadeCategory); + batch->_glUniform1f(fadeThresholdLocation, transitionState.threshold); + batch->_glUniform3f(fadeNoiseOffsetLocation, transitionState.noiseOffset.x, transitionState.noiseOffset.y, transitionState.noiseOffset.z); + batch->_glUniform3f(fadeBaseOffsetLocation, transitionState.baseOffset.x, transitionState.baseOffset.y, transitionState.baseOffset.z); + batch->_glUniform3f(fadeBaseInvSizeLocation, transitionState.baseInvSize.x, transitionState.baseInvSize.y, transitionState.baseInvSize.z); + } + } + }; +} + +render::ShapePipeline::ItemSetter FadeEffect::getItemStoredSetter() { + return [this](const render::ShapePipeline& shapePipeline, render::Args* args, const render::Item& item) { + if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { + auto scene = args->_scene; + auto transitionStage = scene->getStage(render::TransitionStage::getName()); + auto& transitionState = transitionStage->getTransition(item.getTransitionId()); + const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType]; + + _lastCategory = fadeCategory; + _lastThreshold = transitionState.threshold; + _lastNoiseOffset = transitionState.noiseOffset; + _lastBaseOffset = transitionState.baseOffset; + _lastBaseInvSize = transitionState.baseInvSize; + } + }; +} + +void FadeEffect::packToAttributes(const int category, const float threshold, const glm::vec3& noiseOffset, + const glm::vec3& baseOffset, const glm::vec3& baseInvSize, + glm::vec4& packedData1, glm::vec4& packedData2, glm::vec4& packedData3) { + packedData1.x = noiseOffset.x; + packedData1.y = noiseOffset.y; + packedData1.z = noiseOffset.z; + packedData1.w = (float)(category+0.1f); // GLSL hack so that casting back from float to int in fragment shader returns the correct value. + + packedData2.x = baseOffset.x; + packedData2.y = baseOffset.y; + packedData2.z = baseOffset.z; + packedData2.w = threshold; + + packedData3.x = baseInvSize.x; + packedData3.y = baseInvSize.y; + packedData3.z = baseInvSize.z; + packedData3.w = 0.f; +} diff --git a/libraries/render-utils/src/FadeEffect.h b/libraries/render-utils/src/FadeEffect.h new file mode 100644 index 0000000000..4b4e401332 --- /dev/null +++ b/libraries/render-utils/src/FadeEffect.h @@ -0,0 +1,54 @@ +// +// FadeEffect.h + +// Created by Olivier Prat on 17/07/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_render_utils_FadeEffect_h +#define hifi_render_utils_FadeEffect_h + +#include +#include + +class FadeEffect : public Dependency { + SINGLETON_DEPENDENCY; + +public: + + void build(render::Task::TaskConcept& task, const task::Varying& editableItems); + + render::ShapePipeline::BatchSetter getBatchSetter() const; + render::ShapePipeline::ItemSetter getItemUniformSetter() const; + render::ShapePipeline::ItemSetter getItemStoredSetter(); + + int getLastCategory() const { return _lastCategory; } + float getLastThreshold() const { return _lastThreshold; } + const glm::vec3& getLastNoiseOffset() const { return _lastNoiseOffset; } + const glm::vec3& getLastBaseOffset() const { return _lastBaseOffset; } + const glm::vec3& getLastBaseInvSize() const { return _lastBaseInvSize; } + + static void packToAttributes(const int category, const float threshold, const glm::vec3& noiseOffset, + const glm::vec3& baseOffset, const glm::vec3& baseInvSize, + glm::vec4& packedData1, glm::vec4& packedData2, glm::vec4& packedData3); + +private: + + gpu::BufferView _configurations; + gpu::TexturePointer _maskMap; + + // The last fade set through the stored item setter + int _lastCategory { 0 }; + float _lastThreshold { 0.f }; + glm::vec3 _lastNoiseOffset { 0.f, 0.f, 0.f }; + glm::vec3 _lastBaseOffset { 0.f, 0.f, 0.f }; + glm::vec3 _lastBaseInvSize { 1.f, 1.f, 1.f }; + + explicit FadeEffect(); + virtual ~FadeEffect() { } + +}; +#endif // hifi_render_utils_FadeEffect_h diff --git a/libraries/render-utils/src/FadeEffectJobs.cpp b/libraries/render-utils/src/FadeEffectJobs.cpp new file mode 100644 index 0000000000..90459cf0e6 --- /dev/null +++ b/libraries/render-utils/src/FadeEffectJobs.cpp @@ -0,0 +1,733 @@ +// +// FadeEffectJobs.cpp + +// Created by Olivier Prat on 07/07/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 +// +#include "FadeEffectJobs.h" +#include "render/Logging.h" +#include "render/TransitionStage.h" + +#include +#include +#include + +#include + +#include + +#define FADE_MIN_SCALE 0.001 +#define FADE_MAX_SCALE 10000.0 +#define FADE_MAX_SPEED 50.f + +inline float parameterToValuePow(float parameter, const double minValue, const double maxOverMinValue) { + return (float)(minValue * pow(maxOverMinValue, double(parameter))); +} + +inline float valueToParameterPow(float value, const double minValue, const double maxOverMinValue) { + return (float)(log(double(value) / minValue) / log(maxOverMinValue)); +} + +void FadeEditJob::configure(const Config& config) { + _isEditEnabled = config.editFade; +} + +void FadeEditJob::run(const render::RenderContextPointer& renderContext, const FadeEditJob::Input& inputs) { + auto scene = renderContext->_scene; + + if (_isEditEnabled) { + float minIsectDistance = std::numeric_limits::max(); + auto& itemBounds = inputs.get0(); + auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance); + render::Transaction transaction; + bool hasTransaction{ false }; + + if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've changed edited item + hasTransaction = true; + transaction.removeTransitionFromItem(_editedItem); + } + _editedItem = editedItem; + + if (render::Item::isValidID(_editedItem)) { + static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = { + render::Transition::ELEMENT_ENTER_DOMAIN, + render::Transition::BUBBLE_ISECT_OWNER, + render::Transition::BUBBLE_ISECT_TRESPASSER, + render::Transition::USER_ENTER_DOMAIN, + render::Transition::AVATAR_CHANGE + }; + + auto transitionType = categoryToTransition[inputs.get1()]; + + transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) { + if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) { + // Relaunch transition + render::Transaction transaction; + transaction.addTransitionToItem(id, transitionType); + scene->enqueueTransaction(transaction); + } + }); + hasTransaction = true; + } + + if (hasTransaction) { + scene->enqueueTransaction(transaction); + } + } + else if (render::Item::isValidID(_editedItem)) { + // Remove transition from previously edited item as we've disabled fade edition + render::Transaction transaction; + transaction.removeTransitionFromItem(_editedItem); + scene->enqueueTransaction(transaction); + _editedItem = render::Item::INVALID_ITEM_ID; + } +} + +render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const { + const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition(); + const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection(); + BoxFace face; + glm::vec3 normal; + float isectDistance; + render::ItemID nearestItem = render::Item::INVALID_ITEM_ID; + const float minDistance = 1.f; + const float maxDistance = 50.f; + + for (const auto& itemBound : inputs) { + if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) { + auto& item = renderContext->_scene->getItem(itemBound.id); + if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance(FADE_CATEGORY_COUNT, value); + emit dirtyCategory(); + emit dirty(); +} + +void FadeConfig::setDuration(float value) { + events[editedCategory].duration = value; + emit dirty(); +} + +float FadeConfig::getDuration() const { + return events[editedCategory].duration; +} + +void FadeConfig::setBaseSizeX(float value) { + events[editedCategory].baseSize.x = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getBaseSizeX() const { + return valueToParameterPow(events[editedCategory].baseSize.x, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setBaseSizeY(float value) { + events[editedCategory].baseSize.y = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getBaseSizeY() const { + return valueToParameterPow(events[editedCategory].baseSize.y, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setBaseSizeZ(float value) { + events[editedCategory].baseSize.z = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getBaseSizeZ() const { + return valueToParameterPow(events[editedCategory].baseSize.z, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setBaseLevel(float value) { + events[editedCategory].baseLevel = value; + emit dirty(); +} + +void FadeConfig::setInverted(bool value) { + events[editedCategory].isInverted = value; + emit dirty(); +} + +bool FadeConfig::isInverted() const { + return events[editedCategory].isInverted; +} + +void FadeConfig::setNoiseSizeX(float value) { + events[editedCategory].noiseSize.x = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getNoiseSizeX() const { + return valueToParameterPow(events[editedCategory].noiseSize.x, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setNoiseSizeY(float value) { + events[editedCategory].noiseSize.y = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getNoiseSizeY() const { + return valueToParameterPow(events[editedCategory].noiseSize.y, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setNoiseSizeZ(float value) { + events[editedCategory].noiseSize.z = parameterToValuePow(value, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); + emit dirty(); +} + +float FadeConfig::getNoiseSizeZ() const { + return valueToParameterPow(events[editedCategory].noiseSize.z, FADE_MIN_SCALE, FADE_MAX_SCALE / FADE_MIN_SCALE); +} + +void FadeConfig::setNoiseLevel(float value) { + events[editedCategory].noiseLevel = value; + emit dirty(); +} + +void FadeConfig::setNoiseSpeedX(float value) { + events[editedCategory].noiseSpeed.x = powf(value, 3.f)*FADE_MAX_SPEED; + emit dirty(); +} + +float FadeConfig::getNoiseSpeedX() const { + return powf(events[editedCategory].noiseSpeed.x / FADE_MAX_SPEED, 1.f / 3.f); +} + +void FadeConfig::setNoiseSpeedY(float value) { + events[editedCategory].noiseSpeed.y = powf(value, 3.f)*FADE_MAX_SPEED; + emit dirty(); +} + +float FadeConfig::getNoiseSpeedY() const { + return powf(events[editedCategory].noiseSpeed.y / FADE_MAX_SPEED, 1.f / 3.f); +} + +void FadeConfig::setNoiseSpeedZ(float value) { + events[editedCategory].noiseSpeed.z = powf(value, 3.f)*FADE_MAX_SPEED; + emit dirty(); +} + +float FadeConfig::getNoiseSpeedZ() const { + return powf(events[editedCategory].noiseSpeed.z / FADE_MAX_SPEED, 1.f / 3.f); +} + +void FadeConfig::setEdgeWidth(float value) { + events[editedCategory].edgeWidth = value * value; + emit dirty(); +} + +float FadeConfig::getEdgeWidth() const { + return sqrtf(events[editedCategory].edgeWidth); +} + +void FadeConfig::setEdgeInnerColorR(float value) { + events[editedCategory].edgeInnerColor.r = value; + emit dirty(); +} + +void FadeConfig::setEdgeInnerColorG(float value) { + events[editedCategory].edgeInnerColor.g = value; + emit dirty(); +} + +void FadeConfig::setEdgeInnerColorB(float value) { + events[editedCategory].edgeInnerColor.b = value; + emit dirty(); +} + +void FadeConfig::setEdgeInnerIntensity(float value) { + events[editedCategory].edgeInnerColor.a = value; + emit dirty(); +} + +void FadeConfig::setEdgeOuterColorR(float value) { + events[editedCategory].edgeOuterColor.r = value; + emit dirty(); +} + +void FadeConfig::setEdgeOuterColorG(float value) { + events[editedCategory].edgeOuterColor.g = value; + emit dirty(); +} + +void FadeConfig::setEdgeOuterColorB(float value) { + events[editedCategory].edgeOuterColor.b = value; + emit dirty(); +} + +void FadeConfig::setEdgeOuterIntensity(float value) { + events[editedCategory].edgeOuterColor.a = value; + emit dirty(); +} + +void FadeConfig::setTiming(int value) { + assert(value < TIMING_COUNT); + events[editedCategory].timing = value; + emit dirty(); +} + +QString FadeConfig::eventNames[FADE_CATEGORY_COUNT] = { + "element_enter_leave_domain", + "bubble_isect_owner", + "bubble_isect_trespasser", + "user_enter_leave_domain", + "avatar_change", +}; + +void FadeConfig::save() const { + assert(editedCategory < FADE_CATEGORY_COUNT); + QJsonObject lProperties; + const QString configFile = "config/" + eventNames[editedCategory] + ".json"; + QUrl path(PathUtils::resourcesPath() + configFile); + QFile file(path.toString()); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + qWarning() << "Fade event configuration file " << path << " cannot be opened"; + } + else { + const auto& event = events[editedCategory]; + + lProperties["edgeInnerColor"] = QJsonArray{ event.edgeInnerColor.r, event.edgeInnerColor.g, event.edgeInnerColor.b, event.edgeInnerColor.a }; + lProperties["edgeOuterColor"] = QJsonArray{ event.edgeOuterColor.r, event.edgeOuterColor.g, event.edgeOuterColor.b, event.edgeOuterColor.a }; + lProperties["noiseSize"] = QJsonArray{ event.noiseSize.x, event.noiseSize.y, event.noiseSize.z }; + lProperties["noiseSpeed"] = QJsonArray{ event.noiseSpeed.x, event.noiseSpeed.y, event.noiseSpeed.z }; + lProperties["baseSize"] = QJsonArray{ event.baseSize.x, event.baseSize.y, event.baseSize.z }; + lProperties["noiseLevel"] = event.noiseLevel; + lProperties["baseLevel"] = event.baseLevel; + lProperties["duration"] = event.duration; + lProperties["edgeWidth"] = event.edgeWidth; + lProperties["timing"] = event.timing; + lProperties["isInverted"] = event.isInverted; + + file.write( QJsonDocument(lProperties).toJson() ); + file.close(); + } +} + +void FadeConfig::load() { + const QString configFile = "config/" + eventNames[editedCategory] + ".json"; + + QUrl path(PathUtils::resourcesPath() + configFile); + QFile file(path.toString()); + if (!file.exists()) { + qWarning() << "Fade event configuration file " << path << " does not exist"; + } + else if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Fade event configuration file " << path << " cannot be opened"; + } + else { + QString fileData = file.readAll(); + file.close(); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(fileData.toUtf8(), &error); + if (error.error == error.NoError) { + QJsonObject jsonObject = doc.object(); + QJsonValue value; + auto& event = events[editedCategory]; + + qCDebug(renderlogging) << "Fade event configuration file" << path << "loaded"; + + value = jsonObject["edgeInnerColor"]; + if (value.isArray()) { + QJsonArray data = value.toArray(); + + if (data.size() < 4) { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'edgeInnerColor' field. Expected array of size 4"; + } + else { + event.edgeInnerColor.r = (float)data.at(0).toDouble(); + event.edgeInnerColor.g = (float)data.at(1).toDouble(); + event.edgeInnerColor.b = (float)data.at(2).toDouble(); + event.edgeInnerColor.a = (float)data.at(3).toDouble(); + } + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'edgeInnerColor' field. Expected array of size 4"; + } + + value = jsonObject["edgeOuterColor"]; + if (value.isArray()) { + QJsonArray data = value.toArray(); + + if (data.size() < 4) { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'edgeOuterColor' field. Expected array of size 4"; + } + else { + event.edgeOuterColor.r = (float)data.at(0).toDouble(); + event.edgeOuterColor.g = (float)data.at(1).toDouble(); + event.edgeOuterColor.b = (float)data.at(2).toDouble(); + event.edgeOuterColor.a = (float)data.at(3).toDouble(); + } + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'edgeOuterColor' field. Expected array of size 4"; + } + + value = jsonObject["noiseSize"]; + if (value.isArray()) { + QJsonArray data = value.toArray(); + + if (data.size() < 3) { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'noiseSize' field. Expected array of size 3"; + } + else { + event.noiseSize.x = (float)data.at(0).toDouble(); + event.noiseSize.y = (float)data.at(1).toDouble(); + event.noiseSize.z = (float)data.at(2).toDouble(); + } + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'noiseSize' field. Expected array of size 3"; + } + + value = jsonObject["noiseSpeed"]; + if (value.isArray()) { + QJsonArray data = value.toArray(); + + if (data.size() < 3) { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'noiseSpeed' field. Expected array of size 3"; + } + else { + event.noiseSpeed.x = (float)data.at(0).toDouble(); + event.noiseSpeed.y = (float)data.at(1).toDouble(); + event.noiseSpeed.z = (float)data.at(2).toDouble(); + } + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'noiseSpeed' field. Expected array of size 3"; + } + + value = jsonObject["baseSize"]; + if (value.isArray()) { + QJsonArray data = value.toArray(); + + if (data.size() < 3) { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'baseSize' field. Expected array of size 3"; + } + else { + event.baseSize.x = (float)data.at(0).toDouble(); + event.baseSize.y = (float)data.at(1).toDouble(); + event.baseSize.z = (float)data.at(2).toDouble(); + } + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'baseSize' field. Expected array of size 3"; + } + + value = jsonObject["noiseLevel"]; + if (value.isDouble()) { + event.noiseLevel = (float)value.toDouble(); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'noiseLevel' field. Expected float value"; + } + + value = jsonObject["baseLevel"]; + if (value.isDouble()) { + event.baseLevel = (float)value.toDouble(); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'baseLevel' field. Expected float value"; + } + + value = jsonObject["duration"]; + if (value.isDouble()) { + event.duration = (float)value.toDouble(); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'duration' field. Expected float value"; + } + + value = jsonObject["edgeWidth"]; + if (value.isDouble()) { + event.edgeWidth = std::min(1.f, std::max(0.f, (float)value.toDouble())); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'edgeWidth' field. Expected float value"; + } + + value = jsonObject["timing"]; + if (value.isDouble()) { + event.timing = std::max(0, std::min(TIMING_COUNT - 1, value.toInt())); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'timing' field. Expected integer value"; + } + + value = jsonObject["isInverted"]; + if (value.isBool()) { + event.isInverted = value.toBool(); + } + else { + qWarning() << "Fade event configuration file " << path << " contains an invalid 'isInverted' field. Expected boolean value"; + } + + emit dirty(); + } + else { + qWarning() << "Fade event configuration file" << path << "failed to load:" << + error.errorString() << "at offset" << error.offset; + } + } +} + +FadeJob::FadeJob() { + _previousTime = usecTimestampNow(); +} + +void FadeJob::configure(const Config& config) { + auto& configurations = _configurations.edit(); + + for (auto i = 0; i < FADE_CATEGORY_COUNT; i++) { + auto& eventParameters = configurations.parameters[i]; + const auto& eventConfig = config.events[i]; + + eventParameters._baseLevel = eventConfig.baseLevel; + eventParameters._noiseInvSizeAndLevel.x = 1.f / eventConfig.noiseSize.x; + eventParameters._noiseInvSizeAndLevel.y = 1.f / eventConfig.noiseSize.y; + eventParameters._noiseInvSizeAndLevel.z = 1.f / eventConfig.noiseSize.z; + eventParameters._noiseInvSizeAndLevel.w = eventConfig.noiseLevel; + eventParameters._isInverted = eventConfig.isInverted & 1; + eventParameters._edgeWidthInvWidth.x = eventConfig.edgeWidth; + eventParameters._edgeWidthInvWidth.y = 1.f / eventParameters._edgeWidthInvWidth.x; + eventParameters._innerEdgeColor = eventConfig.edgeInnerColor; + eventParameters._outerEdgeColor = eventConfig.edgeOuterColor; + _thresholdScale[i] = 1.f + (eventParameters._edgeWidthInvWidth.x + std::max(0.f, (eventConfig.noiseLevel + eventConfig.baseLevel)*0.5f - 0.5f)); + } +} + +void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Output& output) { + Config* jobConfig = static_cast(renderContext->jobConfig.get()); + auto scene = renderContext->args->_scene; + auto transitionStage = scene->getStage(render::TransitionStage::getName()); + uint64_t now = usecTimestampNow(); + const double deltaTime = (int64_t(now) - int64_t(_previousTime)) / double(USECS_PER_SECOND); + render::Transaction transaction; + bool isFirstItem = true; + bool hasTransaction = false; + + output = (FadeCategory) jobConfig->editedCategory; + + // And now update fade effect + for (auto transitionId : *transitionStage) { + auto& state = transitionStage->editTransition(transitionId); +#ifdef DEBUG + auto& item = scene->getItem(state.itemId); + assert(item.getTransitionId() == transitionId); +#endif + if (update(*jobConfig, scene, transaction, state, deltaTime)) { + hasTransaction = true; + } + if (isFirstItem) { + jobConfig->setProperty("threshold", state.threshold); + isFirstItem = false; + } + } + _previousTime = now; + if (hasTransaction) { + scene->enqueueTransaction(transaction); + } +} + +const FadeCategory FadeJob::transitionToCategory[render::Transition::TYPE_COUNT] = { + FADE_ELEMENT_ENTER_LEAVE_DOMAIN, + FADE_ELEMENT_ENTER_LEAVE_DOMAIN, + FADE_BUBBLE_ISECT_OWNER, + FADE_BUBBLE_ISECT_TRESPASSER, + FADE_USER_ENTER_LEAVE_DOMAIN, + FADE_USER_ENTER_LEAVE_DOMAIN, + FADE_AVATAR_CHANGE +}; + +bool FadeJob::update(const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const { + const auto fadeCategory = transitionToCategory[transition.eventType]; + auto& eventConfig = config.events[fadeCategory]; + auto item = scene->getItemSafe(transition.itemId); + const double eventDuration = (double)eventConfig.duration; + const FadeConfig::Timing timing = (FadeConfig::Timing) eventConfig.timing; + + if (item.exist()) { + auto aabb = item.getBound(); + if (render::Item::isValidID(transition.boundItemId)) { + auto boundItem = scene->getItemSafe(transition.boundItemId); + if (boundItem.exist()) { + aabb = boundItem.getBound(); + } + } + auto& dimensions = aabb.getDimensions(); + + assert(timing < FadeConfig::TIMING_COUNT); + + transition.noiseOffset = aabb.calcCenter(); + transition.baseInvSize.x = 1.f / eventConfig.baseSize.x; + transition.baseInvSize.y = 1.f / eventConfig.baseSize.y; + transition.baseInvSize.z = 1.f / eventConfig.baseSize.z; + + switch (transition.eventType) { + case render::Transition::ELEMENT_ENTER_DOMAIN: + case render::Transition::ELEMENT_LEAVE_DOMAIN: + { + transition.threshold = computeElementEnterRatio(transition.time, eventDuration, timing); + transition.baseOffset = transition.noiseOffset; + transition.baseInvSize.x = 1.f / dimensions.x; + transition.baseInvSize.y = 1.f / dimensions.y; + transition.baseInvSize.z = 1.f / dimensions.z; + transition.isFinished += (transition.threshold >= 1.f) & 1; + if (transition.eventType == render::Transition::ELEMENT_ENTER_DOMAIN) { + transition.threshold = 1.f - transition.threshold; + } + } + break; + + case render::Transition::BUBBLE_ISECT_OWNER: + { + transition.threshold = 0.5f; + transition.baseOffset = transition.noiseOffset; + } + break; + + case render::Transition::BUBBLE_ISECT_TRESPASSER: + { + transition.threshold = 0.5f; + transition.baseOffset = transition.noiseOffset; + } + break; + + case render::Transition::USER_ENTER_DOMAIN: + case render::Transition::USER_LEAVE_DOMAIN: + { + transition.threshold = computeElementEnterRatio(transition.time, eventDuration, timing); + transition.baseOffset = transition.noiseOffset - dimensions.y / 2.f; + transition.baseInvSize.y = 1.f / dimensions.y; + transition.isFinished += (transition.threshold >= 1.f) & 1; + if (transition.eventType == render::Transition::USER_LEAVE_DOMAIN) { + transition.threshold = 1.f - transition.threshold; + } + } + break; + + case render::Transition::AVATAR_CHANGE: + break; + + default: + assert(false); + } + } + + transition.noiseOffset += eventConfig.noiseSpeed * (float)transition.time; + if (config.manualFade) { + transition.threshold = config.manualThreshold; + } + transition.threshold = std::max(0.f, std::min(1.f, transition.threshold)); + transition.threshold = (transition.threshold - 0.5f)*_thresholdScale[fadeCategory] + 0.5f; + transition.time += deltaTime; + + // If the transition is finished for more than a number of frames (here 1), garbage collect it. + if (transition.isFinished > 1) { + transaction.removeTransitionFromItem(transition.itemId); + return true; + } + return false; +} + +float FadeJob::computeElementEnterRatio(double time, const double period, FadeConfig::Timing timing) { + assert(period > 0.0); + float fadeAlpha = 1.0f; + const double INV_FADE_PERIOD = 1.0 / period; + double fraction = time * INV_FADE_PERIOD; + fraction = std::max(fraction, 0.0); + if (fraction < 1.0) { + switch (timing) { + default: + fadeAlpha = (float)fraction; + break; + case FadeConfig::EASE_IN: + fadeAlpha = (float)(fraction*fraction*fraction); + break; + case FadeConfig::EASE_OUT: + fadeAlpha = 1.f - (float)fraction; + fadeAlpha = 1.f- fadeAlpha*fadeAlpha*fadeAlpha; + break; + case FadeConfig::EASE_IN_OUT: + fadeAlpha = (float)(fraction*fraction*fraction*(fraction*(fraction * 6 - 15) + 10)); + break; + } + } + return fadeAlpha; +} diff --git a/libraries/render-utils/src/FadeEffectJobs.h b/libraries/render-utils/src/FadeEffectJobs.h new file mode 100644 index 0000000000..f827bb6e99 --- /dev/null +++ b/libraries/render-utils/src/FadeEffectJobs.h @@ -0,0 +1,248 @@ +// +// FadeEffectJobs.h + +// Created by Olivier Prat on 07/07/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_render_utils_FadeEffectJobs_h +#define hifi_render_utils_FadeEffectJobs_h + +#include "FadeEffect.h" + +#include +#include +#include +#include + +enum FadeCategory { + FADE_ELEMENT_ENTER_LEAVE_DOMAIN = 0, + FADE_BUBBLE_ISECT_OWNER, + FADE_BUBBLE_ISECT_TRESPASSER, + FADE_USER_ENTER_LEAVE_DOMAIN, + FADE_AVATAR_CHANGE, + + // Don't forget to modify Fade.slh to reflect the change in number of categories + FADE_CATEGORY_COUNT, +}; + +class FadeEditConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool editFade MEMBER editFade NOTIFY dirty) + +public: + + bool editFade{ false }; + +signals: + + void dirty(); +}; + +class FadeConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(int editedCategory MEMBER editedCategory WRITE setEditedCategory NOTIFY dirtyCategory) + Q_PROPERTY(float duration READ getDuration WRITE setDuration NOTIFY dirty) + Q_PROPERTY(float baseSizeX READ getBaseSizeX WRITE setBaseSizeX NOTIFY dirty) + Q_PROPERTY(float baseSizeY READ getBaseSizeY WRITE setBaseSizeY NOTIFY dirty) + Q_PROPERTY(float baseSizeZ READ getBaseSizeZ WRITE setBaseSizeZ NOTIFY dirty) + Q_PROPERTY(float baseLevel READ getBaseLevel WRITE setBaseLevel NOTIFY dirty) + Q_PROPERTY(bool _isInverted READ isInverted WRITE setInverted NOTIFY dirty) + Q_PROPERTY(float noiseSizeX READ getNoiseSizeX WRITE setNoiseSizeX NOTIFY dirty) + Q_PROPERTY(float noiseSizeY READ getNoiseSizeY WRITE setNoiseSizeY NOTIFY dirty) + Q_PROPERTY(float noiseSizeZ READ getNoiseSizeZ WRITE setNoiseSizeZ NOTIFY dirty) + Q_PROPERTY(float noiseLevel READ getNoiseLevel WRITE setNoiseLevel NOTIFY dirty) + Q_PROPERTY(float edgeWidth READ getEdgeWidth WRITE setEdgeWidth NOTIFY dirty) + Q_PROPERTY(float edgeInnerColorR READ getEdgeInnerColorR WRITE setEdgeInnerColorR NOTIFY dirty) + Q_PROPERTY(float edgeInnerColorG READ getEdgeInnerColorG WRITE setEdgeInnerColorG NOTIFY dirty) + Q_PROPERTY(float edgeInnerColorB READ getEdgeInnerColorB WRITE setEdgeInnerColorB NOTIFY dirty) + Q_PROPERTY(float edgeInnerIntensity READ getEdgeInnerIntensity WRITE setEdgeInnerIntensity NOTIFY dirty) + Q_PROPERTY(float edgeOuterColorR READ getEdgeOuterColorR WRITE setEdgeOuterColorR NOTIFY dirty) + Q_PROPERTY(float edgeOuterColorG READ getEdgeOuterColorG WRITE setEdgeOuterColorG NOTIFY dirty) + Q_PROPERTY(float edgeOuterColorB READ getEdgeOuterColorB WRITE setEdgeOuterColorB NOTIFY dirty) + Q_PROPERTY(float edgeOuterIntensity READ getEdgeOuterIntensity WRITE setEdgeOuterIntensity NOTIFY dirty) + Q_PROPERTY(int timing READ getTiming WRITE setTiming NOTIFY dirty) + Q_PROPERTY(float noiseSpeedX READ getNoiseSpeedX WRITE setNoiseSpeedX NOTIFY dirty) + Q_PROPERTY(float noiseSpeedY READ getNoiseSpeedY WRITE setNoiseSpeedY NOTIFY dirty) + Q_PROPERTY(float noiseSpeedZ READ getNoiseSpeedZ WRITE setNoiseSpeedZ NOTIFY dirty) + Q_PROPERTY(float threshold MEMBER threshold NOTIFY dirty) + Q_PROPERTY(bool manualFade MEMBER manualFade NOTIFY dirty) + Q_PROPERTY(float manualThreshold MEMBER manualThreshold NOTIFY dirty) + +public: + + enum Timing { + LINEAR, + EASE_IN, + EASE_OUT, + EASE_IN_OUT, + + TIMING_COUNT + }; + + FadeConfig(); + + void setEditedCategory(int value); + + void setDuration(float value); + float getDuration() const; + + void setBaseSizeX(float value); + float getBaseSizeX() const; + + void setBaseSizeY(float value); + float getBaseSizeY() const; + + void setBaseSizeZ(float value); + float getBaseSizeZ() const; + + void setBaseLevel(float value); + float getBaseLevel() const { return events[editedCategory].baseLevel; } + + void setInverted(bool value); + bool isInverted() const; + + void setNoiseSizeX(float value); + float getNoiseSizeX() const; + + void setNoiseSizeY(float value); + float getNoiseSizeY() const; + + void setNoiseSizeZ(float value); + float getNoiseSizeZ() const; + + void setNoiseLevel(float value); + float getNoiseLevel() const { return events[editedCategory].noiseLevel; } + + void setNoiseSpeedX(float value); + float getNoiseSpeedX() const; + + void setNoiseSpeedY(float value); + float getNoiseSpeedY() const; + + void setNoiseSpeedZ(float value); + float getNoiseSpeedZ() const; + + void setEdgeWidth(float value); + float getEdgeWidth() const; + + void setEdgeInnerColorR(float value); + float getEdgeInnerColorR() const { return events[editedCategory].edgeInnerColor.r; } + + void setEdgeInnerColorG(float value); + float getEdgeInnerColorG() const { return events[editedCategory].edgeInnerColor.g; } + + void setEdgeInnerColorB(float value); + float getEdgeInnerColorB() const { return events[editedCategory].edgeInnerColor.b; } + + void setEdgeInnerIntensity(float value); + float getEdgeInnerIntensity() const { return events[editedCategory].edgeInnerColor.a; } + + void setEdgeOuterColorR(float value); + float getEdgeOuterColorR() const { return events[editedCategory].edgeOuterColor.r; } + + void setEdgeOuterColorG(float value); + float getEdgeOuterColorG() const { return events[editedCategory].edgeOuterColor.g; } + + void setEdgeOuterColorB(float value); + float getEdgeOuterColorB() const { return events[editedCategory].edgeOuterColor.b; } + + void setEdgeOuterIntensity(float value); + float getEdgeOuterIntensity() const { return events[editedCategory].edgeOuterColor.a; } + + void setTiming(int value); + int getTiming() const { return events[editedCategory].timing; } + + struct Event { + glm::vec4 edgeInnerColor; + glm::vec4 edgeOuterColor; + glm::vec3 noiseSize; + glm::vec3 noiseSpeed; + glm::vec3 baseSize; + float noiseLevel; + float baseLevel; + float duration; + float edgeWidth; + int timing; + bool isInverted; + }; + + Event events[FADE_CATEGORY_COUNT]; + int editedCategory{ FADE_ELEMENT_ENTER_LEAVE_DOMAIN }; + float threshold{ 0.f }; + float manualThreshold{ 0.f }; + bool manualFade{ false }; + + Q_INVOKABLE void save() const; + Q_INVOKABLE void load(); + + static QString eventNames[FADE_CATEGORY_COUNT]; + +signals: + + void dirty(); + void dirtyCategory(); + +}; + +class FadeEditJob { + +public: + + using Config = FadeEditConfig; + using Input = render::VaryingSet2; + using JobModel = render::Job::ModelI; + + FadeEditJob() {} + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const FadeEditJob::Input& inputs); + +private: + + bool _isEditEnabled{ false }; + render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID }; + + render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const; +}; + +class FadeJob { + +public: + + static const FadeCategory transitionToCategory[render::Transition::TYPE_COUNT]; + + using Config = FadeConfig; + using Output = FadeCategory; + using JobModel = render::Job::ModelO; + + FadeJob(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, FadeJob::Output& output); + + gpu::BufferView getConfigurationBuffer() const { return _configurations; } + +private: + +#include "Fade_shared.slh" + + struct FadeConfiguration + { + FadeParameters parameters[FADE_CATEGORY_COUNT]; + }; + using FadeConfigurationBuffer = gpu::StructBuffer; + + FadeConfigurationBuffer _configurations; + float _thresholdScale[FADE_CATEGORY_COUNT]; + uint64_t _previousTime{ 0 }; + + bool update(const Config& config, const render::ScenePointer& scene, render::Transaction& transaction, render::Transition& transition, const double deltaTime) const; + static float computeElementEnterRatio(double time, const double period, FadeConfig::Timing timing); + +}; + +#endif // hifi_render_utils_FadeEffectJobs_h diff --git a/libraries/render-utils/src/Fade_shared.slh b/libraries/render-utils/src/Fade_shared.slh new file mode 100644 index 0000000000..7d1c0fb8bc --- /dev/null +++ b/libraries/render-utils/src/Fade_shared.slh @@ -0,0 +1,27 @@ +// glsl / C++ compatible source as interface for FadeEffect +#ifdef __cplusplus +# define VEC4 glm::vec4 +# define VEC2 glm::vec2 +# define FLOAT32 glm::float32 +# define INT32 glm::int32 +#else +# define VEC4 vec4 +# define VEC2 vec2 +# define FLOAT32 float +# define INT32 int +#endif + +struct FadeParameters +{ + VEC4 _noiseInvSizeAndLevel; + VEC4 _innerEdgeColor; + VEC4 _outerEdgeColor; + VEC2 _edgeWidthInvWidth; + FLOAT32 _baseLevel; + INT32 _isInverted; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 5f2acff16f..856b6dceab 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -25,6 +25,7 @@ #include "TextureCache.h" #include "RenderUtilsLogging.h" #include "StencilMaskPass.h" +#include "FadeEffect.h" #include "gpu/StandardShaderLib.h" @@ -37,6 +38,9 @@ #include "simple_vert.h" #include "simple_textured_frag.h" #include "simple_textured_unlit_frag.h" +#include "simple_fade_vert.h" +#include "simple_textured_fade_frag.h" +#include "simple_textured_unlit_fade_frag.h" #include "simple_opaque_web_browser_frag.h" #include "simple_opaque_web_browser_overlay_frag.h" #include "simple_transparent_web_browser_frag.h" @@ -56,9 +60,11 @@ static const int VERTICES_PER_TRIANGLE = 3; static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; +static const gpu::Element TEXCOORD4_ELEMENT { gpu::VEC4, gpu::FLOAT, gpu::XYZW }; static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT; static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT; +static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); @@ -447,34 +453,64 @@ gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() { return INSTANCED_SOLID_STREAM_FORMAT; } +gpu::Stream::FormatPointer& getInstancedSolidFadeStreamFormat() { + if (!INSTANCED_SOLID_FADE_STREAM_FORMAT) { + INSTANCED_SOLID_FADE_STREAM_FORMAT = std::make_shared(); // 1 for everyone + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD2, gpu::Stream::TEXCOORD2, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD3, gpu::Stream::TEXCOORD3, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + INSTANCED_SOLID_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD4, gpu::Stream::TEXCOORD4, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + } + return INSTANCED_SOLID_FADE_STREAM_FORMAT; +} + +QHash GeometryCache::_simplePrograms; + +gpu::ShaderPointer GeometryCache::_simpleShader; +gpu::ShaderPointer GeometryCache::_unlitShader; +gpu::ShaderPointer GeometryCache::_simpleFadeShader; +gpu::ShaderPointer GeometryCache::_unlitFadeShader; + render::ShapePipelinePointer GeometryCache::_simpleOpaquePipeline; render::ShapePipelinePointer GeometryCache::_simpleTransparentPipeline; +render::ShapePipelinePointer GeometryCache::_simpleOpaqueFadePipeline; +render::ShapePipelinePointer GeometryCache::_simpleTransparentFadePipeline; render::ShapePipelinePointer GeometryCache::_simpleWirePipeline; +uint8_t GeometryCache::CUSTOM_PIPELINE_NUMBER = 0; + +render::ShapePipelinePointer GeometryCache::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { + initializeShapePipelines(); + + if (key.isWireframe()) { + return _simpleWirePipeline; + } + + if (key.isFaded()) { + if (key.isTranslucent()) { + return _simpleTransparentFadePipeline; + } + else { + return _simpleOpaqueFadePipeline; + } + } + else { + if (key.isTranslucent()) { + return _simpleTransparentPipeline; + } + else { + return _simpleOpaquePipeline; + } + } +} + GeometryCache::GeometryCache() : _nextID(0) { + // Let's register its special shapePipeline factory: + registerShapePipeline(); buildShapes(); - GeometryCache::_simpleOpaquePipeline = - std::make_shared(getSimplePipeline(false, false, true, false), nullptr, - [](const render::ShapePipeline&, gpu::Batch& batch, RenderArgs* args) { - // Set the defaults needed for a simple program - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, - DependencyManager::get()->getWhiteTexture()); - }, - nullptr - ); - GeometryCache::_simpleTransparentPipeline = - std::make_shared(getSimplePipeline(false, true, true, false), nullptr, - [](const render::ShapePipeline&, gpu::Batch& batch, RenderArgs* args) { - // Set the defaults needed for a simple program - batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, - DependencyManager::get()->getWhiteTexture()); - }, - nullptr - ); - GeometryCache::_simpleWirePipeline = - std::make_shared(getSimplePipeline(false, false, true, true), nullptr, - [](const render::ShapePipeline&, gpu::Batch& batch, RenderArgs* args) {}, nullptr); } GeometryCache::~GeometryCache() { @@ -514,9 +550,46 @@ void GeometryCache::releaseID(int id) { _registeredGridBuffers.remove(id); } -void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) { - gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); - batch.setInputBuffer(gpu::Stream::COLOR, colorView); +void GeometryCache::initializeShapePipelines() { + if (!_simpleOpaquePipeline) { + _simpleOpaquePipeline = getShapePipeline(false, false, true, false); + _simpleTransparentPipeline = getShapePipeline(false, true, true, false); + _simpleOpaqueFadePipeline = getFadingShapePipeline(false, false, false, false, false); + _simpleTransparentFadePipeline = getFadingShapePipeline(false, true, false, false, false); + _simpleWirePipeline = getShapePipeline(false, false, true, true); + } +} + +render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool transparent, bool culled, + bool unlit, bool depthBias) { + + return std::make_shared(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false), nullptr, + [](const render::ShapePipeline& , gpu::Batch& batch, render::Args*) { + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); + } + ); +} + +render::ShapePipelinePointer GeometryCache::getFadingShapePipeline(bool textured, bool transparent, bool culled, + bool unlit, bool depthBias) { + auto fadeEffect = DependencyManager::get(); + auto fadeBatchSetter = fadeEffect->getBatchSetter(); + auto fadeItemSetter = fadeEffect->getItemStoredSetter(); + return std::make_shared(getSimplePipeline(textured, transparent, culled, unlit, depthBias, true), nullptr, + [fadeBatchSetter, fadeItemSetter](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args* args) { + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); + fadeBatchSetter(shapePipeline, batch, args); + }, + fadeItemSetter + ); +} + +render::ShapePipelinePointer GeometryCache::getOpaqueShapePipeline(bool isFading) { + return isFading ? _simpleOpaqueFadePipeline : _simpleOpaquePipeline; +} + +render::ShapePipelinePointer GeometryCache::getTransparentShapePipeline(bool isFading) { + return isFading ? _simpleTransparentFadePipeline : _simpleTransparentPipeline; } void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) { @@ -529,6 +602,11 @@ void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) { _shapes[shape].drawWire(batch); } +void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer) { + gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); + batch.setInputBuffer(gpu::Stream::COLOR, colorView); +} + void GeometryCache::renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer) { batch.setInputFormat(getInstancedSolidStreamFormat()); setupBatchInstance(batch, colorBuffer); @@ -541,6 +619,32 @@ void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, siz _shapes[shape].drawWireInstances(batch, count); } +void setupBatchFadeInstance(gpu::Batch& batch, gpu::BufferPointer colorBuffer, + gpu::BufferPointer fadeBuffer1, gpu::BufferPointer fadeBuffer2, gpu::BufferPointer fadeBuffer3) { + gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); + gpu::BufferView texCoord2View(fadeBuffer1, TEXCOORD4_ELEMENT); + gpu::BufferView texCoord3View(fadeBuffer2, TEXCOORD4_ELEMENT); + gpu::BufferView texCoord4View(fadeBuffer3, TEXCOORD4_ELEMENT); + batch.setInputBuffer(gpu::Stream::COLOR, colorView); + batch.setInputBuffer(gpu::Stream::TEXCOORD2, texCoord2View); + batch.setInputBuffer(gpu::Stream::TEXCOORD3, texCoord3View); + batch.setInputBuffer(gpu::Stream::TEXCOORD4, texCoord4View); +} + +void GeometryCache::renderFadeShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer, + gpu::BufferPointer& fadeBuffer1, gpu::BufferPointer& fadeBuffer2, gpu::BufferPointer& fadeBuffer3) { + batch.setInputFormat(getInstancedSolidFadeStreamFormat()); + setupBatchFadeInstance(batch, colorBuffer, fadeBuffer1, fadeBuffer2, fadeBuffer3); + _shapes[shape].drawInstances(batch, count); +} + +void GeometryCache::renderWireFadeShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer, + gpu::BufferPointer& fadeBuffer1, gpu::BufferPointer& fadeBuffer2, gpu::BufferPointer& fadeBuffer3) { + batch.setInputFormat(getInstancedSolidFadeStreamFormat()); + setupBatchFadeInstance(batch, colorBuffer, fadeBuffer1, fadeBuffer2, fadeBuffer3); + _shapes[shape].drawWireInstances(batch, count); +} + void GeometryCache::renderCube(gpu::Batch& batch) { renderShape(batch, Cube); } @@ -1639,8 +1743,12 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glowIntensity = 0.0f; #endif - if (glowIntensity <= 0) { - bindSimpleProgram(batch, false, false, false, true, false); + if (glowIntensity <= 0.0f) { + if (color.a >= 1.0f) { + bindSimpleProgram(batch, false, false, false, true, true); + } else { + bindSimpleProgram(batch, false, true, false, true, true); + } renderLine(batch, p1, p2, color, id); return; } @@ -1770,6 +1878,7 @@ public: IS_CULLED_FLAG, IS_UNLIT_FLAG, HAS_DEPTH_BIAS_FLAG, + IS_FADING_FLAG, NUM_FLAGS, }; @@ -1780,6 +1889,7 @@ public: IS_CULLED = (1 << IS_CULLED_FLAG), IS_UNLIT = (1 << IS_UNLIT_FLAG), HAS_DEPTH_BIAS = (1 << HAS_DEPTH_BIAS_FLAG), + IS_FADING = (1 << IS_FADING_FLAG), }; typedef unsigned short Flags; @@ -1790,6 +1900,7 @@ public: bool isCulled() const { return isFlag(IS_CULLED); } bool isUnlit() const { return isFlag(IS_UNLIT); } bool hasDepthBias() const { return isFlag(HAS_DEPTH_BIAS); } + bool isFading() const { return isFlag(IS_FADING); } Flags _flags = 0; short _spare = 0; @@ -1798,9 +1909,9 @@ public: SimpleProgramKey(bool textured = false, bool transparent = false, bool culled = true, - bool unlit = false, bool depthBias = false) { + bool unlit = false, bool depthBias = false, bool fading = false) { _flags = (textured ? IS_TEXTURED : 0) | (transparent ? IS_TRANSPARENT : 0) | (culled ? IS_CULLED : 0) | - (unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0); + (unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0); } SimpleProgramKey(int bitmask) : _flags(bitmask) {} @@ -1879,23 +1990,8 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra } } -gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) { - SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased }; - - // Compile the shaders - static std::once_flag once; - std::call_once(once, [&]() { - auto VS = gpu::Shader::createVertex(std::string(simple_vert)); - auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag)); - auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag)); - - _simpleShader = gpu::Shader::createProgram(VS, PS); - _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*_simpleShader, slotBindings); - gpu::Shader::makeProgram(*_unlitShader, slotBindings); - }); +gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading) { + SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading }; // If the pipeline already exists, return it auto it = _simplePrograms.find(config); @@ -1903,6 +1999,40 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp return it.value(); } + // Compile the shaders + if (!fading) { + static std::once_flag once; + std::call_once(once, [&]() { + auto VS = gpu::Shader::createVertex(std::string(simple_vert)); + auto PS = gpu::Shader::createPixel(std::string(simple_textured_frag)); + auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_frag)); + + _simpleShader = gpu::Shader::createProgram(VS, PS); + _unlitShader = gpu::Shader::createProgram(VS, PSUnlit); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); + gpu::Shader::makeProgram(*_simpleShader, slotBindings); + gpu::Shader::makeProgram(*_unlitShader, slotBindings); + }); + } else { + static std::once_flag once; + std::call_once(once, [&]() { + auto VS = gpu::Shader::createVertex(std::string(simple_fade_vert)); + auto PS = gpu::Shader::createPixel(std::string(simple_textured_fade_frag)); + auto PSUnlit = gpu::Shader::createPixel(std::string(simple_textured_unlit_fade_frag)); + + _simpleFadeShader = gpu::Shader::createProgram(VS, PS); + _unlitFadeShader = gpu::Shader::createProgram(VS, PSUnlit); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), render::ShapePipeline::Slot::MAP::ALBEDO)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), render::ShapePipeline::Slot::MAP::FADE_MASK)); + gpu::Shader::makeProgram(*_simpleFadeShader, slotBindings); + gpu::Shader::makeProgram(*_unlitFadeShader, slotBindings); + }); + } + // If the pipeline did not exist, make it auto state = std::make_shared(); if (config.isCulled()) { @@ -1921,11 +2051,12 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp if (config.isTransparent()) { PrepareStencil::testMask(*state); - } else { + } + else { PrepareStencil::testMaskDrawShape(*state); } - gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader; + gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : (config.isFading() ? _simpleFadeShader : _simpleShader); gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); _simplePrograms.insert(config, pipeline); return pipeline; @@ -1960,26 +2091,93 @@ void renderInstances(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color if (isWire) { DependencyManager::get()->renderWireShapeInstances(batch, shape, data.count(), data.buffers[INSTANCE_COLOR_BUFFER]); - } else { + } + else { DependencyManager::get()->renderShapeInstances(batch, shape, data.count(), data.buffers[INSTANCE_COLOR_BUFFER]); } }); } +static const size_t INSTANCE_FADE_BUFFER1 = 1; +static const size_t INSTANCE_FADE_BUFFER2 = 2; +static const size_t INSTANCE_FADE_BUFFER3 = 3; + +void renderFadeInstances(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, int fadeCategory, float fadeThreshold, + const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, bool isWire, + const render::ShapePipelinePointer& pipeline, GeometryCache::Shape shape) { + // Add pipeline to name + std::string instanceName = (isWire ? "wire_shapes_" : "solid_shapes_") + std::to_string(shape) + "_" + std::to_string(std::hash()(pipeline)); + + // Add color to named buffer + { + gpu::BufferPointer instanceColorBuffer = batch.getNamedBuffer(instanceName, INSTANCE_COLOR_BUFFER); + auto compactColor = toCompactColor(color); + instanceColorBuffer->append(compactColor); + } + // Add fade parameters to named buffers + { + gpu::BufferPointer fadeBuffer1 = batch.getNamedBuffer(instanceName, INSTANCE_FADE_BUFFER1); + gpu::BufferPointer fadeBuffer2 = batch.getNamedBuffer(instanceName, INSTANCE_FADE_BUFFER2); + gpu::BufferPointer fadeBuffer3 = batch.getNamedBuffer(instanceName, INSTANCE_FADE_BUFFER3); + // Pack parameters in 3 vec4s + glm::vec4 fadeData1; + glm::vec4 fadeData2; + glm::vec4 fadeData3; + FadeEffect::packToAttributes(fadeCategory, fadeThreshold, fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, + fadeData1, fadeData2, fadeData3); + fadeBuffer1->append(fadeData1); + fadeBuffer2->append(fadeData2); + fadeBuffer3->append(fadeData3); + } + + // Add call to named buffer + batch.setupNamedCalls(instanceName, [args, isWire, pipeline, shape](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + auto& buffers = data.buffers; + batch.setPipeline(pipeline->pipeline); + pipeline->prepare(batch, args); + + if (isWire) { + DependencyManager::get()->renderWireFadeShapeInstances(batch, shape, data.count(), + buffers[INSTANCE_COLOR_BUFFER], buffers[INSTANCE_FADE_BUFFER1], buffers[INSTANCE_FADE_BUFFER2], buffers[INSTANCE_FADE_BUFFER3]); + } + else { + DependencyManager::get()->renderFadeShapeInstances(batch, shape, data.count(), + buffers[INSTANCE_COLOR_BUFFER], buffers[INSTANCE_FADE_BUFFER1], buffers[INSTANCE_FADE_BUFFER2], buffers[INSTANCE_FADE_BUFFER3]); + } + }); +} + void GeometryCache::renderSolidShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); renderInstances(args, batch, color, false, pipeline, shape); } void GeometryCache::renderWireShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, shape); } +void GeometryCache::renderSolidFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, + int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, + const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); + renderFadeInstances(args, batch, color, fadeCategory, fadeThreshold, fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, false, pipeline, shape); +} + +void GeometryCache::renderWireFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, + int fadeCategory, float fadeThreshold, const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, + const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); + renderFadeInstances(args, batch, color, fadeCategory, fadeThreshold, fadeNoiseOffset, fadeBaseOffset, fadeBaseInvSize, true, pipeline, shape); +} void GeometryCache::renderSolidSphereInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); renderInstances(args, batch, color, false, pipeline, GeometryCache::Sphere); } void GeometryCache::renderWireSphereInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, GeometryCache::Sphere); } @@ -1987,7 +2185,9 @@ void GeometryCache::renderWireSphereInstance(RenderArgs* args, gpu::Batch& batch // available shape types, both solid and wireframes //#define DEBUG_SHAPES + void GeometryCache::renderSolidCubeInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + assert(pipeline != nullptr); #ifdef DEBUG_SHAPES static auto startTime = usecTimestampNow(); renderInstances(INSTANCE_NAME, batch, color, pipeline, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { @@ -2027,5 +2227,6 @@ void GeometryCache::renderSolidCubeInstance(RenderArgs* args, gpu::Batch& batch, void GeometryCache::renderWireCubeInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { static const std::string INSTANCE_NAME = __FUNCTION__; + assert(pipeline != nullptr); renderInstances(args, batch, color, true, pipeline, GeometryCache::Cube); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index fa558a1151..40aa829444 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -147,6 +147,14 @@ public: NUM_SHAPES, }; + static uint8_t CUSTOM_PIPELINE_NUMBER; + static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key); + static void registerShapePipeline() { + if (!CUSTOM_PIPELINE_NUMBER) { + CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); + } + } + int allocateID() { return _nextID++; } void releaseID(int id); static const int UNKNOWN_ID; @@ -155,8 +163,8 @@ public: void bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool transparent = false, bool culled = true, bool unlit = false, bool depthBias = false); // Get the pipeline to render static geometry - gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true, - bool unlit = false, bool depthBias = false); + static gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true, + bool unlit = false, bool depthBias = false, bool fading = false); void bindOpaqueWebBrowserProgram(gpu::Batch& batch, bool isAA); gpu::PipelinePointer getOpaqueWebBrowserProgram(bool isAA); @@ -164,14 +172,26 @@ public: void bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA); gpu::PipelinePointer getTransparentWebBrowserProgram(bool isAA); - render::ShapePipelinePointer getOpaqueShapePipeline() { return GeometryCache::_simpleOpaquePipeline; } - render::ShapePipelinePointer getTransparentShapePipeline() { return GeometryCache::_simpleTransparentPipeline; } - render::ShapePipelinePointer getWireShapePipeline() { return GeometryCache::_simpleWirePipeline; } + static void initializeShapePipelines(); + + render::ShapePipelinePointer getOpaqueShapePipeline() { assert(_simpleOpaquePipeline != nullptr); return _simpleOpaquePipeline; } + render::ShapePipelinePointer getTransparentShapePipeline() { assert(_simpleTransparentPipeline != nullptr); return _simpleTransparentPipeline; } + render::ShapePipelinePointer getOpaqueFadeShapePipeline() { assert(_simpleOpaqueFadePipeline != nullptr); return _simpleOpaqueFadePipeline; } + render::ShapePipelinePointer getTransparentFadeShapePipeline() { assert(_simpleTransparentFadePipeline != nullptr); return _simpleTransparentFadePipeline; } + render::ShapePipelinePointer getOpaqueShapePipeline(bool isFading); + render::ShapePipelinePointer getTransparentShapePipeline(bool isFading); + render::ShapePipelinePointer getWireShapePipeline() { assert(_simpleWirePipeline != nullptr); return GeometryCache::_simpleWirePipeline; } + // Static (instanced) geometry void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); + void renderFadeShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer, + gpu::BufferPointer& fadeBuffer1, gpu::BufferPointer& fadeBuffer2, gpu::BufferPointer& fadeBuffer3); + void renderWireFadeShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer, + gpu::BufferPointer& fadeBuffer1, gpu::BufferPointer& fadeBuffer2, gpu::BufferPointer& fadeBuffer3); + void renderSolidShapeInstance(RenderArgs* args, gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1), const render::ShapePipelinePointer& pipeline = _simpleOpaquePipeline); void renderSolidShapeInstance(RenderArgs* args, gpu::Batch& batch, Shape shape, const glm::vec3& color, @@ -186,6 +206,13 @@ public: renderWireShapeInstance(args, batch, shape, glm::vec4(color, 1.0f), pipeline); } + void renderSolidFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, Shape shape, const glm::vec4& color, int fadeCategory, float fadeThreshold, + const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, + const render::ShapePipelinePointer& pipeline); + void renderWireFadeShapeInstance(RenderArgs* args, gpu::Batch& batch, Shape shape, const glm::vec4& color, int fadeCategory, float fadeThreshold, + const glm::vec3& fadeNoiseOffset, const glm::vec3& fadeBaseOffset, const glm::vec3& fadeBaseInvSize, + const render::ShapePipelinePointer& pipeline); + void renderSolidSphereInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simpleOpaquePipeline); void renderSolidSphereInstance(RenderArgs* args, gpu::Batch& batch, const glm::vec3& color, @@ -416,15 +443,20 @@ private: QHash _lastRegisteredGridBuffer; QHash _registeredGridBuffers; - gpu::ShaderPointer _simpleShader; - gpu::ShaderPointer _unlitShader; + static gpu::ShaderPointer _simpleShader; + static gpu::ShaderPointer _unlitShader; + static gpu::ShaderPointer _simpleFadeShader; + static gpu::ShaderPointer _unlitFadeShader; static render::ShapePipelinePointer _simpleOpaquePipeline; static render::ShapePipelinePointer _simpleTransparentPipeline; + static render::ShapePipelinePointer _simpleOpaqueFadePipeline; + static render::ShapePipelinePointer _simpleTransparentFadePipeline; static render::ShapePipelinePointer _simpleOpaqueOverlayPipeline; static render::ShapePipelinePointer _simpleTransparentOverlayPipeline; static render::ShapePipelinePointer _simpleWirePipeline; gpu::PipelinePointer _glowLinePipeline; - QHash _simplePrograms; + + static QHash _simplePrograms; gpu::ShaderPointer _simpleOpaqueWebBrowserShader; gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline; @@ -435,6 +467,11 @@ private: gpu::PipelinePointer _simpleOpaqueWebBrowserOverlayPipeline; gpu::ShaderPointer _simpleTransparentWebBrowserOverlayShader; gpu::PipelinePointer _simpleTransparentWebBrowserOverlayPipeline; + + static render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true, + bool unlit = false, bool depthBias = false); + static render::ShapePipelinePointer getFadingShapePipeline(bool textured = false, bool transparent = false, bool culled = true, + bool unlit = false, bool depthBias = false); }; #endif // hifi_GeometryCache_h diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index f6cb55deed..517fe97dba 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -318,6 +318,7 @@ template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) { return payload->render(args); } + } ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : @@ -327,6 +328,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int _meshIndex, i assert(model && model->isLoaded()); _model = model; auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); + updateMeshPart(modelMesh, partIndex); updateTransform(transform, offsetTransform); @@ -390,10 +392,6 @@ ItemKey ModelMeshPartPayload::getKey() const { builder.withTransparent(); } } - - if (_fadeState != FADE_COMPLETE) { - builder.withTransparent(); - } } return builder.build(); } @@ -465,7 +463,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { ShapeKey::Builder builder; builder.withMaterial(); - if (isTranslucent || _fadeState != FADE_COMPLETE) { + if (isTranslucent) { builder.withTranslucent(); } if (hasTangents) { @@ -510,9 +508,8 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { } } - if (_fadeState != FADE_COMPLETE) { - batch._glColor4f(1.0f, 1.0f, 1.0f, computeFadeAlpha()); - } else if (!_hasColorAttrib) { + // TODO: Get rid of that extra call + if (!_hasColorAttrib) { batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } } @@ -525,45 +522,17 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline: batch.setModelTransform(_transform); } -float ModelMeshPartPayload::computeFadeAlpha() { - if (_fadeState == FADE_WAITING_TO_START) { - return 0.0f; - } - float fadeAlpha = 1.0f; - const float INV_FADE_PERIOD = 1.0f / (float)(1 * USECS_PER_SECOND); - float fraction = (float)(usecTimestampNow() - _fadeStartTime) * INV_FADE_PERIOD; - if (fraction < 1.0f) { - fadeAlpha = Interpolate::simpleNonLinearBlend(fraction); - } - if (fadeAlpha >= 1.0f) { - _fadeState = FADE_COMPLETE; - // when fade-in completes we flag model for one last "render item update" - ModelPointer model = _model.lock(); - if (model) { - model->setRenderItemsNeedUpdate(); - } - return 1.0f; - } - return Interpolate::simpleNonLinearBlend(fadeAlpha); -} - void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); ModelPointer model = _model.lock(); - if (!model || !model->addedToScene() || !model->isVisible()) { + if (!model || !model->isAddedToScene() || !model->isVisible()) { return; // bail asap } - if (_fadeState == FADE_WAITING_TO_START) { + if (_state == WAITING_TO_START) { if (model->isLoaded()) { - // FIXME as far as I can tell this is the ONLY reason render-util depends on entities. - if (EntityItem::getEntitiesShouldFadeFunction()()) { - _fadeStartTime = usecTimestampNow(); - _fadeState = FADE_IN_PROGRESS; - } else { - _fadeState = FADE_COMPLETE; - } + _state = STARTED; model->setRenderItemsNeedUpdate(); } else { return; diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 5d12e60ce3..99c14510b5 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -23,10 +23,6 @@ #include "Model.h" -const uint8_t FADE_WAITING_TO_START = 0; -const uint8_t FADE_IN_PROGRESS = 1; -const uint8_t FADE_COMPLETE = 2; - class Model; class MeshPartPayload { @@ -95,8 +91,6 @@ public: const Transform& boundTransform, const gpu::BufferPointer& buffer); - float computeFadeAlpha(); - // Render Item interface render::ItemKey getKey() const override; int getLayer() const; @@ -122,8 +116,13 @@ public: bool _materialNeedsUpdate { true }; private: - quint64 _fadeStartTime { 0 }; - uint8_t _fadeState { FADE_WAITING_TO_START }; + + enum State : uint8_t { + WAITING_TO_START = 0, + STARTED = 1, + }; + + mutable State _state { WAITING_TO_START } ; }; namespace render { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 497346c138..63aeacf80c 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -116,6 +116,7 @@ public: const QVector& vertices, const QVector& normals); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } + bool isAddedToScene() const { return _addedToScene; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } @@ -267,7 +268,6 @@ signals: void setCollisionModelURLFinished(bool success); protected: - bool addedToScene() const { return _addedToScene; } void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 20c999019b..5b51b2d1cc 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -34,6 +34,7 @@ #include "FramebufferCache.h" #include "TextureCache.h" #include "ZoneRenderer.h" +#include "FadeEffect.h" #include "AmbientOcclusionEffect.h" #include "AntialiasingEffect.h" @@ -45,24 +46,35 @@ using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber); -extern void initDeferredPipelines(render::ShapePlumber& plumber); +extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); + +RenderDeferredTask::RenderDeferredTask() { + DependencyManager::set(); +} + +void RenderDeferredTask::configure(const Config& config) +{ +} void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { - auto items = input.get(); + const auto& items = input.get(); + auto fadeEffect = DependencyManager::get(); // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared(); - initDeferredPipelines(*shapePlumber); + initDeferredPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); // Extract opaques / transparents / lights / metas / overlays / background - const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; - const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; - const auto lights = items[RenderFetchCullSortTask::LIGHT]; - const auto metas = items[RenderFetchCullSortTask::META]; - const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; - const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; - const auto background = items[RenderFetchCullSortTask::BACKGROUND]; - const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION]; + const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; + const auto& metas = items.get0()[RenderFetchCullSortTask::META]; + const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; + const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; + //const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; + const auto& spatialSelection = items[1]; + + fadeEffect->build(task, opaques); // Filter the non antialiaased overlays const int LAYER_NO_AA = 3; @@ -77,7 +89,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto opaqueRangeTimer = task.addJob("BeginOpaqueRangeTimer", "DrawOpaques"); - const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).hasVarying(); + const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).asVarying(); const auto prepareDeferredOutputs = task.addJob("PrepareDeferred", prepareDeferredInputs); const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); @@ -86,7 +98,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("PrepareStencil", primaryFramebuffer); // Render opaque objects in DeferredBuffer - const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying(); + const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); task.addJob("OpaqueRangeTimer", opaqueRangeTimer); @@ -95,12 +107,12 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Opaque all rendered // Linear Depth Pass - const auto linearDepthPassInputs = LinearDepthPass::Inputs(deferredFrameTransform, deferredFramebuffer).hasVarying(); + const auto linearDepthPassInputs = LinearDepthPass::Inputs(deferredFrameTransform, deferredFramebuffer).asVarying(); const auto linearDepthPassOutputs = task.addJob("LinearDepth", linearDepthPassInputs); const auto linearDepthTarget = linearDepthPassOutputs.getN(0); // Curvature pass - const auto surfaceGeometryPassInputs = SurfaceGeometryPass::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).hasVarying(); + const auto surfaceGeometryPassInputs = SurfaceGeometryPass::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).asVarying(); const auto surfaceGeometryPassOutputs = task.addJob("SurfaceGeometry", surfaceGeometryPassInputs); const auto surfaceGeometryFramebuffer = surfaceGeometryPassOutputs.getN(0); const auto curvatureFramebuffer = surfaceGeometryPassOutputs.getN(1); @@ -111,7 +123,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto scatteringResource = task.addJob("Scattering"); // AO job - const auto ambientOcclusionInputs = AmbientOcclusionEffect::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).hasVarying(); + const auto ambientOcclusionInputs = AmbientOcclusionEffect::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).asVarying(); const auto ambientOcclusionOutputs = task.addJob("AmbientOcclusion", ambientOcclusionInputs); const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN(0); const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN(1); @@ -125,13 +137,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Light Clustering // Create the cluster grid of lights, cpu job for now - const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).hasVarying(); + const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget).asVarying(); const auto lightClusters = task.addJob("LightClustering", lightClusteringPassInputs); // DeferredBuffer is complete, now let's shade it into the LightingBuffer const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, - surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource, lightClusters).hasVarying(); + surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource, lightClusters).asVarying(); task.addJob("RenderDeferred", deferredLightingInputs); @@ -139,12 +151,12 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawBackgroundDeferred", lightingModel); // Render transparent objects forward in LightingBuffer - const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).hasVarying(); + const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); // LIght Cluster Grid Debuging job { - const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, linearDepthTarget, lightClusters).hasVarying(); + const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, linearDepthTarget, lightClusters).asVarying(); task.addJob("DebugLightClusters", debugLightClustersInputs); } @@ -164,8 +176,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren } // Overlays - const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).hasVarying(); - const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).hasVarying(); + const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).asVarying(); + const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).asVarying(); task.addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); task.addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); @@ -181,10 +193,10 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DebugDeferredBuffer", debugFramebuffers); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, - surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource).hasVarying(); + surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource).asVarying(); task.addJob("DebugScattering", debugSubsurfaceScatteringInputs); - const auto debugAmbientOcclusionInputs = DebugAmbientOcclusion::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget, ambientOcclusionUniforms).hasVarying(); + const auto debugAmbientOcclusionInputs = DebugAmbientOcclusion::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget, ambientOcclusionUniforms).asVarying(); task.addJob("DebugAmbientOcclusion", debugAmbientOcclusionInputs); // Scene Octree Debugging job @@ -209,7 +221,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Antialiasing", primaryFramebuffer); // Draw 2DWeb non AA - const auto nonAAOverlaysInputs = DrawOverlay3D::Inputs(nonAAOverlays, lightingModel).hasVarying(); + const auto nonAAOverlaysInputs = DrawOverlay3D::Inputs(nonAAOverlays, lightingModel).asVarying(); task.addJob("Draw2DWebSurfaces", nonAAOverlaysInputs, false); task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); @@ -269,6 +281,7 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& if (lightingModel->isWireframeEnabled()) { keyBuilder.withWireframe(); } + ShapeKey globalKey = keyBuilder.build(); args->_globalShapeKey = globalKey._flags.to_ulong(); @@ -310,11 +323,12 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); - // From the lighting model define a global shapKey ORED with individiual keys + // From the lighting model define a global shapeKey ORED with individiual keys ShapeKey::Builder keyBuilder; if (lightingModel->isWireframeEnabled()) { keyBuilder.withWireframe(); } + ShapeKey globalKey = keyBuilder.build(); args->_globalShapeKey = globalKey._flags.to_ulong(); @@ -357,7 +371,7 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& if (_opaquePass) { gpu::doInBatch(args->_context, [&](gpu::Batch& batch){ batch.enableStereo(false); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, true); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); }); } diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index fd7c5eb23b..e7575a1c95 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -44,12 +44,11 @@ public: protected: }; - class DrawConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY newStats) - Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) + public: int getNumDrawn() { return _numDrawn; } @@ -163,14 +162,34 @@ public: void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); }; +class RenderDeferredTaskConfig : public render::Task::Config { + Q_OBJECT + Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty) + Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty) + Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty) + Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty) +public: + float fadeScale{ 0.5f }; + float fadeDuration{ 3.0f }; + float debugFadePercent{ 0.f }; + bool debugFade{ false }; + +signals: + void dirty(); + +}; + class RenderDeferredTask { public: using Input = RenderFetchCullSortTask::Output; - using JobModel = render::Task::ModelI; + using Config = RenderDeferredTaskConfig; + using JobModel = render::Task::ModelI; - RenderDeferredTask() {} + RenderDeferredTask(); + void configure(const Config& config); void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + }; #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index a77d741aa5..296eea1da8 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -36,14 +36,14 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend initForwardPipelines(*shapePlumber); // Extract opaques / transparents / lights / metas / overlays / background - const auto opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; - const auto transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; - const auto lights = items[RenderFetchCullSortTask::LIGHT]; - const auto metas = items[RenderFetchCullSortTask::META]; - const auto overlayOpaques = items[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; - const auto overlayTransparents = items[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; - const auto background = items[RenderFetchCullSortTask::BACKGROUND]; - const auto spatialSelection = items[RenderFetchCullSortTask::SPATIAL_SELECTION]; + const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; +// const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; +// const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; +// const auto& metas = items.get0()[RenderFetchCullSortTask::META]; +// const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; +// const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; + const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; +// const auto& spatialSelection = items[1]; const auto framebuffer = task.addJob("PrepareFramebuffer"); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 6c3a58b7e5..c5949cb336 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -29,12 +29,25 @@ #include "skin_model_shadow_vert.h" #include "skin_model_normal_map_vert.h" +#include "model_shadow_fade_vert.h" +#include "model_lightmap_fade_vert.h" +#include "model_lightmap_normal_map_fade_vert.h" +#include "skin_model_fade_vert.h" +#include "skin_model_shadow_fade_vert.h" +#include "skin_model_normal_map_fade_vert.h" + #include "simple_vert.h" #include "simple_textured_frag.h" #include "simple_textured_unlit_frag.h" #include "simple_transparent_textured_frag.h" #include "simple_transparent_textured_unlit_frag.h" +#include "simple_fade_vert.h" +#include "simple_textured_fade_frag.h" +#include "simple_textured_unlit_fade_frag.h" +#include "simple_transparent_textured_fade_frag.h" +#include "simple_transparent_textured_unlit_fade_frag.h" + #include "model_frag.h" #include "model_unlit_frag.h" #include "model_shadow_frag.h" @@ -42,6 +55,16 @@ #include "model_normal_specular_map_frag.h" #include "model_specular_map_frag.h" +#include "model_fade_vert.h" +#include "model_normal_map_fade_vert.h" + +#include "model_fade_frag.h" +#include "model_shadow_fade_frag.h" +#include "model_unlit_fade_frag.h" +#include "model_normal_map_fade_frag.h" +#include "model_normal_specular_map_fade_frag.h" +#include "model_specular_map_fade_frag.h" + #include "forward_model_frag.h" #include "forward_model_unlit_frag.h" #include "forward_model_normal_map_frag.h" @@ -55,6 +78,13 @@ #include "model_translucent_frag.h" #include "model_translucent_unlit_frag.h" +#include "model_lightmap_fade_frag.h" +#include "model_lightmap_normal_map_fade_frag.h" +#include "model_lightmap_normal_specular_map_fade_frag.h" +#include "model_lightmap_specular_map_fade_frag.h" +#include "model_translucent_fade_frag.h" +#include "model_translucent_unlit_fade_frag.h" + #include "overlay3D_vert.h" #include "overlay3D_frag.h" #include "overlay3D_model_frag.h" @@ -70,11 +100,12 @@ using namespace render; using namespace std::placeholders; void initOverlay3DPipelines(ShapePlumber& plumber); -void initDeferredPipelines(ShapePlumber& plumber); +void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initForwardPipelines(ShapePlumber& plumber); void addPlumberPipeline(ShapePlumber& plumber, - const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel); + const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel, + const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); @@ -140,7 +171,7 @@ void initOverlay3DPipelines(ShapePlumber& plumber) { } } -void initDeferredPipelines(render::ShapePlumber& plumber) { +void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // Vertex shaders auto simpleVertex = gpu::Shader::createVertex(std::string(simple_vert)); auto modelVertex = gpu::Shader::createVertex(std::string(model_vert)); @@ -151,6 +182,16 @@ void initDeferredPipelines(render::ShapePlumber& plumber) { auto skinModelVertex = gpu::Shader::createVertex(std::string(skin_model_vert)); auto skinModelNormalMapVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_vert)); auto skinModelShadowVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); + auto modelLightmapFadeVertex = gpu::Shader::createVertex(std::string(model_lightmap_fade_vert)); + auto modelLightmapNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_lightmap_normal_map_fade_vert)); + auto skinModelFadeVertex = gpu::Shader::createVertex(std::string(skin_model_fade_vert)); + auto skinModelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(skin_model_normal_map_fade_vert)); + + auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_fade_vert)); + auto modelNormalMapFadeVertex = gpu::Shader::createVertex(std::string(model_normal_map_fade_vert)); + auto simpleFadeVertex = gpu::Shader::createVertex(std::string(simple_fade_vert)); + auto modelShadowFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); + auto skinModelShadowFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); // Pixel shaders auto simplePixel = gpu::Shader::createPixel(std::string(simple_textured_frag)); @@ -169,104 +210,220 @@ void initDeferredPipelines(render::ShapePlumber& plumber) { auto modelLightmapNormalMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_map_frag)); auto modelLightmapSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_specular_map_frag)); auto modelLightmapNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_frag)); + auto modelLightmapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_fade_frag)); + auto modelLightmapNormalMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_map_fade_frag)); + auto modelLightmapSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_specular_map_fade_frag)); + auto modelLightmapNormalSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_lightmap_normal_specular_map_fade_frag)); + + auto modelFadePixel = gpu::Shader::createPixel(std::string(model_fade_frag)); + auto modelUnlitFadePixel = gpu::Shader::createPixel(std::string(model_unlit_fade_frag)); + auto modelNormalMapFadePixel = gpu::Shader::createPixel(std::string(model_normal_map_fade_frag)); + auto modelSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_specular_map_fade_frag)); + auto modelNormalSpecularMapFadePixel = gpu::Shader::createPixel(std::string(model_normal_specular_map_fade_frag)); + auto modelShadowFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); + auto modelTranslucentFadePixel = gpu::Shader::createPixel(std::string(model_translucent_fade_frag)); + auto modelTranslucentUnlitFadePixel = gpu::Shader::createPixel(std::string(model_translucent_unlit_fade_frag)); + auto simpleFadePixel = gpu::Shader::createPixel(std::string(simple_textured_fade_frag)); + auto simpleUnlitFadePixel = gpu::Shader::createPixel(std::string(simple_textured_unlit_fade_frag)); + auto simpleTranslucentFadePixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_fade_frag)); + auto simpleTranslucentUnlitFadePixel = gpu::Shader::createPixel(std::string(simple_transparent_textured_unlit_fade_frag)); using Key = render::ShapeKey; - auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3); + auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4, _5); // TODO: Refactor this to use a filter // Opaques addPipeline( Key::Builder().withMaterial(), - modelVertex, modelPixel); + modelVertex, modelPixel, nullptr, nullptr); addPipeline( Key::Builder(), - simpleVertex, simplePixel); + simpleVertex, simplePixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withUnlit(), - modelVertex, modelUnlitPixel); + modelVertex, modelUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withUnlit(), - simpleVertex, simpleUnlitPixel); + simpleVertex, simpleUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTangents(), - modelNormalMapVertex, modelNormalMapPixel); + modelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSpecular(), - modelVertex, modelSpecularMapPixel); + modelVertex, modelSpecularMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTangents().withSpecular(), - modelNormalMapVertex, modelNormalSpecularMapPixel); + modelNormalMapVertex, modelNormalSpecularMapPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withFade(), + modelFadeVertex, modelFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withFade(), + simpleFadeVertex, simpleFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withUnlit().withFade(), + modelFadeVertex, modelUnlitFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withUnlit().withFade(), + simpleFadeVertex, simpleUnlitFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTangents().withFade(), + modelNormalMapFadeVertex, modelNormalMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSpecular().withFade(), + modelFadeVertex, modelSpecularMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTangents().withSpecular().withFade(), + modelNormalMapFadeVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter); + // Translucents addPipeline( Key::Builder().withMaterial().withTranslucent(), - modelVertex, modelTranslucentPixel); + modelVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withTranslucent(), - simpleVertex, simpleTranslucentPixel); + simpleVertex, simpleTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withUnlit(), - modelVertex, modelTranslucentUnlitPixel); + modelVertex, modelTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withTranslucent().withUnlit(), - simpleVertex, simpleTranslucentUnlitPixel); + simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelNormalMapVertex, modelTranslucentPixel); + modelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withSpecular(), - modelVertex, modelTranslucentPixel); + modelVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withSpecular(), - modelNormalMapVertex, modelTranslucentPixel); + modelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), - modelVertex, modelTranslucentPixel); + modelVertex, modelTranslucentPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withTranslucent().withFade(), + modelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withTranslucent().withFade(), + simpleFadeVertex, simpleTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTranslucent().withUnlit().withFade(), + modelFadeVertex, modelTranslucentUnlitFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withTranslucent().withUnlit().withFade(), + simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTranslucent().withTangents().withFade(), + modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTranslucent().withSpecular().withFade(), + modelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withTranslucent().withTangents().withSpecular().withFade(), + modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + // FIXME: Ignore lightmap for translucents meshpart + Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(), + modelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + // Lightmapped addPipeline( Key::Builder().withMaterial().withLightmap(), - modelLightmapVertex, modelLightmapPixel); + modelLightmapVertex, modelLightmapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withLightmap().withTangents(), - modelLightmapNormalMapVertex, modelLightmapNormalMapPixel); + modelLightmapNormalMapVertex, modelLightmapNormalMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withLightmap().withSpecular(), - modelLightmapVertex, modelLightmapSpecularMapPixel); + modelLightmapVertex, modelLightmapSpecularMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withLightmap().withTangents().withSpecular(), - modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel); + modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withLightmap().withFade(), + modelLightmapFadeVertex, modelLightmapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withLightmap().withTangents().withFade(), + modelLightmapNormalMapFadeVertex, modelLightmapNormalMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withLightmap().withSpecular().withFade(), + modelLightmapFadeVertex, modelLightmapSpecularMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withLightmap().withTangents().withSpecular().withFade(), + modelLightmapNormalMapFadeVertex, modelLightmapNormalSpecularMapFadePixel, batchSetter, itemSetter); + // Skinned addPipeline( Key::Builder().withMaterial().withSkinned(), - skinModelVertex, modelPixel); + skinModelVertex, modelPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents(), - skinModelNormalMapVertex, modelNormalMapPixel); + skinModelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withSpecular(), - skinModelVertex, modelSpecularMapPixel); + skinModelVertex, modelSpecularMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents().withSpecular(), - skinModelNormalMapVertex, modelNormalSpecularMapPixel); + skinModelNormalMapVertex, modelNormalSpecularMapPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withSkinned().withFade(), + skinModelFadeVertex, modelFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTangents().withFade(), + skinModelNormalMapFadeVertex, modelNormalMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withSpecular().withFade(), + skinModelFadeVertex, modelSpecularMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTangents().withSpecular().withFade(), + skinModelNormalMapFadeVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter); + // Skinned and Translucent addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent(), - skinModelVertex, modelTranslucentPixel); + skinModelVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapVertex, modelTranslucentPixel); + skinModelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withSpecular(), - skinModelVertex, modelTranslucentPixel); + skinModelVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular(), - skinModelNormalMapVertex, modelTranslucentPixel); + skinModelNormalMapVertex, modelTranslucentPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), + skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(), + skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTranslucent().withSpecular().withFade(), + skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular().withFade(), + skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + // Depth-only addPipeline( Key::Builder().withDepthOnly(), - modelShadowVertex, modelShadowPixel); + modelShadowVertex, modelShadowPixel, nullptr, nullptr); addPipeline( Key::Builder().withSkinned().withDepthOnly(), - skinModelShadowVertex, modelShadowPixel); + skinModelShadowVertex, modelShadowPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withDepthOnly().withFade(), + modelShadowFadeVertex, modelShadowFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withSkinned().withDepthOnly().withFade(), + skinModelShadowFadeVertex, modelShadowFadePixel, batchSetter, itemSetter); } void initForwardPipelines(render::ShapePlumber& plumber) { @@ -284,7 +441,7 @@ void initForwardPipelines(render::ShapePlumber& plumber) { auto modelNormalSpecularMapPixel = gpu::Shader::createPixel(std::string(forward_model_normal_specular_map_frag)); using Key = render::ShapeKey; - auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3); + auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, nullptr, nullptr); // Opaques addPipeline( Key::Builder().withMaterial(), @@ -317,7 +474,8 @@ void initForwardPipelines(render::ShapePlumber& plumber) { } void addPlumberPipeline(ShapePlumber& plumber, - const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel) { + const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel, + const render::ShapePipeline::BatchSetter& extraBatchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // These key-values' pipelines are added by this functor in addition to the key passed assert(!key.isWireframe()); assert(!key.isDepthBiased()); @@ -354,8 +512,18 @@ void addPlumberPipeline(ShapePlumber& plumber, state->setDepthBiasSlopeScale(1.0f); } - plumber.addPipeline(builder.build(), program, state, - key.isTranslucent() ? &lightBatchSetter : &batchSetter); + auto baseBatchSetter = key.isTranslucent() ? &lightBatchSetter : &batchSetter; + render::ShapePipeline::BatchSetter finalBatchSetter; + if (extraBatchSetter) { + finalBatchSetter = [baseBatchSetter, extraBatchSetter](const ShapePipeline& pipeline, gpu::Batch& batch, render::Args* args) { + baseBatchSetter(pipeline, batch, args); + extraBatchSetter(pipeline, batch, args); + }; + } + else { + finalBatchSetter = baseBatchSetter; + } + plumber.addPipeline(builder.build(), program, state, finalBatchSetter, itemSetter); } } diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 03a2a4f9b1..ac80c03873 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -28,6 +28,12 @@ #include "model_shadow_frag.h" #include "skin_model_shadow_frag.h" +#include "model_shadow_fade_vert.h" +#include "skin_model_shadow_fade_vert.h" + +#include "model_shadow_fade_frag.h" +#include "skin_model_shadow_fade_frag.h" + using namespace render; void RenderShadowMap::run(const render::RenderContextPointer& renderContext, @@ -46,6 +52,8 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, const auto& fbo = shadow->framebuffer; RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.enableStereo(false); @@ -62,8 +70,8 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, batch.setProjectionTransform(shadow->getProjection()); batch.setViewTransform(shadow->getView(), false); - auto shadowPipeline = _shapePlumber->pickPipeline(args, ShapeKey()); - auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, ShapeKey::Builder().withSkinned()); + auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); std::vector skinnedShapeKeys{}; @@ -104,15 +112,29 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withoutSkinned(), + ShapeKey::Filter::Builder().withoutSkinned().withoutFade(), modelProgram, state); auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); auto skinPixel = gpu::Shader::createPixel(std::string(skin_model_shadow_frag)); gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, skinPixel); shapePlumber->addPipeline( - ShapeKey::Filter::Builder().withSkinned(), + ShapeKey::Filter::Builder().withSkinned().withoutFade(), skinProgram, state); + + auto modelFadeVertex = gpu::Shader::createVertex(std::string(model_shadow_fade_vert)); + auto modelFadePixel = gpu::Shader::createPixel(std::string(model_shadow_fade_frag)); + gpu::ShaderPointer modelFadeProgram = gpu::Shader::createProgram(modelFadeVertex, modelFadePixel); + shapePlumber->addPipeline( + ShapeKey::Filter::Builder().withoutSkinned().withFade(), + modelFadeProgram, state); + + auto skinFadeVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_fade_vert)); + auto skinFadePixel = gpu::Shader::createPixel(std::string(skin_model_shadow_fade_frag)); + gpu::ShaderPointer skinFadeProgram = gpu::Shader::createProgram(skinFadeVertex, skinFadePixel); + shapePlumber->addPipeline( + ShapeKey::Filter::Builder().withSkinned().withFade(), + skinFadeProgram, state); } const auto cachedMode = task.addJob("ShadowSetup"); diff --git a/libraries/render-utils/src/UpdateSceneTask.cpp b/libraries/render-utils/src/UpdateSceneTask.cpp index 2daee5fb5a..0e0c3f087b 100644 --- a/libraries/render-utils/src/UpdateSceneTask.cpp +++ b/libraries/render-utils/src/UpdateSceneTask.cpp @@ -13,11 +13,13 @@ #include #include "LightStage.h" #include "BackgroundStage.h" +#include #include "DeferredLightingEffect.h" void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { task.addJob("LightStageSetup"); task.addJob("BackgroundStageSetup"); + task.addJob("TransitionStageSetup"); task.addJob("DefaultLightingSetup"); diff --git a/libraries/render-utils/src/model_fade.slf b/libraries/render-utils/src/model_fade.slf new file mode 100644 index 0000000000..d232667660 --- /dev/null +++ b/libraries/render-utils/src/model_fade.slf @@ -0,0 +1,70 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// model_fade.frag +// fragment shader +// +// Created by Olivier Prat on 04/19/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec4 _worldPosition; +in vec3 _normal; +in vec3 _color; +in vec2 _texCoord0; +in vec2 _texCoord1; + + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + emissive += fadeEmissive; + + float scattering = getMaterialScattering(mat); + + packDeferredFragment( + normalize(_normal.xyz), + opacity, + albedo, + roughness, + getMaterialMetallic(mat), + emissive, + occlusionTex, + scattering); +} diff --git a/libraries/render-utils/src/model_fade.slv b/libraries/render-utils/src/model_fade.slv new file mode 100644 index 0000000000..4c6bc534a9 --- /dev/null +++ b/libraries/render-utils/src/model_fade.slv @@ -0,0 +1,44 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// model_fade.slv +// vertex shader +// +// Created by Olivier Prat on 04/24/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out float _alpha; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec4 _position; +out vec4 _worldPosition; +out vec3 _normal; +out vec3 _color; + +void main(void) { + _color = colorToLinearRGB(inColor.xyz); + _alpha = inColor.w; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> +} diff --git a/libraries/render-utils/src/model_lightmap_fade.slf b/libraries/render-utils/src/model_lightmap_fade.slf new file mode 100644 index 0000000000..92d00a2046 --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_fade.slf @@ -0,0 +1,54 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS)$> +<$declareMaterialLightmap()$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> + + + packDeferredFragmentLightmap( + normalize(_normal), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat), + getMaterialFresnel(mat), + lightmapVal+fadeEmissive); +} diff --git a/libraries/render-utils/src/model_lightmap_fade.slv b/libraries/render-utils/src/model_lightmap_fade.slv new file mode 100644 index 0000000000..561049d614 --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_fade.slv @@ -0,0 +1,46 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _color; +out vec4 _worldPosition; + +void main(void) { + // pass along the color in linear space + _color = colorToLinearRGB(inColor.xyz); + + // and the texture coordinates + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> +} + diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slf b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf new file mode 100644 index 0000000000..825e84d666 --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slf @@ -0,0 +1,57 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_normal_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL)$> +<$declareMaterialLightmap()$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _tangent; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> + + vec3 viewNormal; + <$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$> + + packDeferredFragmentLightmap( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat), + getMaterialMetallic(mat), + getMaterialFresnel(mat), + lightmapVal+fadeEmissive); +} diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv new file mode 100644 index 0000000000..4049fb0077 --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv @@ -0,0 +1,46 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_normal_map_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _tangent; +out vec3 _color; +out vec4 _worldPosition; + +void main(void) { + // pass along the color in linear space + _color = colorToLinearRGB(inColor.xyz); + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord1, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> +} diff --git a/libraries/render-utils/src/model_lightmap_normal_specular_map_fade.slf b/libraries/render-utils/src/model_lightmap_normal_specular_map_fade.slf new file mode 100644 index 0000000000..791d5bf552 --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_normal_specular_map_fade.slf @@ -0,0 +1,57 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_normal_specular_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC)$> +<$declareMaterialLightmap()$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _tangent; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, normalTexel, metallicTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> + + vec3 viewNormal; + <$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$> + + packDeferredFragmentLightmap( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat) * metallicTex, + /*specular, // no use of */ getMaterialFresnel(mat), + lightmapVal+fadeEmissive); +} diff --git a/libraries/render-utils/src/model_lightmap_specular_map_fade.slf b/libraries/render-utils/src/model_lightmap_specular_map_fade.slf new file mode 100644 index 0000000000..e82018eefb --- /dev/null +++ b/libraries/render-utils/src/model_lightmap_specular_map_fade.slf @@ -0,0 +1,53 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_lightmap_specular_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC)$> +<$declareMaterialLightmap()$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedo, roughness, _SCRIBE_NULL, metallicTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$> + + packDeferredFragmentLightmap( + normalize(_normal), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedo.a), + getMaterialAlbedo(mat) * albedo.rgb * _color, + getMaterialRoughness(mat) * roughness, + getMaterialMetallic(mat) * metallicTex, + /*metallicTex, // no use of */getMaterialFresnel(mat), + lightmapVal+fadeEmissive); +} diff --git a/libraries/render-utils/src/model_normal_map_fade.slf b/libraries/render-utils/src/model_normal_map_fade.slf new file mode 100644 index 0000000000..d8b864260c --- /dev/null +++ b/libraries/render-utils/src/model_normal_map_fade.slf @@ -0,0 +1,74 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_normal_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 04/19/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION, SCATTERING)$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec4 _worldPosition; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _tangent; +in vec3 _color; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, scatteringTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + vec3 viewNormal; + <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$> + + float scattering = getMaterialScattering(mat); + <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; + + packDeferredFragment( + viewNormal, + opacity, + albedo, + roughness, + getMaterialMetallic(mat), + emissive+fadeEmissive, + occlusionTex, + scattering); +} diff --git a/libraries/render-utils/src/model_normal_map_fade.slv b/libraries/render-utils/src/model_normal_map_fade.slv new file mode 100644 index 0000000000..a71900d5c3 --- /dev/null +++ b/libraries/render-utils/src/model_normal_map_fade.slv @@ -0,0 +1,48 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_normal_map_fade.vert +// vertex shader +// +// Created by Olivier Prat on 04/24/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec4 _worldPosition; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _tangent; +out vec3 _color; +out float _alpha; + +void main(void) { + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> +} diff --git a/libraries/render-utils/src/model_normal_specular_map_fade.slf b/libraries/render-utils/src/model_normal_specular_map_fade.slf new file mode 100644 index 0000000000..5492b24763 --- /dev/null +++ b/libraries/render-utils/src/model_normal_specular_map_fade.slf @@ -0,0 +1,76 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_normal_specular_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _tangent; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + vec3 viewNormal; + <$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$> + + float metallic = getMaterialMetallic(mat); + <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + + float scattering = getMaterialScattering(mat); + + packDeferredFragment( + normalize(viewNormal.xyz), + opacity, + albedo, + roughness, + metallic, + emissive+fadeEmissive, + occlusionTex, + scattering); +} diff --git a/libraries/render-utils/src/model_shadow_fade.slf b/libraries/render-utils/src/model_shadow_fade.slf new file mode 100644 index 0000000000..c00eed622c --- /dev/null +++ b/libraries/render-utils/src/model_shadow_fade.slf @@ -0,0 +1,30 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_shadow_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include Fade.slh@> +<$declareFadeFragment()$> + +layout(location = 0) out vec4 _fragColor; + +in vec4 _worldPosition; + +void main(void) { + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFadeClip(fadeParams, _worldPosition.xyz); + + // pass-through to set z-buffer + _fragColor = vec4(1.0, 1.0, 1.0, 0.0); +} diff --git a/libraries/render-utils/src/model_shadow_fade.slv b/libraries/render-utils/src/model_shadow_fade.slv new file mode 100644 index 0000000000..8762f1dd90 --- /dev/null +++ b/libraries/render-utils/src/model_shadow_fade.slv @@ -0,0 +1,29 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_shadow_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// 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 +// + +<@include gpu/Inputs.slh@> + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +out vec4 _worldPosition; + +void main(void) { + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> +} diff --git a/libraries/render-utils/src/model_specular_map_fade.slf b/libraries/render-utils/src/model_specular_map_fade.slf new file mode 100644 index 0000000000..6eb56c0929 --- /dev/null +++ b/libraries/render-utils/src/model_specular_map_fade.slf @@ -0,0 +1,72 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_specular_map_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _position; +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec3 _normal; +in vec3 _color; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + float metallic = getMaterialMetallic(mat); + <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + + float scattering = getMaterialScattering(mat); + + packDeferredFragment( + normalize(_normal), + opacity, + albedo, + roughness, + metallic, + emissive+fadeEmissive, + occlusionTex, + scattering); +} diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf new file mode 100644 index 0000000000..c46b396ebc --- /dev/null +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -0,0 +1,91 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_translucent_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include model/Material.slh@> + +<@include DeferredGlobalLight.slh@> + +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec2 _texCoord0; +in vec2 _texCoord1; +in vec4 _position; +in vec3 _normal; +in vec3 _color; +in float _alpha; +in vec4 _worldPosition; + +out vec4 _fragColor; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> + <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + + float opacity = getMaterialOpacity(mat) * _alpha; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + + float roughness = getMaterialRoughness(mat); + <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; + + float metallic = getMaterialMetallic(mat); + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } else { + fresnel = albedo; + metallic = 1.0; + } + + vec3 emissive = getMaterialEmissive(mat); + <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; + + vec3 fragPosition = _position.xyz; + vec3 fragNormal = normalize(_normal); + + TransformCamera cam = getTransformCamera(); + + _fragColor = vec4(evalGlobalLightingAlphaBlended( + cam._viewInverse, + 1.0, + occlusionTex, + fragPosition, + fragNormal, + albedo, + fresnel, + metallic, + emissive+fadeEmissive, + roughness, opacity), + opacity); +} diff --git a/libraries/render-utils/src/model_translucent_unlit_fade.slf b/libraries/render-utils/src/model_translucent_unlit_fade.slf new file mode 100644 index 0000000000..6a77efe4ca --- /dev/null +++ b/libraries/render-utils/src/model_translucent_unlit_fade.slf @@ -0,0 +1,50 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_translucent_unlit_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include model/Material.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> +<@include LightingModel.slh@> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec2 _texCoord0; +in vec3 _color; +in float _alpha; +in vec4 _worldPosition; + +out vec4 _fragColor; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> + + float opacity = getMaterialOpacity(mat) * _alpha; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + albedo += fadeEmissive; + _fragColor = vec4(albedo * isUnlitEnabled(), opacity); +} diff --git a/libraries/render-utils/src/model_unlit_fade.slf b/libraries/render-utils/src/model_unlit_fade.slf new file mode 100644 index 0000000000..0fe9f2ebac --- /dev/null +++ b/libraries/render-utils/src/model_unlit_fade.slf @@ -0,0 +1,54 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// model_unlit_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> +<@include LightingModel.slh@> +<@include model/Material.slh@> + +<@include Fade.slh@> +<$declareFadeFragment()$> + +<@include MaterialTextures.slh@> +<$declareMaterialTextures(ALBEDO)$> + +in vec2 _texCoord0; +in vec3 _normal; +in vec3 _color; +in float _alpha; +in vec4 _worldPosition; + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material mat = getMaterial(); + int matKey = getMaterialKey(mat); + <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> + + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; + + vec3 albedo = getMaterialAlbedo(mat); + <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; + albedo *= _color; + albedo += fadeEmissive; + packDeferredFragmentUnlit( + normalize(_normal), + opacity, + albedo * isUnlitEnabled()); +} diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf new file mode 100644 index 0000000000..245d32e81e --- /dev/null +++ b/libraries/render-utils/src/simple_fade.slf @@ -0,0 +1,101 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include DeferredBufferWrite.slh@> +<@include model/Material.slh@> + +<@include Fade.slh@> +<$declareFadeFragmentInstanced()$> + +// the interpolated normal +in vec3 _normal; +in vec3 _modelNormal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _position; +in vec4 _worldPosition; + +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2030 +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParamsInstanced(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + Material material = getMaterial(); + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float emissiveAmount = 0.0; + +#ifdef PROCEDURAL + +#ifdef PROCEDURAL_V1 + specular = getProceduralColor().rgb; + // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline + //specular = pow(specular, vec3(2.2)); + emissiveAmount = 1.0; +#else + emissiveAmount = getProceduralColors(diffuse, specular, shininess); +#endif + +#endif + + const float ALPHA_THRESHOLD = 0.999; + if (_color.a < ALPHA_THRESHOLD) { + if (emissiveAmount > 0.0) { + packDeferredFragmentTranslucent( + normal, + _color.a, + specular+fadeEmissive, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } else { + packDeferredFragmentTranslucent( + normal, + _color.a, + diffuse+fadeEmissive, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } + } else { + if (emissiveAmount > 0.0) { + packDeferredFragmentLightmap( + normal, + 1.0, + diffuse+fadeEmissive, + max(0, 1.0 - shininess / 128.0), + DEFAULT_METALLIC, + specular, + specular); + } else { + packDeferredFragment( + normal, + 1.0, + diffuse, + max(0, 1.0 - shininess / 128.0), + length(specular), + DEFAULT_EMISSIVE+fadeEmissive, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); + } + } +} diff --git a/libraries/render-utils/src/simple_fade.slv b/libraries/render-utils/src/simple_fade.slv new file mode 100644 index 0000000000..3d9eb2c812 --- /dev/null +++ b/libraries/render-utils/src/simple_fade.slv @@ -0,0 +1,44 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/04/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Fade.slh@> +<$declareFadeVertexInstanced()$> + +// the interpolated normal +out vec3 _normal; +out vec3 _modelNormal; +out vec4 _color; +out vec2 _texCoord0; +out vec4 _position; +out vec4 _worldPosition; + +void main(void) { + _color = colorToLinearRGBA(inColor); + _texCoord0 = inTexCoord0.st; + _position = inPosition; + _modelNormal = inNormal.xyz; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$passThroughFadeObjectParams()$> +} \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf new file mode 100644 index 0000000000..025fe5fca6 --- /dev/null +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -0,0 +1,66 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_textured_fade.slf +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Color.slh@> +<@include DeferredBufferWrite.slh@> +<@include model/Material.slh@> + +<@include Fade.slh@> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _worldPosition; + +// Declare after all samplers to prevent sampler location mix up with originalTexture +<$declareFadeFragmentInstanced()$> + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParamsInstanced(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + vec4 texel = texture(originalTexture, _texCoord0); + float colorAlpha = _color.a; + if (_color.a <= 0.0) { + texel = colorToLinearRGBA(texel); + colorAlpha = -_color.a; + } + + const float ALPHA_THRESHOLD = 0.999; + if (colorAlpha * texel.a < ALPHA_THRESHOLD) { + packDeferredFragmentTranslucent( + normalize(_normal), + colorAlpha * texel.a, + _color.rgb * texel.rgb + fadeEmissive, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } else { + packDeferredFragment( + normalize(_normal), + 1.0, + _color.rgb * texel.rgb, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE + fadeEmissive, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING); + } +} \ No newline at end of file diff --git a/libraries/render-utils/src/simple_textured_unlit_fade.slf b/libraries/render-utils/src/simple_textured_unlit_fade.slf new file mode 100644 index 0000000000..6f03c6746f --- /dev/null +++ b/libraries/render-utils/src/simple_textured_unlit_fade.slf @@ -0,0 +1,60 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_textured_unlit_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Color.slh@> +<@include DeferredBufferWrite.slh@> + +<@include Fade.slh@> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _worldPosition; + +// Declare after all samplers to prevent sampler location mix up with originalTexture +<$declareFadeFragmentInstanced()$> + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParamsInstanced(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + vec4 texel = texture(originalTexture, _texCoord0.st); + float colorAlpha = _color.a; + if (_color.a <= 0.0) { + texel = colorToLinearRGBA(texel); + colorAlpha = -_color.a; + } + + const float ALPHA_THRESHOLD = 0.999; + if (colorAlpha * texel.a < ALPHA_THRESHOLD) { + packDeferredFragmentTranslucent( + normalize(_normal), + colorAlpha * texel.a, + _color.rgb * texel.rgb+fadeEmissive, + DEFAULT_FRESNEL, + DEFAULT_ROUGHNESS); + } else { + packDeferredFragmentUnlit( + normalize(_normal), + 1.0, + _color.rgb * texel.rgb+fadeEmissive); + } +} \ No newline at end of file diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf new file mode 100644 index 0000000000..20c7907bbe --- /dev/null +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -0,0 +1,74 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_transparent_textured_fade.slf +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Color.slh@> + +<@include DeferredBufferWrite.slh@> +<@include DeferredGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +<@include Fade.slh@> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec4 _position; +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _worldPosition; + +// Declare after all samplers to prevent sampler location mix up with originalTexture +<$declareFadeFragmentInstanced()$> + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParamsInstanced(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + vec4 texel = texture(originalTexture, _texCoord0.st); + float opacity = _color.a; + if (_color.a <= 0.0) { + texel = colorToLinearRGBA(texel); + opacity = -_color.a; + } + opacity *= texel.a; + vec3 albedo = _color.rgb * texel.rgb; + + vec3 fragPosition = _position.xyz; + vec3 fragNormal = normalize(_normal); + + TransformCamera cam = getTransformCamera(); + + _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + fragNormal, + albedo, + DEFAULT_FRESNEL, + 0.0f, + fadeEmissive, + DEFAULT_ROUGHNESS, + opacity), + opacity); + +} \ No newline at end of file diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf new file mode 100644 index 0000000000..1c42a1f724 --- /dev/null +++ b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf @@ -0,0 +1,47 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// simple_transparent_textured_unlit_fade.slf +// fragment shader +// +// Created by Olivier Prat on 06/05/17. +// 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 +// + +<@include gpu/Color.slh@> + +<@include Fade.slh@> + +// the albedo texture +uniform sampler2D originalTexture; + +// the interpolated normal +in vec3 _normal; +in vec4 _color; +in vec2 _texCoord0; +in vec4 _worldPosition; + +layout(location = 0) out vec4 _fragColor0; + +// Declare after all samplers to prevent sampler location mix up with originalTexture +<$declareFadeFragmentInstanced()$> + +void main(void) { + vec3 fadeEmissive; + FadeObjectParams fadeParams; + + <$fetchFadeObjectParamsInstanced(fadeParams)$> + applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + + vec4 texel = texture(originalTexture, _texCoord0.st); + float colorAlpha = _color.a; + if (_color.a <= 0.0) { + texel = colorToLinearRGBA(texel); + colorAlpha = -_color.a; + } + _fragColor0 = vec4(_color.rgb * texel.rgb+fadeEmissive, colorAlpha * texel.a); +} \ No newline at end of file diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv new file mode 100644 index 0000000000..fa8e1f8991 --- /dev/null +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -0,0 +1,53 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _color; +out float _alpha; +out vec4 _worldPosition; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec3 interpolatedNormal = vec3(0.0, 0.0, 0.0); + + skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldPos(obj, position, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> +} diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv new file mode 100644 index 0000000000..4e638866fc --- /dev/null +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -0,0 +1,62 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_normal_map_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _tangent; +out vec3 _color; +out float _alpha; +out vec4 _worldPosition; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedTangent = vec4(0.0, 0.0, 0.0, 0.0); + + skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldPos(obj, position, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> + + _normal = interpolatedNormal.xyz; + _tangent = interpolatedTangent.xyz; +} diff --git a/libraries/render-utils/src/skin_model_shadow.slf b/libraries/render-utils/src/skin_model_shadow.slf index 178ea7b387..e464d6e6c8 100644 --- a/libraries/render-utils/src/skin_model_shadow.slf +++ b/libraries/render-utils/src/skin_model_shadow.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// model_shadow.frag +// skin_model_shadow.frag // fragment shader // // Created by Andrzej Kapolka on 3/24/14. diff --git a/libraries/render-utils/src/skin_model_shadow_fade.slf b/libraries/render-utils/src/skin_model_shadow_fade.slf new file mode 100644 index 0000000000..aaf29076b8 --- /dev/null +++ b/libraries/render-utils/src/skin_model_shadow_fade.slf @@ -0,0 +1,30 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_shadow_fade.frag +// fragment shader +// +// Created by Olivier Prat on 06/08/17. +// 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 +// + +<@include Fade.slh@> +<$declareFadeFragment()$> + +in vec4 _worldPosition; + +layout(location = 0) out vec4 _fragColor; + +void main(void) { + FadeObjectParams fadeParams; + + <$fetchFadeObjectParams(fadeParams)$> + applyFadeClip(fadeParams, _worldPosition.xyz); + + // pass-through to set z-buffer + _fragColor = vec4(1.0, 1.0, 1.0, 0.0); +} diff --git a/libraries/render-utils/src/skin_model_shadow_fade.slv b/libraries/render-utils/src/skin_model_shadow_fade.slv new file mode 100644 index 0000000000..7b27263569 --- /dev/null +++ b/libraries/render-utils/src/skin_model_shadow_fade.slv @@ -0,0 +1,32 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_shadow_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// 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 +// + +<@include gpu/Inputs.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> + +out vec4 _worldPosition; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + skinPosition(inSkinClusterIndex, inSkinClusterWeight, inPosition, position); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToWorldPos(obj, position, _worldPosition)$> +} diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 6a91081c95..12f9506286 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -119,9 +119,12 @@ namespace render { DebugFlags _debugFlags { RENDER_DEBUG_NONE }; gpu::Batch* _batch = nullptr; - uint32_t _globalShapeKey { 0 }; + uint32_t _globalShapeKey{ 0 }; + uint32_t _itemShapeKey{ 0 }; bool _enableTexturing { true }; + bool _enableFade{ false }; + RenderDetails _details; render::ScenePointer _scene; int8_t _cameraMode { -1 }; diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index a3175ffdec..8372231597 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -42,6 +42,7 @@ void render::renderItems(const RenderContextPointer& renderContext, const ItemBo void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, const Item& item, const ShapeKey& globalKey) { assert(item.getKey().isShape()); auto key = item.getShapeKey() | globalKey; + args->_itemShapeKey = key._flags.to_ulong(); if (key.isValid() && !key.hasOwnPipeline()) { args->_shapePipeline = shapeContext->pickPipeline(args, key); if (args->_shapePipeline) { @@ -54,6 +55,7 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons } else { qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key; } + args->_itemShapeKey = 0; } void render::renderShapes(const RenderContextPointer& renderContext, @@ -85,10 +87,10 @@ void render::renderStateSortShapes(const RenderContextPointer& renderContext, using SortedShapes = std::unordered_map, render::ShapeKey::Hash, render::ShapeKey::KeyEqual>; SortedPipelines sortedPipelines; SortedShapes sortedShapes; - std::vector ownPipelineBucket; + std::vector< std::tuple > ownPipelineBucket; for (auto i = 0; i < numItemsToDraw; ++i) { - auto item = scene->getItem(inItems[i].id); + auto& item = scene->getItem(inItems[i].id); { assert(item.getKey().isShape()); @@ -100,7 +102,7 @@ void render::renderStateSortShapes(const RenderContextPointer& renderContext, } bucket.push_back(item); } else if (key.hasOwnPipeline()) { - ownPipelineBucket.push_back(item); + ownPipelineBucket.push_back( std::make_tuple(item, key) ); } else { qCDebug(renderlogging) << "Item could not be rendered with invalid key" << key; } @@ -114,15 +116,19 @@ void render::renderStateSortShapes(const RenderContextPointer& renderContext, if (!args->_shapePipeline) { continue; } + args->_itemShapeKey = pipelineKey._flags.to_ulong(); for (auto& item : bucket) { args->_shapePipeline->prepareShapeItem(args, pipelineKey, item); item.render(args); } } args->_shapePipeline = nullptr; - for (auto& item : ownPipelineBucket) { + for (auto& itemAndKey : ownPipelineBucket) { + auto& item = std::get<0>(itemAndKey); + args->_itemShapeKey = std::get<1>(itemAndKey)._flags.to_ulong(); item.render(args); } + args->_itemShapeKey = 0; } void DrawLight::run(const RenderContextPointer& renderContext, const ItemBounds& inLights) { diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index f6b765cd9d..19953f3399 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -28,7 +28,7 @@ void FilterLayeredItems::run(const RenderContextPointer& renderContext, const It outItems.clear(); // For each item, filter it into one bucket - for (auto itemBound : inItems) { + for (auto& itemBound : inItems) { auto& item = scene->getItem(itemBound.id); if (item.getLayer() == _keepLayer) { outItems.emplace_back(itemBound); diff --git a/libraries/render/src/render/IndexedContainer.h b/libraries/render/src/render/IndexedContainer.h index bb1a9b72b7..4b51f8eb3c 100644 --- a/libraries/render/src/render/IndexedContainer.h +++ b/libraries/render/src/render/IndexedContainer.h @@ -12,7 +12,9 @@ #ifndef hifi_render_IndexedContainer_h #define hifi_render_IndexedContainer_h +#include #include +#include namespace render { namespace indexed_container { @@ -97,6 +99,10 @@ namespace indexed_container { return _elements[index]; } + bool isElementFreed(Index index) const { + return std::find(_allocator._freeIndices.begin(), _allocator._freeIndices.end(), index) != _allocator._freeIndices.end(); + } + const Element& get(Index index) const { return _elements[index]; } diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index f80563bf73..9e4fc09701 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -13,8 +13,13 @@ #include #include "gpu/Batch.h" +#include "TransitionStage.h" + using namespace render; +const Item::ID Item::INVALID_ITEM_ID = 0; +const ItemCell Item::INVALID_CELL = -1; + const Item::Status::Value Item::Status::Value::INVALID = Item::Status::Value(); const float Item::Status::Value::RED = 0.0f; @@ -78,3 +83,12 @@ void Item::resetPayload(const PayloadPointer& payload) { _key = _payload->getKey(); } } + +const ShapeKey Item::getShapeKey() const { + auto shapeKey = _payload->getShapeKey(); + if (!TransitionStage::isIndexInvalid(_transitionId)) { + // Objects that are fading are rendered double-sided to give a sense of volume + return ShapeKey::Builder(shapeKey).withFade().withoutCullFace(); + } + return shapeKey; +} \ No newline at end of file diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 007b34395d..16e9700a8f 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -28,9 +28,11 @@ #include "model/Material.h" #include "ShapePipeline.h" - namespace render { +typedef int32_t Index; +const Index INVALID_INDEX{ -1 }; + class Context; // Key is the KEY to filter Items and create specialized lists @@ -69,6 +71,7 @@ public: Flags _flags{ 0 }; public: Builder() {} + Builder(const ItemKey& key) : _flags{ key._flags } {} ItemKey build() const { return ItemKey(_flags); } @@ -241,8 +244,8 @@ public: typedef std::vector Vector; typedef ItemID ID; - static const ID INVALID_ITEM_ID = 0; - static const ItemCell INVALID_CELL = -1; + static const ID INVALID_ITEM_ID; + static const ItemCell INVALID_CELL; // Convenient function to clear an ID or check it s valid static void clearID(ID& id) { id = INVALID_ITEM_ID; } @@ -316,7 +319,6 @@ public: virtual const ItemKey getKey() const = 0; virtual const Bound getBound() const = 0; virtual int getLayer() const = 0; - virtual void render(RenderArgs* args) = 0; virtual const ShapeKey getShapeKey() const = 0; @@ -368,7 +370,7 @@ public: void render(RenderArgs* args) const { _payload->render(args); } // Shape Type Interface - const ShapeKey getShapeKey() const { return _payload->getShapeKey(); } + const ShapeKey getShapeKey() const; // Meta Type Interface uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); } @@ -376,10 +378,14 @@ public: // Access the status const StatusPointer& getStatus() const { return _payload->getStatus(); } + void setTransitionId(Index id) { _transitionId = id; } + Index getTransitionId() const { return _transitionId; } + protected: PayloadPointer _payload; ItemKey _key; ItemCell _cell{ INVALID_CELL }; + Index _transitionId{ INVALID_INDEX }; friend class Scene; }; @@ -436,7 +442,6 @@ public: virtual const Item::Bound getBound() const override { return payloadGetBound(_data); } virtual int getLayer() const override { return payloadGetLayer(_data); } - virtual void render(RenderArgs* args) override { payloadRender(_data, args); } // Shape Type interface diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index d735afa52d..b9f65f48a0 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -65,6 +65,5 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto overlayTransparents = task.addJob("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET]; - output = Varying(Output{{ - opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, background, spatialSelection }}); + output = Output(BucketList{ opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, background }, spatialSelection); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.h b/libraries/render/src/render/RenderFetchCullSortTask.h index f32293f001..b25480ae3a 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.h +++ b/libraries/render/src/render/RenderFetchCullSortTask.h @@ -26,12 +26,12 @@ public: OVERLAY_OPAQUE_SHAPE, OVERLAY_TRANSPARENT_SHAPE, BACKGROUND, - SPATIAL_SELECTION, NUM_BUCKETS }; - using Output = std::array; + using BucketList = render::VaryingArray; + using Output = render::VaryingSet2; using JobModel = render::Task::ModelO; RenderFetchCullSortTask() {} diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index b8f93c52c3..d2d3ad6de6 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -13,13 +13,16 @@ #include #include #include "Logging.h" +#include "TransitionStage.h" + +// Comment this to disable transitions (fades) +#define SCENE_ENABLE_TRANSITIONS using namespace render; void Transaction::resetItem(ItemID id, const PayloadPointer& payload) { if (payload) { - _resetItems.emplace_back(id); - _resetPayloads.emplace_back(payload); + _resetItems.emplace_back(Reset{ id, payload }); } else { qCDebug(renderlogging) << "WARNING: Transaction::resetItem with a null payload!"; removeItem(id); @@ -30,9 +33,24 @@ void Transaction::removeItem(ItemID id) { _removedItems.emplace_back(id); } +void Transaction::addTransitionToItem(ItemID id, Transition::Type transition, ItemID boundId) { + _addedTransitions.emplace_back(TransitionAdd{ id, transition, boundId }); +} + +void Transaction::removeTransitionFromItem(ItemID id) { + _addedTransitions.emplace_back(TransitionAdd{ id, Transition::NONE, render::Item::INVALID_ITEM_ID }); +} + +void Transaction::reApplyTransitionToItem(ItemID id) { + _reAppliedTransitions.emplace_back(TransitionReApply{ id }); +} + +void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) { + _queriedTransitions.emplace_back(TransitionQuery{ id, func }); +} + void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { - _updatedItems.emplace_back(id); - _updateFunctors.emplace_back(functor); + _updatedItems.emplace_back(Update{ id, functor }); } void Transaction::resetSelection(const Selection& selection) { @@ -41,11 +59,12 @@ void Transaction::resetSelection(const Selection& selection) { void Transaction::merge(const Transaction& transaction) { _resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end()); - _resetPayloads.insert(_resetPayloads.end(), transaction._resetPayloads.begin(), transaction._resetPayloads.end()); _removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end()); _updatedItems.insert(_updatedItems.end(), transaction._updatedItems.begin(), transaction._updatedItems.end()); - _updateFunctors.insert(_updateFunctors.end(), transaction._updateFunctors.begin(), transaction._updateFunctors.end()); _resetSelections.insert(_resetSelections.end(), transaction._resetSelections.begin(), transaction._resetSelections.end()); + _addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end()); + _queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end()); + _reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end()); } @@ -104,17 +123,23 @@ void Scene::processTransactionQueue() { // capture anything coming from the transaction // resets and potential NEW items - resetItems(consolidatedTransaction._resetItems, consolidatedTransaction._resetPayloads); + resetItems(consolidatedTransaction._resetItems); // Update the numItemsAtomic counter AFTER the reset changes went through _numAllocatedItems.exchange(maxID); // updates - updateItems(consolidatedTransaction._updatedItems, consolidatedTransaction._updateFunctors); + updateItems(consolidatedTransaction._updatedItems); // removes removeItems(consolidatedTransaction._removedItems); +#ifdef SCENE_ENABLE_TRANSITIONS + // add transitions + transitionItems(consolidatedTransaction._addedTransitions); + reApplyTransitions(consolidatedTransaction._reAppliedTransitions); + queryTransitionItems(consolidatedTransaction._queriedTransitions); +#endif // Update the numItemsAtomic counter AFTER the pending changes went through _numAllocatedItems.exchange(maxID); } @@ -127,34 +152,31 @@ void Scene::processTransactionQueue() { } } -void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) { - auto resetPayload = payloads.begin(); - for (auto resetID : ids) { +void Scene::resetItems(const Transaction::Resets& transactions) { + for (auto& reset : transactions) { // Access the true item - auto& item = _items[resetID]; + auto itemId = std::get<0>(reset); + auto& item = _items[itemId]; auto oldKey = item.getKey(); auto oldCell = item.getCell(); // Reset the item with a new payload - item.resetPayload(*resetPayload); + item.resetPayload(std::get<1>(reset)); auto newKey = item.getKey(); // Update the item's container assert((oldKey.isSpatial() == newKey.isSpatial()) || oldKey._flags.none()); if (newKey.isSpatial()) { - auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), resetID, newKey); + auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), itemId, newKey); item.resetCell(newCell, newKey.isSmall()); } else { - _masterNonspatialSet.insert(resetID); + _masterNonspatialSet.insert(itemId); } - - // next loop - resetPayload++; } } -void Scene::removeItems(const ItemIDs& ids) { - for (auto removedID :ids) { +void Scene::removeItems(const Transaction::Removes& transactions) { + for (auto removedID : transactions) { // Access the true item auto& item = _items[removedID]; auto oldCell = item.getCell(); @@ -167,17 +189,18 @@ void Scene::removeItems(const ItemIDs& ids) { _masterNonspatialSet.erase(removedID); } + // Remove the transition to prevent updating it for nothing + resetItemTransition(removedID); + // Kill it item.kill(); } } -void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) { - - auto updateFunctor = functors.begin(); - for (auto updateID : ids) { +void Scene::updateItems(const Transaction::Updates& transactions) { + for (auto& update : transactions) { + auto updateID = std::get<0>(update); if (updateID == Item::INVALID_ITEM_ID) { - updateFunctor++; continue; } @@ -187,7 +210,7 @@ void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) { auto oldKey = item.getKey(); // Update the item - item.update((*updateFunctor)); + item.update(std::get<1>(update)); auto newKey = item.getKey(); // Update the item's container @@ -209,10 +232,107 @@ void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) { _masterNonspatialSet.insert(updateID); } } + } +} +void Scene::transitionItems(const Transaction::TransitionAdds& transactions) { + auto transitionStage = getStage(TransitionStage::getName()); - // next loop - updateFunctor++; + for (auto& add : transactions) { + auto itemId = std::get<0>(add); + // Access the true item + const auto& item = _items[itemId]; + auto transitionId = item.getTransitionId(); + auto transitionType = std::get<1>(add); + auto boundId = std::get<2>(add); + + // Remove pre-existing transition, if need be + if (!TransitionStage::isIndexInvalid(transitionId)) { + transitionStage->removeTransition(transitionId); + transitionId = TransitionStage::INVALID_INDEX; + } + // Add a new one. + if (transitionType != Transition::NONE) { + transitionId = transitionStage->addTransition(itemId, transitionType, boundId); + } + + setItemTransition(itemId, transitionId); + } +} + +void Scene::reApplyTransitions(const Transaction::TransitionReApplies& transactions) { + for (auto itemId : transactions) { + // Access the true item + const auto& item = _items[itemId]; + auto transitionId = item.getTransitionId(); + setItemTransition(itemId, transitionId); + } +} + +void Scene::queryTransitionItems(const Transaction::TransitionQueries& transactions) { + auto transitionStage = getStage(TransitionStage::getName()); + + for (auto& query : transactions) { + auto itemId = std::get<0>(query); + // Access the true item + const auto& item = _items[itemId]; + auto func = std::get<1>(query); + if (item.exist() && func != nullptr) { + auto transitionId = item.getTransitionId(); + + if (!TransitionStage::isIndexInvalid(transitionId)) { + auto& transition = transitionStage->getTransition(transitionId); + func(itemId, &transition); + } else { + func(itemId, nullptr); + } + } + } +} + +void Scene::collectSubItems(ItemID parentId, ItemIDs& subItems) const { + // Access the true item + auto& item = _items[parentId]; + + if (item.exist()) { + // Recursivelly collect the subitems + auto subItemBeginIndex = subItems.size(); + auto subItemCount = item.fetchMetaSubItems(subItems); + for (auto i = subItemBeginIndex; i < (subItemBeginIndex + subItemCount); i++) { + auto subItemId = subItems[i]; + // Bizarrely, subItemId == parentId can happen for metas... See metaFetchMetaSubItems in RenderableEntityItem.cpp + if (subItemId != parentId) { + collectSubItems(subItemId, subItems); + } + } + } +} + +void Scene::setItemTransition(ItemID itemId, Index transitionId) { + // Access the true item + auto& item = _items[itemId]; + + item.setTransitionId(transitionId); + if (item.exist()) { + ItemIDs subItems; + + // Sub-items share the same transition Id + collectSubItems(itemId, subItems); + for (auto subItemId : subItems) { + // Curiously... this can happen + if (subItemId != itemId) { + setItemTransition(subItemId, transitionId); + } + } + } +} + +void Scene::resetItemTransition(ItemID itemId) { + auto& item = _items[itemId]; + if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { + auto transitionStage = getStage(TransitionStage::getName()); + transitionStage->removeTransition(item.getTransitionId()); + setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); } } @@ -227,8 +347,8 @@ Selection Scene::getSelection(const Selection::Name& name) const { } } -void Scene::resetSelections(const Selections& selections) { - for (auto selection : selections) { +void Scene::resetSelections(const Transaction::SelectionResets& transactions) { + for (auto selection : transactions) { auto found = _selections.find(selection.getName()); if (found == _selections.end()) { _selections.insert(SelectionMap::value_type(selection.getName(), selection)); diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index 199d9ce224..3b61a20f24 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -16,10 +16,12 @@ #include "SpatialTree.h" #include "Stage.h" #include "Selection.h" +#include "Transition.h" namespace render { class Engine; +class Scene; // Transaction is the mechanism to make any change to the scene. // Whenever a new item need to be reset, @@ -31,13 +33,23 @@ class Engine; // of updating the scene before it s rendered. // class Transaction { + friend class Scene; public: + + typedef std::function TransitionQueryFunc; + Transaction() {} ~Transaction() {} // Item transactions void resetItem(ItemID id, const PayloadPointer& payload); void removeItem(ItemID id); + bool hasRemovedItems() const { return !_removedItems.empty(); } + + void addTransitionToItem(ItemID id, Transition::Type transition, ItemID boundId = render::Item::INVALID_ITEM_ID); + void removeTransitionFromItem(ItemID id); + void reApplyTransitionToItem(ItemID id); + void queryTransitionOnItem(ItemID id, TransitionQueryFunc func); template void updateItem(ItemID id, std::function func) { updateItem(id, std::make_shared>(func)); @@ -54,15 +66,31 @@ public: // Checkers if there is work to do when processing the transaction bool touchTransactions() const { return !_resetSelections.empty(); } - ItemIDs _resetItems; - Payloads _resetPayloads; - ItemIDs _removedItems; - ItemIDs _updatedItems; - UpdateFunctors _updateFunctors; - - Selections _resetSelections; - protected: + + using Reset = std::tuple; + using Remove = ItemID; + using Update = std::tuple; + using TransitionAdd = std::tuple; + using TransitionQuery = std::tuple; + using TransitionReApply = ItemID; + using SelectionReset = Selection; + + using Resets = std::vector; + using Removes = std::vector; + using Updates = std::vector; + using TransitionAdds = std::vector; + using TransitionQueries = std::vector; + using TransitionReApplies = std::vector; + using SelectionResets = std::vector; + + Resets _resetItems; + Removes _removedItems; + Updates _updatedItems; + TransitionAdds _addedTransitions; + TransitionQueries _queriedTransitions; + TransitionReApplies _reAppliedTransitions; + SelectionResets _resetSelections; }; typedef std::queue TransactionQueue; @@ -123,8 +151,11 @@ public: } void resetStage(const Stage::Name& name, const StagePointer& stage); + void setItemTransition(ItemID id, Index transitionId); + void resetItemTransition(ItemID id); protected: + // Thread safe elements that can be accessed from anywhere std::atomic _IDAllocator{ 1 }; // first valid itemID will be One std::atomic _numAllocatedItems{ 1 }; // num of allocated items, matching the _items.size() @@ -138,15 +169,20 @@ protected: ItemSpatialTree _masterSpatialTree; ItemIDSet _masterNonspatialSet; - void resetItems(const ItemIDs& ids, Payloads& payloads); - void removeItems(const ItemIDs& ids); - void updateItems(const ItemIDs& ids, UpdateFunctors& functors); + void resetItems(const Transaction::Resets& transactions); + void removeItems(const Transaction::Removes& transactions); + void updateItems(const Transaction::Updates& transactions); + void transitionItems(const Transaction::TransitionAdds& transactions); + void reApplyTransitions(const Transaction::TransitionReApplies& transactions); + void queryTransitionItems(const Transaction::TransitionQueries& transactions); + + void collectSubItems(ItemID parentId, ItemIDs& subItems) const; // The Selection map mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method SelectionMap _selections; - void resetSelections(const Selections& selections); + void resetSelections(const Transaction::SelectionResets& transactions); // More actions coming to selections soon: // void removeFromSelection(const Selection& selection); // void appendToSelection(const Selection& selection); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index c83c0b44fc..762b7712d7 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -85,6 +85,8 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); slotBindings.insert(gpu::Shader::Binding(std::string("lightAmbientBuffer"), Slot::BUFFER::LIGHT_AMBIENT_BUFFER)); slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); gpu::Shader::makeProgram(*program, slotBindings); @@ -103,7 +105,9 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer"); locations->lightAmbientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer"); locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); - + locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); + locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); @@ -117,7 +121,7 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke PerformanceTimer perfTimer("ShapePlumber::pickPipeline"); - const auto& pipelineIterator = _pipelineMap.find(key); + auto pipelineIterator = _pipelineMap.find(key); if (pipelineIterator == _pipelineMap.end()) { // The first time we can't find a pipeline, we should try things to solve that if (_missingKeys.find(key) == _missingKeys.end()) { diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 13daac2db7..f0749504eb 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -36,6 +36,7 @@ public: DEPTH_BIAS, WIREFRAME, NO_CULL_FACE, + FADE, OWN_PIPELINE, INVALID, @@ -83,6 +84,7 @@ public: Builder& withDepthBias() { _flags.set(DEPTH_BIAS); return (*this); } Builder& withWireframe() { _flags.set(WIREFRAME); return (*this); } Builder& withoutCullFace() { _flags.set(NO_CULL_FACE); return (*this); } + Builder& withFade() { _flags.set(FADE); return (*this); } Builder& withOwnPipeline() { _flags.set(OWN_PIPELINE); return (*this); } Builder& invalidate() { _flags.set(INVALID); return (*this); } @@ -143,6 +145,9 @@ public: Builder& withCullFace() { _flags.reset(NO_CULL_FACE); _mask.set(NO_CULL_FACE); return (*this); } Builder& withoutCullFace() { _flags.set(NO_CULL_FACE); _mask.set(NO_CULL_FACE); return (*this); } + Builder& withFade() { _flags.set(FADE); _mask.set(FADE); return (*this); } + Builder& withoutFade() { _flags.reset(FADE); _mask.set(FADE); return (*this); } + Builder& withCustom(uint8_t custom) { _flags &= (~CUSTOM_MASK); _flags |= (custom << CUSTOM_0); _mask |= (CUSTOM_MASK); return (*this); } Builder& withoutCustom() { _flags &= (~CUSTOM_MASK); _mask |= (CUSTOM_MASK); return (*this); } @@ -170,6 +175,7 @@ public: bool isDepthBiased() const { return _flags[DEPTH_BIAS]; } bool isWireframe() const { return _flags[WIREFRAME]; } bool isCullFace() const { return !_flags[NO_CULL_FACE]; } + bool isFaded() const { return _flags[FADE]; } bool hasOwnPipeline() const { return _flags[OWN_PIPELINE]; } bool isValid() const { return !_flags[INVALID]; } @@ -209,6 +215,7 @@ inline QDebug operator<<(QDebug debug, const ShapeKey& key) { << "isDepthBiased:" << key.isDepthBiased() << "isWireframe:" << key.isWireframe() << "isCullFace:" << key.isCullFace() + << "isFaded:" << key.isFaded() << "]"; } } else { @@ -230,6 +237,7 @@ public: LIGHTING_MODEL, LIGHT, LIGHT_AMBIENT_BUFFER, + FADE_PARAMETERS, }; enum MAP { @@ -241,6 +249,7 @@ public: OCCLUSION, SCATTERING, LIGHT_AMBIENT, + FADE_MASK, }; }; @@ -259,6 +268,8 @@ public: int lightBufferUnit; int lightAmbientBufferUnit; int lightAmbientMapUnit; + int fadeMaskTextureUnit; + int fadeParameterBufferUnit; }; using LocationsPointer = std::shared_ptr; @@ -266,7 +277,7 @@ public: using ItemSetter = std::function; - ShapePipeline(gpu::PipelinePointer pipeline, LocationsPointer locations, BatchSetter batchSetter, ItemSetter itemSetter) : + ShapePipeline(gpu::PipelinePointer pipeline, LocationsPointer locations, BatchSetter batchSetter = nullptr, ItemSetter itemSetter = nullptr) : pipeline(pipeline), locations(locations), _batchSetter(batchSetter), diff --git a/libraries/render/src/render/Transition.h b/libraries/render/src/render/Transition.h new file mode 100644 index 0000000000..622e6f69ce --- /dev/null +++ b/libraries/render/src/render/Transition.h @@ -0,0 +1,51 @@ +// +// Transition.h + +// Created by Olivier Prat on 07/07/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_render_Transition_h +#define hifi_render_Transition_h + +#include "Item.h" + +namespace render { + + // This holds the current state for all transition event types applied to a render item + class Transition { + public: + + enum Type { + ELEMENT_ENTER_DOMAIN = 0, + ELEMENT_LEAVE_DOMAIN, + BUBBLE_ISECT_OWNER, + BUBBLE_ISECT_TRESPASSER, + USER_ENTER_DOMAIN, + USER_LEAVE_DOMAIN, + AVATAR_CHANGE, + + // Don't forget to modify Fade.slh to reflect the change in number of categories + TYPE_COUNT, + NONE = TYPE_COUNT + }; + + Type eventType{ ELEMENT_ENTER_DOMAIN }; + ItemID itemId{ Item::INVALID_ITEM_ID }; + ItemID boundItemId{ Item::INVALID_ITEM_ID }; + double time{ 0.0 }; + glm::vec3 noiseOffset{ 0.f, 0.f, 0.f }; + glm::vec3 baseOffset{ 0.f, 0.f, 0.f }; + glm::vec3 baseInvSize{ 1.f, 1.f, 1.f }; + float threshold{ 0.f }; + uint8_t isFinished{ 0 }; + }; + + typedef std::shared_ptr TransitionPointer; + typedef std::vector TransitionTypes; +} + +#endif // hifi_render_Transition_h \ No newline at end of file diff --git a/libraries/render/src/render/TransitionStage.cpp b/libraries/render/src/render/TransitionStage.cpp new file mode 100644 index 0000000000..33ef829c64 --- /dev/null +++ b/libraries/render/src/render/TransitionStage.cpp @@ -0,0 +1,42 @@ +#include "TransitionStage.h" + +#include + +using namespace render; + +std::string TransitionStage::_name("Transition"); + +TransitionStage::Index TransitionStage::addTransition(ItemID itemId, Transition::Type type, ItemID boundId) { + Transition transition; + Index id; + + transition.eventType = type; + transition.itemId = itemId; + transition.boundItemId = boundId; + id = _transitions.newElement(transition); + _activeTransitionIds.push_back(id); + + return id; +} + +void TransitionStage::removeTransition(Index index) { + TransitionIdList::iterator idIterator = std::find(_activeTransitionIds.begin(), _activeTransitionIds.end(), index); + if (idIterator != _activeTransitionIds.end()) { + _activeTransitionIds.erase(idIterator); + } + if (!_transitions.isElementFreed(index)) { + _transitions.freeElement(index); + } +} + +TransitionStageSetup::TransitionStageSetup() { +} + +void TransitionStageSetup::run(const RenderContextPointer& renderContext) { + auto stage = renderContext->_scene->getStage(TransitionStage::getName()); + if (!stage) { + stage = std::make_shared(); + renderContext->_scene->resetStage(TransitionStage::getName(), stage); + } +} + diff --git a/libraries/render/src/render/TransitionStage.h b/libraries/render/src/render/TransitionStage.h new file mode 100644 index 0000000000..8dfef1b78e --- /dev/null +++ b/libraries/render/src/render/TransitionStage.h @@ -0,0 +1,68 @@ +// +// TransitionStage.h + +// Created by Olivier Prat on 07/07/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_render_TransitionStage_h +#define hifi_render_TransitionStage_h + +#include "Stage.h" +#include "IndexedContainer.h" +#include "Engine.h" +#include "Transition.h" + +namespace render { + + // Transition stage to set up Transition-related effects + class TransitionStage : public render::Stage { + public: + + static const std::string& getName() { return _name; } + + using Index = indexed_container::Index; + static const Index INVALID_INDEX{ indexed_container::INVALID_INDEX }; + using TransitionIdList = indexed_container::Indices; + + static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } + + bool checkTransitionId(Index index) const { return _transitions.checkIndex(index); } + + const Transition& getTransition(Index TransitionId) const { return _transitions.get(TransitionId); } + + Transition& editTransition(Index TransitionId) { return _transitions.edit(TransitionId); } + + Index addTransition(ItemID itemId, Transition::Type type, ItemID boundId); + void removeTransition(Index index); + + TransitionIdList::iterator begin() { return _activeTransitionIds.begin(); } + TransitionIdList::iterator end() { return _activeTransitionIds.end(); } + + private: + + using Transitions = indexed_container::IndexedVector; + + static std::string _name; + + Transitions _transitions; + TransitionIdList _activeTransitionIds; + }; + using TransitionStagePointer = std::shared_ptr; + + class TransitionStageSetup { + public: + using JobModel = render::Job::Model; + + TransitionStageSetup(); + void run(const RenderContextPointer& renderContext); + + protected: + }; + +} + +#endif // hifi_render_TransitionStage_h diff --git a/libraries/render/src/task/Task.h b/libraries/render/src/task/Task.h index f76ba92546..e99b33305c 100644 --- a/libraries/render/src/task/Task.h +++ b/libraries/render/src/task/Task.h @@ -155,6 +155,12 @@ public: return concept->_data; } + template const T& get() const { + auto concept = std::static_pointer_cast(_concept); + assert(concept); + return concept->_data; + } + virtual void run(const ContextPointer& renderContext) { PerformanceTimer perfTimer(_name.c_str()); PROFILE_RANGE(render, _name.c_str()); diff --git a/libraries/render/src/task/Varying.h b/libraries/render/src/task/Varying.h index 50f4acd414..0144801701 100644 --- a/libraries/render/src/task/Varying.h +++ b/libraries/render/src/task/Varying.h @@ -40,6 +40,8 @@ public: template Varying getN (uint8_t index) const { return get()[index]; } template Varying editN (uint8_t index) { return edit()[index]; } + bool isNull() const { return _concept == nullptr; } + protected: class Concept { public: @@ -93,7 +95,7 @@ public: } virtual uint8_t length() const { return 2; } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; @@ -126,7 +128,7 @@ public: } virtual uint8_t length() const { return 3; } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; template @@ -163,7 +165,7 @@ public: } virtual uint8_t length() const { return 4; } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; @@ -206,7 +208,7 @@ public: } virtual uint8_t length() const { return 5; } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; template @@ -236,7 +238,7 @@ public: const T5& get5() const { return std::get<5>((*this)).template get(); } T5& edit5() { return std::get<5>((*this)).template edit(); } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; template @@ -269,7 +271,7 @@ public: const T6& get6() const { return std::get<6>((*this)).template get(); } T6& edit6() { return std::get<6>((*this)).template edit(); } - Varying hasVarying() const { return Varying((*this)); } + Varying asVarying() const { return Varying((*this)); } }; @@ -281,6 +283,11 @@ public: (*this)[i] = Varying(T()); } } + + VaryingArray(std::initializer_list list) { + assert(list.size() == NUM); + std::copy(list.begin(), list.end(), std::array::begin()); + } }; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 4ae907eb3b..6b9718fbb8 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -17,6 +17,7 @@ #include #include "NumericalConstants.h" +#include "GLMHelpers.h" glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) { // compute the projection of the point vector onto the segment vector @@ -657,3 +658,150 @@ bool findPlaneFromPoints(const glm::vec3* points, size_t numPoints, glm::vec3& p planeNormalOut = glm::normalize(dir); return true; } + +bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& planeB, const glm::vec4& planeC, glm::vec3& intersectionPointOut) { + glm::vec3 normalA(planeA); + glm::vec3 normalB(planeB); + glm::vec3 normalC(planeC); + glm::vec3 u = glm::cross(normalB, normalC); + float denom = glm::dot(normalA, u); + if (fabsf(denom) < EPSILON) { + return false; // planes do not intersect in a point. + } else { + intersectionPointOut = (planeA.w * u + glm::cross(normalA, planeC.w * normalB - planeB.w * normalC)) / denom; + return true; + } +} + +const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); +const int DOP14_COUNT = 14; +const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { + Vectors::UNIT_X, + -Vectors::UNIT_X, + Vectors::UNIT_Y, + -Vectors::UNIT_Y, + Vectors::UNIT_Z, + -Vectors::UNIT_Z, + glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) +}; + +typedef std::tuple Int3Tuple; +const std::tuple DOP14_PLANE_COMBINATIONS[] = { + Int3Tuple(0, 2, 4), Int3Tuple(0, 2, 5), Int3Tuple(0, 2, 6), Int3Tuple(0, 2, 7), Int3Tuple(0, 2, 8), Int3Tuple(0, 2, 9), Int3Tuple(0, 2, 10), Int3Tuple(0, 2, 11), Int3Tuple(0, 2, 12), Int3Tuple(0, 2, 13), + Int3Tuple(0, 3, 4), Int3Tuple(0, 3, 5), Int3Tuple(0, 3, 6), Int3Tuple(0, 3, 7), Int3Tuple(0, 3, 8), Int3Tuple(0, 3, 9), Int3Tuple(0, 3, 10), Int3Tuple(0, 3, 11), Int3Tuple(0, 3, 12), Int3Tuple(0, 3, 13), + Int3Tuple(0, 4, 6), Int3Tuple(0, 4, 7), Int3Tuple(0, 4, 8), Int3Tuple(0, 4, 9), Int3Tuple(0, 4, 10), Int3Tuple(0, 4, 11), Int3Tuple(0, 4, 12), Int3Tuple(0, 4, 13), + Int3Tuple(0, 5, 6), Int3Tuple(0, 5, 7), Int3Tuple(0, 5, 8), Int3Tuple(0, 5, 9), Int3Tuple(0, 5, 10), Int3Tuple(0, 5, 11), Int3Tuple(0, 5, 12), Int3Tuple(0, 5, 13), + Int3Tuple(0, 6, 8), Int3Tuple(0, 6, 9), Int3Tuple(0, 6, 10), Int3Tuple(0, 6, 11), Int3Tuple(0, 6, 12), Int3Tuple(0, 6, 13), + Int3Tuple(0, 7, 8), Int3Tuple(0, 7, 9), Int3Tuple(0, 7, 10), Int3Tuple(0, 7, 11), Int3Tuple(0, 7, 12), Int3Tuple(0, 7, 13), + Int3Tuple(0, 8, 10), Int3Tuple(0, 8, 11), Int3Tuple(0, 8, 12), Int3Tuple(0, 8, 13), Int3Tuple(0, 9, 10), + Int3Tuple(0, 9, 11), Int3Tuple(0, 9, 12), Int3Tuple(0, 9, 13), + Int3Tuple(0, 10, 12), Int3Tuple(0, 10, 13), + Int3Tuple(0, 11, 12), Int3Tuple(0, 11, 13), + Int3Tuple(1, 2, 4), Int3Tuple(1, 2, 5), Int3Tuple(1, 2, 6), Int3Tuple(1, 2, 7), Int3Tuple(1, 2, 8), Int3Tuple(1, 2, 9), Int3Tuple(1, 2, 10), Int3Tuple(1, 2, 11), Int3Tuple(1, 2, 12), Int3Tuple(1, 2, 13), + Int3Tuple(1, 3, 4), Int3Tuple(1, 3, 5), Int3Tuple(1, 3, 6), Int3Tuple(1, 3, 7), Int3Tuple(1, 3, 8), Int3Tuple(1, 3, 9), Int3Tuple(1, 3, 10), Int3Tuple(1, 3, 11), Int3Tuple(1, 3, 12), Int3Tuple(1, 3, 13), + Int3Tuple(1, 4, 6), Int3Tuple(1, 4, 7), Int3Tuple(1, 4, 8), Int3Tuple(1, 4, 9), Int3Tuple(1, 4, 10), Int3Tuple(1, 4, 11), Int3Tuple(1, 4, 12), Int3Tuple(1, 4, 13), + Int3Tuple(1, 5, 6), Int3Tuple(1, 5, 7), Int3Tuple(1, 5, 8), Int3Tuple(1, 5, 9), Int3Tuple(1, 5, 10), Int3Tuple(1, 5, 11), Int3Tuple(1, 5, 12), Int3Tuple(1, 5, 13), + Int3Tuple(1, 6, 8), Int3Tuple(1, 6, 9), Int3Tuple(1, 6, 10), Int3Tuple(1, 6, 11), Int3Tuple(1, 6, 12), Int3Tuple(1, 6, 13), + Int3Tuple(1, 7, 8), Int3Tuple(1, 7, 9), Int3Tuple(1, 7, 10), Int3Tuple(1, 7, 11), Int3Tuple(1, 7, 12), Int3Tuple(1, 7, 13), + Int3Tuple(1, 8, 10), Int3Tuple(1, 8, 11), Int3Tuple(1, 8, 12), Int3Tuple(1, 8, 13), + Int3Tuple(1, 9, 10), Int3Tuple(1, 9, 11), Int3Tuple(1, 9, 12), Int3Tuple(1, 9, 13), + Int3Tuple(1, 10, 12), Int3Tuple(1, 10, 13), + Int3Tuple(1, 11, 12), Int3Tuple(1, 11, 13), + Int3Tuple(2, 4, 6), Int3Tuple(2, 4, 7), Int3Tuple(2, 4, 8), Int3Tuple(2, 4, 9), Int3Tuple(2, 4, 10), Int3Tuple(2, 4, 11), Int3Tuple(2, 4, 12), Int3Tuple(2, 4, 13), + Int3Tuple(2, 5, 6), Int3Tuple(2, 5, 7), Int3Tuple(2, 5, 8), Int3Tuple(2, 5, 9), Int3Tuple(2, 5, 10), Int3Tuple(2, 5, 11), Int3Tuple(2, 5, 12), Int3Tuple(2, 5, 13), + Int3Tuple(2, 6, 8), Int3Tuple(2, 6, 9), Int3Tuple(2, 6, 10), Int3Tuple(2, 6, 11), Int3Tuple(2, 6, 12), Int3Tuple(2, 6, 13), + Int3Tuple(2, 7, 8), Int3Tuple(2, 7, 9), Int3Tuple(2, 7, 10), Int3Tuple(2, 7, 11), Int3Tuple(2, 7, 12), Int3Tuple(2, 7, 13), + Int3Tuple(2, 8, 10), Int3Tuple(2, 8, 11), Int3Tuple(2, 8, 12), Int3Tuple(2, 8, 13), + Int3Tuple(2, 9, 10), Int3Tuple(2, 9, 11), Int3Tuple(2, 9, 12), Int3Tuple(2, 9, 13), + Int3Tuple(2, 10, 12), Int3Tuple(2, 10, 13), + Int3Tuple(2, 11, 12), Int3Tuple(2, 11, 13), + Int3Tuple(3, 4, 6), Int3Tuple(3, 4, 7), Int3Tuple(3, 4, 8), Int3Tuple(3, 4, 9), Int3Tuple(3, 4, 10), Int3Tuple(3, 4, 11), Int3Tuple(3, 4, 12), Int3Tuple(3, 4, 13), + Int3Tuple(3, 5, 6), Int3Tuple(3, 5, 7), Int3Tuple(3, 5, 8), Int3Tuple(3, 5, 9), Int3Tuple(3, 5, 10), Int3Tuple(3, 5, 11), Int3Tuple(3, 5, 12), Int3Tuple(3, 5, 13), + Int3Tuple(3, 6, 8), Int3Tuple(3, 6, 9), Int3Tuple(3, 6, 10), Int3Tuple(3, 6, 11), Int3Tuple(3, 6, 12), Int3Tuple(3, 6, 13), + Int3Tuple(3, 7, 8), Int3Tuple(3, 7, 9), Int3Tuple(3, 7, 10), Int3Tuple(3, 7, 11), Int3Tuple(3, 7, 12), Int3Tuple(3, 7, 13), + Int3Tuple(3, 8, 10), Int3Tuple(3, 8, 11), Int3Tuple(3, 8, 12), Int3Tuple(3, 8, 13), + Int3Tuple(3, 9, 10), Int3Tuple(3, 9, 11), Int3Tuple(3, 9, 12), Int3Tuple(3, 9, 13), + Int3Tuple(3, 10, 12), Int3Tuple(3, 10, 13), + Int3Tuple(3, 11, 12), Int3Tuple(3, 11, 13), + Int3Tuple(4, 6, 8), Int3Tuple(4, 6, 9), Int3Tuple(4, 6, 10), Int3Tuple(4, 6, 11), Int3Tuple(4, 6, 12), Int3Tuple(4, 6, 13), + Int3Tuple(4, 7, 8), Int3Tuple(4, 7, 9), Int3Tuple(4, 7, 10), Int3Tuple(4, 7, 11), Int3Tuple(4, 7, 12), Int3Tuple(4, 7, 13), + Int3Tuple(4, 8, 10), Int3Tuple(4, 8, 11), Int3Tuple(4, 8, 12), Int3Tuple(4, 8, 13), + Int3Tuple(4, 9, 10), Int3Tuple(4, 9, 11), Int3Tuple(4, 9, 12), Int3Tuple(4, 9, 13), + Int3Tuple(4, 10, 12), Int3Tuple(4, 10, 13), + Int3Tuple(4, 11, 12), Int3Tuple(4, 11, 13), + Int3Tuple(5, 6, 8), Int3Tuple(5, 6, 9), Int3Tuple(5, 6, 10), Int3Tuple(5, 6, 11), Int3Tuple(5, 6, 12), Int3Tuple(5, 6, 13), + Int3Tuple(5, 7, 8), Int3Tuple(5, 7, 9), Int3Tuple(5, 7, 10), Int3Tuple(5, 7, 11), Int3Tuple(5, 7, 12), Int3Tuple(5, 7, 13), + Int3Tuple(5, 8, 10), Int3Tuple(5, 8, 11), Int3Tuple(5, 8, 12), Int3Tuple(5, 8, 13), + Int3Tuple(5, 9, 10), Int3Tuple(5, 9, 11), Int3Tuple(5, 9, 12), Int3Tuple(5, 9, 13), + Int3Tuple(5, 10, 12), Int3Tuple(5, 10, 13), + Int3Tuple(5, 11, 12), Int3Tuple(5, 11, 13), + Int3Tuple(6, 8, 10), Int3Tuple(6, 8, 11), Int3Tuple(6, 8, 12), Int3Tuple(6, 8, 13), + Int3Tuple(6, 9, 10), Int3Tuple(6, 9, 11), Int3Tuple(6, 9, 12), Int3Tuple(6, 9, 13), + Int3Tuple(6, 10, 12), Int3Tuple(6, 10, 13), + Int3Tuple(6, 11, 12), Int3Tuple(6, 11, 13), + Int3Tuple(7, 8, 10), Int3Tuple(7, 8, 11), Int3Tuple(7, 8, 12), Int3Tuple(7, 8, 13), + Int3Tuple(7, 9, 10), Int3Tuple(7, 9, 11), Int3Tuple(7, 9, 12), Int3Tuple(7, 9, 13), + Int3Tuple(7, 10, 12), Int3Tuple(7, 10, 13), + Int3Tuple(7, 11, 12), Int3Tuple(7, 11, 13), + Int3Tuple(8, 10, 12), Int3Tuple(8, 10, 13), + Int3Tuple(8, 11, 12), Int3Tuple(8, 11, 13), + Int3Tuple(9, 10, 12), Int3Tuple(9, 10, 13), + Int3Tuple(9, 11, 12), Int3Tuple(9, 11, 13) +}; + +void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut) { + if (dots.size() != DOP14_COUNT) { + return; + } + + // iterate over all purmutations of non-parallel planes. + // find all the vertices that lie on the surface of the k-dop + std::vector vertices; + for (auto& tuple : DOP14_PLANE_COMBINATIONS) { + int i = std::get<0>(tuple); + int j = std::get<1>(tuple); + int k = std::get<2>(tuple); + glm::vec4 planeA(DOP14_NORMALS[i], dots[i]); + glm::vec4 planeB(DOP14_NORMALS[j], dots[j]); + glm::vec4 planeC(DOP14_NORMALS[k], dots[k]); + glm::vec3 intersectionPoint; + const float IN_FRONT_MARGIN = 0.01f; + if (findIntersectionOfThreePlanes(planeA, planeB, planeC, intersectionPoint)) { + bool inFront = false; + for (int p = 0; p < DOP14_COUNT; p++) { + if (glm::dot(DOP14_NORMALS[p], intersectionPoint) > dots[p] + IN_FRONT_MARGIN) { + inFront = true; + } + } + if (!inFront) { + vertices.push_back(intersectionPoint); + } + } + } + + // build a set of lines between these vertices, that also lie on the surface of the k-dop. + for (size_t i = 0; i < vertices.size(); i++) { + for (size_t j = i; j < vertices.size(); j++) { + glm::vec3 midPoint = (vertices[i] + vertices[j]) * 0.5f; + int onSurfaceCount = 0; + const float SURFACE_MARGIN = 0.01f; + for (int p = 0; p < DOP14_COUNT; p++) { + float d = glm::dot(DOP14_NORMALS[p], midPoint); + if (d > dots[p] - SURFACE_MARGIN && d < dots[p] + SURFACE_MARGIN) { + onSurfaceCount++; + } + } + if (onSurfaceCount > 1) { + linesOut.push_back(vertices[i] + center); + linesOut.push_back(vertices[j] + center); + } + } + } +} diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index a5ee67748b..eb9424d938 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -13,6 +13,7 @@ #define hifi_GeometryUtil_h #include +#include glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end); @@ -166,4 +167,10 @@ private: // given a set of points, compute a best fit plane that passes as close as possible through all the points. bool findPlaneFromPoints(const glm::vec3* points, size_t numPoints, glm::vec3& planeNormalOut, glm::vec3& pointOnPlaneOut); +// plane equation is specified by ax + by + cz + d = 0. +// the coefficents are passed in as a vec4. (a, b, c, d) +bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& planeB, const glm::vec4& planeC, glm::vec3& intersectionPointOut); + +void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); + #endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/shared/types/AnimationLoop.h b/libraries/shared/src/shared/types/AnimationLoop.h index 604e62446a..33434209e7 100644 --- a/libraries/shared/src/shared/types/AnimationLoop.h +++ b/libraries/shared/src/shared/types/AnimationLoop.h @@ -35,9 +35,6 @@ public: void setHold(bool hold) { _hold = hold; } bool getHold() const { return _hold; } - void setAllowTranslation(bool value) { _allowTranslation = value; } - bool getAllowTranslation() const { return _allowTranslation; } - void setStartAutomatically(bool startAutomatically); bool getStartAutomatically() const { return _startAutomatically; } @@ -70,7 +67,6 @@ private: float _fps; bool _loop; bool _hold; - bool _allowTranslation; bool _startAutomatically; float _firstFrame; float _lastFrame; diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index 58aeb6c88f..55880584a8 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -6,11 +6,13 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -# FIXME if we want to re-enable this, we need to fix the mechanism for installing the +# FIXME if we want to properly re-enable this, we need to fix the mechanism for installing the # dependency dlls `msvcr100` and `msvcp100` WITHOUT using CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS -#if (NOT ANDROID) -# set(TARGET_NAME hifiSixense) -# setup_hifi_plugin(Script Qml Widgets) -# link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) -# target_sixense() -#endif () +# currently this will build the hifiSixense plugin, but unless the user has previously installed +# the msvcr100 runtime support, the plugin will not load. +if (NOT ANDROID) + set(TARGET_NAME hifiSixense) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) + target_sixense() +endif () diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 996cf4b34c..021365686a 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -38,11 +38,12 @@ Item { } Label { + id: labelValue text: sliderControl.value.toFixed(root.integral ? 0 : 2) anchors.left: root.left anchors.leftMargin: 200 anchors.top: root.top - anchors.topMargin: 7 + anchors.topMargin: 15 } Binding { @@ -56,7 +57,7 @@ Item { Slider { id: sliderControl stepSize: root.integral ? 1.0 : 0.0 - width: 150 + width: root.width-130 height: 20 anchors.right: root.right anchors.rightMargin: 8 diff --git a/scripts/developer/utilities/render/debugFade.js b/scripts/developer/utilities/render/debugFade.js new file mode 100644 index 0000000000..b01c4b5e1f --- /dev/null +++ b/scripts/developer/utilities/render/debugFade.js @@ -0,0 +1,21 @@ +// +// debugFade.js +// developer/utilities/render +// +// Olivier Prat, created on 30/04/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 +// + +// Set up the qml ui +var qml = Script.resolvePath('fade.qml'); +var window = new OverlayWindow({ + title: 'Fade', + source: qml, + width: 910, + height: 610, +}); +window.setPosition(50, 50); +window.closed.connect(function() { Script.stop(); }); diff --git a/scripts/developer/utilities/render/fade.qml b/scripts/developer/utilities/render/fade.qml new file mode 100644 index 0000000000..1dffd0fbbb --- /dev/null +++ b/scripts/developer/utilities/render/fade.qml @@ -0,0 +1,397 @@ +// +// fade.qml +// developer/utilities/render +// +// Olivier Prat, created on 30/04/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "configSlider" +import "../lib/plotperf" + +Column { + id: root + property var config: Render.getConfig("RenderMainView.Fade"); + property var configEdit: Render.getConfig("RenderMainView.FadeEdit"); + spacing: 8 + + Row { + spacing: 8 + + CheckBox { + text: "Edit Fade" + checked: root.configEdit["editFade"] + onCheckedChanged: { + root.configEdit["editFade"] = checked; + Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked; + } + } + ComboBox { + id: categoryBox + width: 400 + model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"] + Timer { + id: postpone + interval: 100; running: false; repeat: false + onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets } + } + onCurrentIndexChanged: { + root.config["editedCategory"] = currentIndex; + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 100ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 100 + postpone.start() + } + } + } + Row { + spacing: 8 + + CheckBox { + text: "Manual" + checked: root.config["manualFade"] + onCheckedChanged: { + root.config["manualFade"] = checked; + } + } + ConfigSlider { + label: "Threshold" + integral: false + config: root.config + property: "manualThreshold" + max: 1.0 + min: 0.0 + width: 400 + } + } + + Action { + id: saveAction + text: "Save" + onTriggered: { + root.config.save() + } + } + Action { + id: loadAction + text: "Load" + onTriggered: { + root.config.load() + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 500ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 500 + postpone.start() + } + } + + Component { + id: paramWidgets + + Column { + spacing: 8 + + CheckBox { + text: "Invert" + checked: root.config["isInverted"] + onCheckedChanged: { root.config["isInverted"] = checked } + } + Row { + spacing: 8 + + GroupBox { + title: "Base Gradient" + width: 450 + Column { + spacing: 8 + + ConfigSlider { + label: "Size X" + integral: false + config: root.config + property: "baseSizeX" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Size Y" + integral: false + config: root.config + property: "baseSizeY" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Size Z" + integral: false + config: root.config + property: "baseSizeZ" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Level" + integral: false + config: root.config + property: "baseLevel" + max: 1.0 + min: 0.0 + width: 400 + } + } + } + GroupBox { + title: "Noise Gradient" + width: 450 + Column { + spacing: 8 + + ConfigSlider { + label: "Size X" + integral: false + config: root.config + property: "noiseSizeX" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Size Y" + integral: false + config: root.config + property: "noiseSizeY" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Size Z" + integral: false + config: root.config + property: "noiseSizeZ" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Level" + integral: false + config: root.config + property: "noiseLevel" + max: 1.0 + min: 0.0 + width: 400 + } + } + } + } + Row { + spacing: 8 + + GroupBox { + title: "Edge" + width: 450 + Column { + spacing: 8 + + ConfigSlider { + label: "Width" + integral: false + config: root.config + property: "edgeWidth" + max: 1.0 + min: 0.0 + width: 400 + } + GroupBox { + title: "Inner color" + Column { + spacing: 8 + ConfigSlider { + label: "Color R" + integral: false + config: root.config + property: "edgeInnerColorR" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color G" + integral: false + config: root.config + property: "edgeInnerColorG" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color B" + integral: false + config: root.config + property: "edgeInnerColorB" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color intensity" + integral: false + config: root.config + property: "edgeInnerIntensity" + max: 5.0 + min: 0.0 + width: 400 + } + } + } + GroupBox { + title: "Outer color" + Column { + spacing: 8 + ConfigSlider { + label: "Color R" + integral: false + config: root.config + property: "edgeOuterColorR" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color G" + integral: false + config: root.config + property: "edgeOuterColorG" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color B" + integral: false + config: root.config + property: "edgeOuterColorB" + max: 1.0 + min: 0.0 + width: 400 + } + ConfigSlider { + label: "Color intensity" + integral: false + config: root.config + property: "edgeOuterIntensity" + max: 5.0 + min: 0.0 + width: 400 + } + } + } + } + } + + Column { + GroupBox { + title: "Timing" + width: 450 + Column { + spacing: 8 + + ConfigSlider { + label: "Duration" + integral: false + config: root.config + property: "duration" + max: 10.0 + min: 0.1 + width: 400 + } + ComboBox { + width: 400 + model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"] + currentIndex: root.config["timing"] + onCurrentIndexChanged: { + root.config["timing"] = currentIndex; + } + } + GroupBox { + title: "Noise Animation" + Column { + spacing: 8 + ConfigSlider { + label: "Speed X" + integral: false + config: root.config + property: "noiseSpeedX" + max: 1.0 + min: -1.0 + width: 400 + } + ConfigSlider { + label: "Speed Y" + integral: false + config: root.config + property: "noiseSpeedY" + max: 1.0 + min: -1.0 + width: 400 + } + ConfigSlider { + label: "Speed Z" + integral: false + config: root.config + property: "noiseSpeedZ" + max: 1.0 + min: -1.0 + width: 400 + } + } + } + + PlotPerf { + title: "Threshold" + height: parent.evalEvenHeight() + object: config + valueUnit: "%" + valueScale: 0.01 + valueNumDigits: "1" + plots: [ + { + prop: "threshold", + label: "Threshold", + color: "#FFBB77" + } + ] + } + + } + } + + Row { + spacing: 8 + Button { + action: saveAction + } + Button { + action: loadAction + } + } + + } + } + } + } + + Loader { + id: paramWidgetLoader + sourceComponent: paramWidgets + } +} diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c141c7cd52..7ee6c64858 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1228,7 +1228,7 @@ Script.scriptEnding.connect(function () { Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); - Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates); + Messages.messageReceived.disconnect(handleMessagesReceived); Messages.unsubscribe("entityToolUpdates"); Messages.unsubscribe("Toolbar-DomainChanged"); createButton = null; diff --git a/scripts/system/help.js b/scripts/system/help.js index 1265a5597b..9ab7fa3fb3 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -61,6 +61,7 @@ tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); Script.clearInterval(interval); if (tablet) { tablet.removeButton(button); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 5ba5568b59..6aacaa5333 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,8 +448,68 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } +.shape-section, .light-section, .model-section, .web-section, .hyperlink-section, .text-section, .zone-section { + display: table; +} -.section-header, .sub-section-header, hr { + + +#properties-list fieldset { + position: relative; + /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ + margin: 21px -21px 0px -21px; + padding: 0.1px 21px 0px 21px; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0px -1px 0px rgb(37,37,37); +} + +#properties-list fieldset.fstuple, #properties-list fieldset.fsrow { + margin-top: 21px; + border: none; + box-shadow: none; +} + +#properties-list > fieldset[data-collapsed="true"] + fieldset { + margin-top: 0px; +} + +#properties-list > fieldset[data-collapsed="true"] > *:not(legend) { + display: none !important; +} + +#properties-list legend + fieldset { + margin-top: 0px; + border: none; + box-shadow: none; +} + +#properties-list > fieldset#properties-header { + margin-top: 0px; + padding-bottom: 0px; +} + + + +#properties-list > fieldset > legend { + position: relative; + display: table; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; + background-color: #404040; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0 -1px 0 rgb(37,37,37), 0 4px 4px 0 rgba(0,0,0,0.75); +} + +div.section-header, .sub-section-header, hr { display: table; width: 100%; margin: 21px -21px 0 -21px; @@ -462,16 +522,18 @@ input[type=checkbox]:checked + label:hover { outline: none; } -.section-header { - position: relative; - background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAqCAIAAAAbNW1vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAmSURBVChTY1BFAgzhSIDBAQmMcoYHRwIJMCgjAQZ9JMBgBQdWVgBh5XmBV5A2FQAAAABJRU5ErkJggg==) repeat-x top left; + + +.column .sub-section-header { + background-image: none; + padding-top: 0; } .sub-section-header, .no-collapse, hr { background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; } -.section-header:first-child { +div.section-header:first-child { margin-top: -2px; padding-top: 0; background: none; @@ -482,7 +544,7 @@ input[type=checkbox]:checked + label:hover { margin-bottom: -10px; } -.section-header span { +#properties-list > fieldset > legend span, .section-header span { font-family: HiFi-Glyphs; font-size: 30px; float: right; @@ -536,6 +598,18 @@ hr { font-size: 13px; } +.property legend, .number legend { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property legend .unit, .number legend .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + .value { display: block; min-height: 18px; @@ -545,6 +619,11 @@ hr { vertical-align: top; width: 48px; } +.value legend { + display: inline-block; + vertical-align: top; + width: 48px; +} .value span { font-family: FiraSans-SemiBold; font-size: 15px; @@ -572,6 +651,13 @@ hr { margin-top: -2px; } +.text legend, .url legend, .number legend, .textarea legend, .rgb legend, .xyz legend, .pyr legend, .dropdown legend, .gen legend { + float: left; + margin-left: 1px; + margin-bottom: 3px; + margin-top: -2px; +} + .number > input { clear: both; float: left; @@ -714,6 +800,14 @@ div.refresh input[type="button"] { display: none !important; } +#property-color-control1 { + display: table-cell; + float: none; +} + +#property-color-control1 + label { + border-left: 20px transparent solid; +} .rgb label { float: left; @@ -724,6 +818,15 @@ div.refresh input[type="button"] { clear: both; } +.rgb legend { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb legend + * { + clear: both; +} + .tuple div { display: inline-block; position: relative; @@ -813,6 +916,42 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { display: table-cell; width: 50%; } + +#properties-list fieldset .two-column { + padding-top:21px; + display: flex; +} + +#properties-list .two-column fieldset { + /*display: table-cell;*/ + width: 50%; + margin: 0; + padding: 0; + border-top: none; + box-shadow: none; +} + +#properties-list .two-column fieldset legend { + display: table; + width: 100%; + margin: 21px -21px 0px -21px; + padding: 0px 0px 0px 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; +} + +fieldset .checkbox-sub-props { + margin-top: 0; + } + +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + .column { vertical-align: top; } @@ -1154,9 +1293,11 @@ th#entity-hasScript { } -#properties-header { +#properties-list #properties-header { display: table-row; height: 28px; + border-top: none; + box-shadow: none; } #properties-header .property { @@ -1261,3 +1402,260 @@ input#reset-to-natural-dimensions { font-size:16px; display:none; } + +#properties-list #collision-info > fieldset:first-of-type { + border-top: none !important; + box-shadow: none; + margin-top: 0; +} + +#properties-list { + display: flex; + flex-direction: column; +} + +/* ----- Order of Menu items for Primitive ----- */ +#properties-list.ShapeMenu #general, +#properties-list.BoxMenu #general, +#properties-list.SphereMenu #general { + order: 1; +} + +#properties-list.ShapeMenu #collision-info, +#properties-list.BoxMenu #collision-info, +#properties-list.SphereMenu #collision-info { + order: 2; +} + +#properties-list.ShapeMenu #physical, +#properties-list.BoxMenu #physical, +#properties-list.SphereMenu #physical { + order: 3; +} + +#properties-list.ShapeMenu #spatial, +#properties-list.BoxMenu #spatial, +#properties-list.SphereMenu #spatial { + order: 4; +} + +#properties-list.ShapeMenu #behavior, +#properties-list.BoxMenu #behavior, +#properties-list.SphereMenu #behavior { + order: 5; +} + +#properties-list.ShapeMenu #hyperlink, +#properties-list.BoxMenu #hyperlink, +#properties-list.SphereMenu #hyperlink { + order: 6; +} + +#properties-list.ShapeMenu #light, +#properties-list.BoxMenu #light, +#properties-list.SphereMenu #light, +#properties-list.ShapeMenu #model, +#properties-list.BoxMenu #model, +#properties-list.SphereMenu #model, +#properties-list.ShapeMenu #zone, +#properties-list.BoxMenu #zone, +#properties-list.SphereMenu #zone, +#properties-list.ShapeMenu #text, +#properties-list.BoxMenu #text, +#properties-list.SphereMenu #text, +#properties-list.ShapeMenu #web, +#properties-list.BoxMenu #web, +#properties-list.SphereMenu #web { + display: none; +} + + +/* ----- Order of Menu items for Light ----- */ +#properties-list.LightMenu #general { + order: 1; +} +#properties-list.LightMenu #light { + order: 2; +} +#properties-list.LightMenu #physical { + order: 3; +} +#properties-list.LightMenu #spatial { + order: 4; +} +#properties-list.LightMenu #behavior { + order: 5; +} +#properties-list.LightMenu #collision-info { + order: 6; +} +#properties-list.LightMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.LightMenu #model, +#properties-list.LightMenu #zone, +#properties-list.LightMenu #text, +#properties-list.LightMenu #web { + display: none; +} +/* items to hide */ +#properties-list.LightMenu .shape-group.shape-section.property.dropdown, +#properties-list.LightMenu color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Model ----- */ +#properties-list.ModelMenu #general { + order: 1; +} +#properties-list.ModelMenu #model { + order: 2; +} +#properties-list.ModelMenu #collision-info { + order: 3; +} +#properties-list.ModelMenu #physical { + order: 4; +} +#properties-list.ModelMenu #spatial { + order: 5; +} +#properties-list.ModelMenu #behavior { + order: 6; +} +#properties-list.ModelMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ModelMenu #light, +#properties-list.ModelMenu #zone, +#properties-list.ModelMenu #text, +#properties-list.ModelMenu #web { + display: none; +} +/* items to hide */ +#properties-list.ModelMenu .shape-group.shape-section.property.dropdown, +#properties-list.ModelMenu .color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Zone ----- */ +#properties-list.ZoneMenu #general { + order: 1; +} +#properties-list.ZoneMenu #zone { + order: 2; +} +#properties-list.ZoneMenu #physical { + order: 3; +} +#properties-list.ZoneMenu #spatial { + order: 4; +} +#properties-list.ZoneMenu #behavior { + order: 5; +} +#properties-list.ZoneMenu #collision-info { + order: 6; +} +#properties-list.ZoneMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ZoneMenu #light, +#properties-list.ZoneMenu #model, +#properties-list.ZoneMenu #text, +#properties-list.ZoneMenu #web { + display: none; +} +/* items to hide */ +#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown, +#properties-list.ZoneMenu .color-section.color-control1 { + display: none +} + + +/* ----- Order of Menu items for Web ----- */ +#properties-list.WebMenu #general { + order: 1; +} +#properties-list.WebMenu #web { + order: 2; +} +#properties-list.WebMenu #collision-info { + order: 3; +} +#properties-list.WebMenu #physical { + order: 4; +} +#properties-list.WebMenu #spatial { + order: 5; +} +#properties-list.WebMenu #behavior { + order: 6; +} +#properties-list.WebMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.WebMenu #light, +#properties-list.WebMenu #model, +#properties-list.WebMenu #zone, +#properties-list.WebMenu #text { + display: none; +} +/* items to hide */ +#properties-list.WebMenu .shape-group.shape-section.property.dropdown, +#properties-list.WebMenu .color-section.color-control1 { + display: none; +} + + + +/* ----- Order of Menu items for Text ----- */ +#properties-list.TextMenu #general { + order: 1; +} +#properties-list.TextMenu #text { + order: 2; +} +#properties-list.TextMenu #collision-info { + order: 3; +} +#properties-list.TextMenu #physical { + order: 4; +} +#properties-list.TextMenu #spatial { + order: 5; +} +#properties-list.TextMenu #behavior { + order: 6; +} +#properties-list.TextMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.TextMenu #light, +#properties-list.TextMenu #model, +#properties-list.TextMenu #zone, +#properties-list.TextMenu #web { + display: none; +} +/* items to hide */ +#properties-list.TextMenu .shape-group.shape-section.property.dropdown, +#properties-list.TextMenu .color-section.color-control1 { + display: none +} + + +/* Currently always hidden */ +#properties-list #polyvox { + display: none; +} + +.skybox-section { + display: none; +} \ No newline at end of file diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b0676859a0..b95f774d0f 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -8,130 +8,266 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html --> - - Properties - - - - - - - - - - - - - - -
-
-
- -
-
- - -
-
- - -
+ + + Properties + + + + + + + + + + + + + + + +
+ +
+
+
-
+
+ + +
+
+ + +
+
+ + +
-
- - +
+
+
- -
- -
-
- - - - Saved! +
+
+
+
+ +
-
-
- -
-
- - -
- - -
- M -
-
- +
+ + +
+ +
+
+
+ + Collides With + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + Grabbing + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+ + + +
+ + PhysicalM + +
+
+ Linear velocity m/s +
+
+
+
+
+
+
+ + +
+
+
+
+ Angular velocity deg/s +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ Gravity m/s2 +
+
+
+
+
+
+
+ Acceleration m/s2 +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ + SpatialM + +
+ Position m
-
-
- + +
+ Rotation deg
-
-
- + +
+ Dimensions m
-
-
- + +
+ Registration (pivot offset as ratio of dimension)
-
-
- + +
+ Scale %
-
+
@@ -153,246 +289,231 @@
-
-
- + + + +
+ + BehaviorM + +
+ +
+
+ + + + Saved! +
+
+
+ +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + +
+
+
+
+ + + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + + + + + +
+ + LightM + +
+
+ Light color
-
-
-
+
+
+
-
- -
- - -
-
- - -
-
- - -
-
- M -
-
- + +
-
-
-
+
+
+
+
+
+ +
-
- - -
-
-
- +
-
-
-
+
+
+
-
-
- - -
-
-
-
-
-
-
+ + + + +
+ + ModelM + +
+
+ +
-
-
-
- -
-
-
-
+ -
-
- -
-
-
-
+
+ +
-
-
-
-
- -
-
-
-
+ +
+
+ +
-
-
- M -
-
- - -
-
- - -
-
-
-
- Collides With + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-
-
- - +
+
+ +
-
- - +
+ +
-
- - -
-
- - -
-
- - +
+ +
-
-
- Grabbing -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
+ +
+
+ +
- -
-
- - -
-
- - -
-
-
- - - - -
-
-
- - - -
-
- - -
-
- -
-
- M -
-
- - -
- -
- - -
-
-
- - -
-
-
-
- - -
-
- - -
-
- - +
+ + + +
+ + ZoneM + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+ +
+
+
+
+<<<<<<< HEAD
@@ -400,35 +521,89 @@
+======= +
+
+ + +
+
+
+
+
+
+>>>>>>> upstream/master
-
-
- - -
-
- - -
-
- - -
+
+ +
-
-
-
- - -
-
- - -
-
- M -
+
+ + +
+
+
+ + Stage + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + Background + + +
+
+ + Skybox + +
+
+ Skybox color +
+
+
+
+
+
+
+ + +
+
+
+ + +
+ + TextM +
@@ -450,116 +625,22 @@
-
+
- + Background color
-
-
- M -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- - -
-
- - -
-
- -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
- -
- -
-
-
- -
-
-
-
-
-
-
- - -
-
- M -
+ + + + +
+ + WebM +
@@ -568,36 +649,45 @@
-
- M +
+ + + + + +
+ + Voxel volume size m + +
+
+
+
-
-
- -
-
-
-
-
+ -
-
-
-
-
-
+
+ +
-
- - +
+ +
-
-
-
-
-
-
+
+ +
-
- +
+ +
+ + diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 4a06c89db4..d93baff636 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -488,6 +488,7 @@ function loaded() { openEventBridge(function() { var allSections = []; + var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); var elTypeIcon = document.getElementById("type-icon"); @@ -574,7 +575,8 @@ function loaded() { var elJSONEditor = document.getElementById("userdata-editor"); var elNewJSONEditor = document.getElementById('userdata-new-editor'); var elColorSections = document.querySelectorAll(".color-section"); - var elColor = document.getElementById("property-color"); + var elColorControl1 = document.getElementById("property-color-control1"); + var elColorControl2 = document.getElementById("property-color-control2"); var elColorRed = document.getElementById("property-color-red"); var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); @@ -689,7 +691,10 @@ function loaded() { data = JSON.parse(data); if (data.type == "server_script_status") { elServerScriptError.value = data.errorInfo; - elServerScriptError.style.display = data.errorInfo ? "block" : "none"; + // If we just set elServerScriptError's diplay to block or none, we still end up with + //it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // So set it's parent to block or none + elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; if (data.statusRetrieved === false) { elServerScriptStatus.innerText = "Failed to retrieve status"; } else if (data.isRunning) { @@ -715,6 +720,7 @@ function loaded() { elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; elID.value = ""; + elPropertiesList.className = ''; disableProperties(); } else if (data.selections && data.selections.length > 1) { deleteJSONEditor(); @@ -743,6 +749,7 @@ function loaded() { elType.innerHTML = type + " (" + data.selections.length + ")"; elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; elTypeIcon.style.display = "inline-block"; + elPropertiesList.className = ''; elID.value = ""; @@ -759,6 +766,7 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; + elPropertiesList.className = properties.type + 'Menu'; elType.innerHTML = properties.type; elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; elTypeIcon.style.display = "inline-block"; @@ -893,48 +901,20 @@ function loaded() { elHyperlinkHref.value = properties.href; elDescription.value = properties.description; - for (var i = 0; i < allSections.length; i++) { - for (var j = 0; j < allSections[i].length; j++) { - allSections[i][j].style.display = 'none'; - } - } - - for (var i = 0; i < elHyperlinkSections.length; i++) { - elHyperlinkSections[i].style.display = 'table'; - } if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { - for (var i = 0; i < elShapeSections.length; i++) { - elShapeSections[i].style.display = 'table'; - } elShape.value = properties.shape; setDropdownText(elShape); - - } else { - for (var i = 0; i < elShapeSections.length; i++) { - elShapeSections[i].style.display = 'none'; - } } if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { - for (var i = 0; i < elColorSections.length; i++) { - elColorSections[i].style.display = 'table'; - } elColorRed.value = properties.color.red; elColorGreen.value = properties.color.green; elColorBlue.value = properties.color.blue; - elColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; - } else { - for (var i = 0; i < elColorSections.length; i++) { - elColorSections[i].style.display = 'none'; - } + elColorControl1.style.backgroundColor = elColorControl2.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; } if (properties.type == "Model") { - for (var i = 0; i < elModelSections.length; i++) { - elModelSections[i].style.display = 'table'; - } - elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; setDropdownText(elShapeType); @@ -953,20 +933,9 @@ function loaded() { elModelOriginalTextures.value = properties.originalTextures; setTextareaScrolling(elModelOriginalTextures); } else if (properties.type == "Web") { - for (var i = 0; i < elWebSections.length; i++) { - elWebSections[i].style.display = 'table'; - } - for (var i = 0; i < elHyperlinkSections.length; i++) { - elHyperlinkSections[i].style.display = 'none'; - } - elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; } else if (properties.type == "Text") { - for (var i = 0; i < elTextSections.length; i++) { - elTextSections[i].style.display = 'table'; - } - elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; @@ -978,10 +947,6 @@ function loaded() { elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; } else if (properties.type == "Light") { - for (var i = 0; i < elLightSections.length; i++) { - elLightSections[i].style.display = 'table'; - } - elLightSpotLight.checked = properties.isSpotlight; elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; @@ -994,10 +959,6 @@ function loaded() { elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); } else if (properties.type == "Zone") { - for (var i = 0; i < elZoneSections.length; i++) { - elZoneSections[i].style.display = 'table'; - } - elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; elZoneKeyLightColorRed.value = properties.keyLight.color.red; @@ -1034,10 +995,6 @@ function loaded() { showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { - for (var i = 0; i < elPolyVoxSections.length; i++) { - elPolyVoxSections[i].style.display = 'table'; - } - elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); @@ -1237,20 +1194,41 @@ function loaded() { elColorRed.addEventListener('change', colorChangeFunction); elColorGreen.addEventListener('change', colorChangeFunction); elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers.push($('#property-color').colpick({ + colorPickers.push($('#property-color-control1').colpick({ colorScheme: 'dark', layout: 'hex', color: '000000', onShow: function(colpick) { - $('#property-color').attr('active', 'true'); + $('#property-color-control1').attr('active', 'true'); }, onHide: function(colpick) { - $('#property-color').attr('active', 'false'); + $('#property-color-control1').attr('active', 'false'); }, onSubmit: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + // Keep the companion control in sync + elColorControl2.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; + } + })); + colorPickers.push($('#property-color-control2').colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + onShow: function(colpick) { + $('#property-color-control2').attr('active', 'true'); + }, + onHide: function(colpick) { + $('#property-color-control2').attr('active', 'false'); + }, + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + // Keep the companion control in sync + elColorControl1.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; + } })); @@ -1515,11 +1493,9 @@ function loaded() { var elCollapsible = document.getElementsByClassName("section-header"); var toggleCollapsedEvent = function(event) { - var element = event.target; - if (element.nodeName !== "DIV") { - element = element.parentNode; - } - var isCollapsed = element.getAttribute("collapsed") !== "true"; + var element = event.target.parentNode.parentNode; + var isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false element.setAttribute("collapsed", isCollapsed ? "true" : "false"); element.getElementsByTagName("span")[0].textContent = isCollapsed ? "L" : "M"; }; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8a8cf62008..80c8f8a0e4 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -25,6 +25,8 @@ var canWriteAssets = false; var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. + + var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free function injectCommonCode(isDirectoryPage) { @@ -65,7 +67,7 @@ // Footer actions. $("#back-button").on("click", function () { - (window.history.state != null) ? window.history.back() : window.location = "https://metaverse.highfidelity.com/marketplace?"; + (document.referrer !== "") ? window.history.back() : window.location = "https://metaverse.highfidelity.com/marketplace?"; }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(GOTO_DIRECTORY); @@ -87,8 +89,87 @@ }); } + function addInventoryButton() { + // Why isn't this an id?! This really shouldn't be a class on the website, but it is. + var navbarBrandElement = document.getElementsByClassName('navbar-brand')[0]; + var inventoryElement = document.createElement('a'); + inventoryElement.classList.add("btn"); + inventoryElement.classList.add("btn-default"); + inventoryElement.id = "inventoryButton"; + inventoryElement.setAttribute('href', "#"); + inventoryElement.innerHTML = "INVENTORY"; + inventoryElement.style = "height:100%;margin-top:0;padding:15px 15px;"; + navbarBrandElement.parentNode.insertAdjacentElement('beforeend', inventoryElement); + $('#inventoryButton').on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "INVENTORY", + referrerURL: window.location.href + })); + }); + } + + function buyButtonClicked(id, name, author, price, href) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "CHECKOUT", + itemId: id, + itemName: name, + itemAuthor: author, + itemPrice: Math.round(Math.random() * 50), + itemHref: href + })); + } + + function injectBuyButtonOnMainPage() { + $('.grid-item').find('#price-or-edit').find('a').each(function() { + $(this).attr('data-href', $(this).attr('href')); + $(this).attr('href', '#'); + }); + $('.grid-item').find('#price-or-edit').find('.price').text("BUY"); + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { + buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), + $(this).closest('.grid-item').find('.item-title').text(), + $(this).closest('.grid-item').find('.creator').find('.value').text(), + 10, + $(this).attr('data-href')); + }); + } + function injectHiFiCode() { - // Nothing to do. + if (confirmAllPurchases) { + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); + }); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); + + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage; + addInventoryButton(); + } + } + + function injectHiFiItemPageCode() { + if (confirmAllPurchases) { + var href = $('#side-info').find('.btn').attr('href'); + $('#side-info').find('.btn').attr('href', '#'); + $('#side-info').find('.btn').html('Buy Item '); + $('#side-info').find('.btn').on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + 10, + href); + }); + addInventoryButton(); + } } function updateClaraCode() { @@ -308,24 +389,15 @@ } } - function onLoad() { - - EventBridge.scriptEventReceived.connect(function (message) { - if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { - canWriteAssets = message.slice(-4) === "true"; - } - - if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { - cancelClaraDownload(); - } - }); - + function injectCode() { var DIRECTORY = 0; var HIFI = 1; var CLARA = 2; + var HIFI_ITEM_PAGE = 3; var pageType = DIRECTORY; if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; } + if (location.href.indexOf("highfidelity.com/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } injectCommonCode(pageType === DIRECTORY); @@ -336,13 +408,40 @@ case HIFI: injectHiFiCode(); break; + case HIFI_ITEM_PAGE: + injectHiFiItemPageCode(); + break; case CLARA: injectClaraCode(); break; } } + function onLoad() { + EventBridge.scriptEventReceived.connect(function (message) { + if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { + canWriteAssets = message.slice(-4) === "true"; + } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { + cancelClaraDownload(); + } else { + var parsedJsonMessage = JSON.parse(message); + + if (parsedJsonMessage.type === "marketplaces") { + if (parsedJsonMessage.action === "inspectionModeSetting") { + confirmAllPurchases = !!parsedJsonMessage.data; + injectCode(); + } + } + } + }); + + // Request inspection mode setting + // Code is injected into the webpage after the setting comes back. + EventBridge.emitWebEvent(JSON.stringify({ + type: "REQUEST_SETTING" + })); + } + // Load / unload. window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - }()); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 77b62913bf..44f3c9e041 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -30,11 +30,13 @@ function objectTranslationPlanePoint(position, dimensions) { SelectionManager = (function() { var that = {}; + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } + // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -238,6 +240,7 @@ function normalizeDegrees(degrees) { return degrees; } +// FUNCTION: getRelativeCenterPosition // Return the enter position of an entity relative to it's registrationPoint // A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0) // A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2) @@ -249,6 +252,7 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { }; } +// SELECTION DISPLAY DEFINITION SelectionDisplay = (function() { var that = {}; @@ -1152,6 +1156,7 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); @@ -1545,11 +1550,6 @@ SelectionDisplay = (function() { translateHandlesVisible = false; } - var rotation = selectionManager.worldRotation; - var dimensions = selectionManager.worldDimensions; - var position = selectionManager.worldPosition; - - Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); @@ -1577,6 +1577,7 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { if (spaceMode != newSpaceMode) { spaceMode = newSpaceMode; @@ -1584,6 +1585,7 @@ SelectionDisplay = (function() { } }; + // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { print("Local space editing is not available with multiple selections"); @@ -1593,8 +1595,11 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UNSELECT ALL + // TODO?: Needs implementation that.unselectAll = function() {}; + // FUNCTION: UPDATE HANDLES that.updateHandles = function() { if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); @@ -2168,10 +2173,10 @@ SelectionDisplay = (function() { position: EdgeTR }); - var boxPosition = Vec3.multiplyQbyV(rotation, center); - boxPosition = Vec3.sum(position, boxPosition); + var selectionBoxPosition = Vec3.multiplyQbyV(rotation, center); + selectionBoxPosition = Vec3.sum(position, selectionBoxPosition); Overlays.editOverlay(selectionBox, { - position: boxPosition, + position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), @@ -2218,13 +2223,13 @@ SelectionDisplay = (function() { var offset = vec3Mult(props.dimensions, centeredRP); offset = Vec3.multiply(-1, offset); offset = Vec3.multiplyQbyV(props.rotation, offset); - var boxPosition = Vec3.sum(props.position, offset); + var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; Overlays.editOverlay(selectionBoxes[i], { - position: boxPosition, + position: curBoxPosition, color: color, rotation: props.rotation, dimensions: props.dimensions, @@ -2305,9 +2310,9 @@ SelectionDisplay = (function() { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z - } + }; Overlays.editOverlay(grabberMoveUp, { - visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" + visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -2327,21 +2332,24 @@ SelectionDisplay = (function() { }; + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { var length = allOverlays.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(allOverlays[i], { + for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { + Overlays.editOverlay(allOverlays[overlayIndex], { visible: isVisible }); } length = selectionBoxes.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(selectionBoxes[i], { + for (var boxIndex = 0; boxIndex < length; boxIndex++) { + Overlays.editOverlay(selectionBoxes[boxIndex], { visible: isVisible }); } }; + // FUNCTION: UNSELECT + // TODO?: Needs implementation that.unselect = function(entityID) {}; var initialXZPick = null; @@ -2350,6 +2358,7 @@ SelectionDisplay = (function() { var startPosition = null; var duplicatedEntityIDs = null; + // TOOL DEFINITION: TRANSLATE XZ TOOL var translateXZTool = { mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, @@ -2538,7 +2547,8 @@ SelectionDisplay = (function() { } }; - var lastXYPick = null + // GRABBER TOOL: GRABBER MOVE UP + var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", @@ -2594,7 +2604,7 @@ SelectionDisplay = (function() { print(" event.y:" + event.y); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" newPosition:", newPosition); + //Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; @@ -2612,6 +2622,7 @@ SelectionDisplay = (function() { }, }); + // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", onBegin: function(event) { @@ -2639,7 +2650,7 @@ SelectionDisplay = (function() { - + // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { return { x: v1.x * v2.x, @@ -2647,6 +2658,8 @@ SelectionDisplay = (function() { z: v1.z * v2.z }; }; + + // FUNCTION: MAKE STRETCH TOOL // stretchMode - name of mode // direction - direction to stretch in // pivot - point to use as a pivot @@ -2898,13 +2911,14 @@ SelectionDisplay = (function() { // Are we using handControllers or Mouse - only relevant for 3D tools var controllerPose = getControllerWorldLocation(activeHand, true); - if (HMD.isHMDAvailable() - && HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) { + var vector = null; + if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; - var vector = Vec3.subtract(newPick, lastPick3D); + vector = Vec3.subtract(newPick, lastPick3D); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2919,7 +2933,7 @@ SelectionDisplay = (function() { newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); - var vector = Vec3.subtract(newPick, lastPick); + vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2955,41 +2969,39 @@ SelectionDisplay = (function() { } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - } - - - newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); - newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); - newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - var newPosition = Vec3.sum(initialPosition, changeInPosition); + newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); + newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); + newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, - dimensions: newDimensions, - }); - } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); + var newPosition = Vec3.sum(initialPosition, changeInPosition); - var wantDebug = false; - if (wantDebug) { - print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); - Vec3.print(" changeInDimensions:", changeInDimensions); - Vec3.print(" newDimensions:", newDimensions); + for (var i = 0; i < SelectionManager.selections.length; i++) { + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition, + dimensions: newDimensions, + }); + } + - Vec3.print(" changeInPosition:", changeInPosition); - Vec3.print(" newPosition:", newPosition); + var wantDebug = false; + if (wantDebug) { + print(stretchMode); + //Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } } SelectionManager._update(); - - }; + };//--End of onMove def return { mode: stretchMode, @@ -3042,7 +3054,8 @@ SelectionDisplay = (function() { z: -1 } }; - + + // FUNCTION: GET DIRECTION FOR 3D STRETCH // Returns a vector with directions for the stretch tool in 3D using hand controllers function getDirectionsFor3DStretch(mode) { if (mode === "STRETCH_LBN") { @@ -3067,7 +3080,7 @@ SelectionDisplay = (function() { } - + // FUNCTION: ADD STRETCH TOOL function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) { if (!pivot) { pivot = direction; @@ -3077,6 +3090,7 @@ SelectionDisplay = (function() { addGrabberTool(overlay, tool); } + // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; Vec3.print("Radius stretch: ", vector); @@ -3097,6 +3111,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { var props = selectionManager.savedProperties[selectionManager.selections[0]]; @@ -3123,6 +3138,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // STRETCH TOOL DEF SECTION addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, @@ -3529,6 +3545,7 @@ SelectionDisplay = (function() { z: 1 }); + // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var angle = angleFromZero * (Math.PI / 180); var position = { @@ -3549,6 +3566,7 @@ SelectionDisplay = (function() { }); } + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", @@ -3625,10 +3643,10 @@ SelectionDisplay = (function() { if (result.intersects) { var center = yawCenter; var zero = yawZero; - // TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle) - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); - // TODO: orientedAngle wants normalized centerToZero and centerToIntersect + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = distanceFromCenter < innerRadius; @@ -3717,6 +3735,7 @@ SelectionDisplay = (function() { } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function(event) { @@ -3789,11 +3808,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = pitchCenter; var zero = pitchZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles + // this internally, so it's fine to pass non-normalized versions here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -3809,7 +3829,6 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); var initialProperties = SelectionManager.savedProperties[entityID]; var dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(pitchChange, dPos); @@ -3874,6 +3893,7 @@ SelectionDisplay = (function() { } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function(event) { @@ -3946,11 +3966,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = rollCenter; var zero = rollZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles + // this internally, so it's fine to pass non-normalized versions here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -3965,7 +3986,6 @@ SelectionDisplay = (function() { }); for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); var initialProperties = SelectionManager.savedProperties[entityID]; var dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(rollChange, dPos); @@ -4030,6 +4050,7 @@ SelectionDisplay = (function() { } }); + // FUNCTION: CHECK MOVE that.checkMove = function() { if (SelectionManager.hasSelection()) { @@ -4044,6 +4065,7 @@ SelectionDisplay = (function() { } }; + // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function(event) { var wantDebug = false; if (!event.isLeftButton && !that.triggered) { @@ -4167,7 +4189,7 @@ SelectionDisplay = (function() { // Only intersect versus yaw/pitch/roll. - var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); + result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); var overlayOrientation; var overlayCenter; @@ -4184,10 +4206,10 @@ SelectionDisplay = (function() { originalRoll = roll; if (result.intersects) { - var tool = grabberTools[result.overlayID]; - if (tool) { - activeTool = tool; - mode = tool.mode; + var resultTool = grabberTools[result.overlayID]; + if (resultTool) { + activeTool = resultTool; + mode = resultTool.mode; somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); @@ -4370,7 +4392,7 @@ SelectionDisplay = (function() { if (!somethingClicked) { // Only intersect versus selectionBox. - var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); + result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); if (result.intersects) { switch (result.overlayID) { case selectionBox: @@ -4425,6 +4447,7 @@ SelectionDisplay = (function() { return somethingClicked; }; + // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { if (activeTool) { activeTool.onMove(event); @@ -4554,7 +4577,7 @@ SelectionDisplay = (function() { return false; }; - + // FUNCTION: UPDATE HANDLE SIZES that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); @@ -4593,6 +4616,7 @@ SelectionDisplay = (function() { }; Script.update.connect(that.updateHandleSizes); + // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { var showHandles = false; if (activeTool && activeTool.onEnd) { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b25589e92..84d7d44689 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,6 +19,9 @@ var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); + var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml"; + var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml"; + var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -57,46 +60,6 @@ function showMarketplace() { UserActivityLogger.openedMarketplace(); tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - tablet.webEventReceived.connect(function (message) { - - if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } - - if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } - - if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } - - if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { - if (isDownloadBeingCancelled) { - return; - } - - var text = message.slice(CLARA_IO_STATUS.length); - if (messageBox === null) { - messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } else { - Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } - return; - } - - if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox !== null) { - Window.closeMessageBox(messageBox); - messageBox = null; - } - return; - } - - if (message === CLARA_IO_CANCELLED_DOWNLOAD) { - isDownloadBeingCancelled = false; - } - }); } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -124,7 +87,8 @@ } function onScreenChanged(type, url) { - onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL + onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL; + wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH)); // for toolbar mode: change button to active when window is first openend, false otherwise. marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { @@ -138,13 +102,128 @@ tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); + function onMessage(message) { + + if (message === GOTO_DIRECTORY) { + tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message === QUERY_CAN_WRITE_ASSETS) { + tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + } else if (message === WARN_USER_NO_PERMISSIONS) { + Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { + return; + } + + var text = message.slice(CLARA_IO_STATUS.length); + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } else { + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } + return; + } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; + } + return; + } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; + } else { + var parsedJsonMessage = JSON.parse(message); + if (parsedJsonMessage.type === "CHECKOUT") { + tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); + tablet.sendToQml({ method: 'updateCheckoutQML', params: parsedJsonMessage }); + } else if (parsedJsonMessage.type === "REQUEST_SETTING") { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "inspectionModeSetting", + data: Settings.getValue("inspectionMode", false) + })); + } else if (parsedJsonMessage.type === "INVENTORY") { + tablet.pushOntoStack(MARKETPLACE_INVENTORY_QML_PATH); + tablet.sendToQml({ + method: 'updateInventory', + referrerURL: parsedJsonMessage.referrerURL + }); + } + } + } + + tablet.webEventReceived.connect(onMessage); + Script.scriptEnding.connect(function () { if (onMarketplaceScreen) { tablet.gotoHomeScreen(); } tablet.removeButton(marketplaceButton); tablet.screenChanged.disconnect(onScreenChanged); + tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); }); + + + // Function Name: wireEventBridge() + // + // Description: + // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or + // disable to event bridge. + // + // Relevant Variables: + // -hasEventBridge: true/false depending on whether we've already connected the event bridge. + var hasEventBridge = false; + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + // Function Name: fromQml() + // + // Description: + // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML + // in the format "{method, params}", like json-rpc. + function fromQml(message) { + switch (message.method) { + case 'checkout_cancelClicked': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); + // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. + // I don't think this is trivial to do since we also want to inject some JS into the DOM. + //tablet.popFromStack(); + break; + case 'checkout_buySuccess': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + //tablet.popFromStack(); + break; + case 'inventory_itemClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); + } + break; + case 'inventory_backClicked': + tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'securityImageSelection_cancelClicked': + tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: + print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message)); + } + } + }()); // END LOCAL_SCOPE diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37618253ee..df5ed45fed 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -764,8 +764,8 @@ Script.scriptEnding.connect(function () { } Window.snapshotShared.disconnect(snapshotUploaded); Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); - Entities.canRezChanged.disconnect(processRezPermissionChange); - Entities.canRezTmpChanged.disconnect(processRezPermissionChange); + Entities.canRezChanged.disconnect(updatePrintPermissions); + Entities.canRezTmpChanged.disconnect(updatePrintPermissions); }); }()); // END LOCAL_SCOPE diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 39043805b8..9ae78527cc 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -24,7 +24,7 @@ #include #ifdef DEFERRED_LIGHTING -extern void initDeferredPipelines(render::ShapePlumber& plumber); +extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); extern void initStencilPipeline(gpu::PipelinePointer& pipeline); #endif @@ -77,7 +77,7 @@ void TestWindow::initGl() { #ifdef DEFERRED_LIGHTING auto deferredLightingEffect = DependencyManager::get(); deferredLightingEffect->init(); - initDeferredPipelines(*_shapePlumber); + initDeferredPipelines(*_shapePlumber, nullptr, nullptr); #endif } diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js new file mode 100644 index 0000000000..e7a135ec9e --- /dev/null +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -0,0 +1,118 @@ +"use strict"; + +// +// skyboxchanger.js +// +// Created by Cain Kilgore on 9th August 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 +// + +(function() { + var TABLET_BUTTON_NAME = "SKYBOX"; + + var ICONS = { + icon: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxedit-i.svg", + activeIcon: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxedit-i.svg" + }; + + var onSkyboxChangerScreen = false; + + function onClicked() { + if (onSkyboxChangerScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource("../SkyboxChanger.qml"); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: ICONS.icon, + activeIcon: ICONS.activeIcon, + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === "../SkyboxChanger.qml") { + onSkyboxChangerScreen = true; + } else { + onSkyboxChangerScreen = false; + } + + button.editProperties({isActive: onSkyboxChangerScreen}); + wireEventBridge(onSkyboxChangerScreen); + } + + function fromQml(message) { + switch (message.method) { + case 'changeSkybox': // changeSkybox Code + var standingZone; + if (!Entities.canRez()) { + Window.alert("You need to have rez permissions to change the Skybox."); + break; + } + + var nearbyEntities = Entities.findEntities(MyAvatar.position, 5); + for (var i = 0; i < nearbyEntities.length; i++) { + if (Entities.getEntityProperties(nearbyEntities[i]).type === "Zone") { + standingZone = nearbyEntities[i]; + } + } + + if (Entities.getEntityProperties(standingZone).locked) { + Window.alert("This zone is currently locked; the Skybox can't be changed."); + break; + } + + var newSkybox = { + skybox: { + url: message.url + }, + keyLight: { + ambientURL: message.url + } + }; + + Entities.editEntity(standingZone, newSkybox); + break; + default: + print('Unrecognized message from QML: ' + JSON.stringify(message)); + } + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onSkyboxChangerScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); +}()); \ No newline at end of file diff --git a/unpublishedScripts/parent-ator/createParentator.js b/unpublishedScripts/parent-ator/createParentator.js new file mode 100644 index 0000000000..470b7d1845 --- /dev/null +++ b/unpublishedScripts/parent-ator/createParentator.js @@ -0,0 +1,66 @@ +// createParentator.js +// +// Script Type: Entity Spawner +// Created by Jeff Moyes on 6/30/2017 +// Copyright 2017 High Fidelity, Inc. +// +// This script creates a gun-looking item that, when tapped on an entity, and then a second entity, sets the second entity as the paernt of the first +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var scriptURL = Script.resolvePath('parentator.js'); +var MODEL_URL = Script.resolvePath('resources/Parent-Tool-Production.fbx'); +var COLLISION_HULL_URL = Script.resolvePath('resources/Parent-Tool-CollisionHull.obj'); + +// the fbx model needs to be rotated from where it would naturally face when it first initializes +var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var START_ROTATION = Quat.normalize(Quat.multiply(Camera.getOrientation(), ROT_Y_180)); +var START_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.7, Quat.getForward(Camera.getOrientation()))); + + + +var parentator = Entities.addEntity({ + name: "Parent-ator", + type: "Model", + modelURL: MODEL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, + dynamic: true, + damping: 0.9, + script: scriptURL, + dimensions: { + x: 0.1270, + y: 0.2715, + z: 0.4672 + }, + position: START_POSITION, + rotation: START_ROTATION, + + + userData: JSON.stringify({ + "grabbableKey": {"grabbable": true}, + "equipHotspots": [ + { + "position": {"x": 0.0, "y": 0.0, "z": -0.170 }, + "radius": 0.15, + "joints":{ + "RightHand":[ + {"x":0.05, "y":0.25, "z":0.03}, + {"x":-0.5, "y":-0.5, "z":-0.5, "w":0.5} + ], + "LeftHand":[ + {"x":-0.05, "y":0.25, "z":0.03}, + {"x":-0.5, "y":0.5, "z":0.5, "w":0.5} + ] + } + } + ] + }) +}); + + +function cleanUp() { + Entities.deleteEntity(parentator); +} +Script.scriptEnding.connect(cleanUp); \ No newline at end of file diff --git a/unpublishedScripts/parent-ator/parentator.js b/unpublishedScripts/parent-ator/parentator.js new file mode 100644 index 0000000000..546ac38ba1 --- /dev/null +++ b/unpublishedScripts/parent-ator/parentator.js @@ -0,0 +1,152 @@ +// parentator.js +// +// Script Type: Entity +// Created by Jeff Moyes on 6/30/2017 +// Copyright 2017 High Fidelity, Inc. +// +// This script allows users to parent one object to another via the "parent-ator" entity +// (which looks like a purple gun-like object). The user: +// 1) equips their avatar with this parent-ator, +// 2) taps the end of the parent-ator on an entity (which becomes the child entity), and +// 3) taps the end of the parent-ator on a second entity (which becomes the parent entity) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +(function() { + var MESSAGE_0_TEXTURE_URL = Script.resolvePath( 'resources/message-0-off.png' ); + var MESSAGE_1_TEXTURE_URL = Script.resolvePath( 'resources/message-1-start.png' ); + var MESSAGE_2_TEXTURE_URL = Script.resolvePath( 'resources/message-2-noperms.png' ); + var MESSAGE_3_TEXTURE_URL = Script.resolvePath( 'resources/message-3-tryagain.png' ); + var MESSAGE_4_TEXTURE_URL = Script.resolvePath( 'resources/message-4-setparent.png' ); + var MESSAGE_5_TEXTURE_URL = Script.resolvePath( 'resources/message-5-success.png' ); + + var SOUND_1_URL = Script.resolvePath( 'resources/parent-tool-sound1.wav' ); + var SOUND_2_URL = Script.resolvePath( 'resources/parent-tool-sound2.wav' ); + var SOUND_ERROR_URL = Script.resolvePath( 'resources/parent-tool-sound-error.wav' ); + var SOUND_SUCCESS_URL = Script.resolvePath( 'resources/parent-tool-sound-success.wav' ); + var SOUND_1, SOUND_2, SOUND_ERROR, SOUND_SUCCESS; + var childEntityID, parentEntityID; + var active = false; + + + + + function Parentator() { + return; + } + + Parentator.prototype.turnOff = function() { + childEntityID = 0; + parentEntityID = 0; + this.active = false; + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_0_TEXTURE_URL }) }); + } + + Parentator.prototype.turnOn = function() { + childEntityID = 0; + parentEntityID = 0; + this.active = true; + if (Entities.canRez()) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_1_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_1 ); + } else { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_2_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + } + } + + + Parentator.prototype.preload = function( entityID ) { + this.entityID = entityID; + SOUND_1 = SoundCache.getSound( SOUND_1_URL ); + SOUND_2 = SoundCache.getSound( SOUND_2_URL ); + SOUND_ERROR = SoundCache.getSound( SOUND_ERROR_URL ); + SOUND_SUCCESS = SoundCache.getSound( SOUND_SUCCESS_URL ); + + // Makue sure it's off + this.turnOff(); + } + + Parentator.prototype.startEquip = function( args ) { + this.hand = args[0]; + this.turnOn(); + } + + + Parentator.prototype.releaseEquip = function( args ) { + this.hand = undefined; + this.turnOff(); + } + + Parentator.prototype.collisionWithEntity = function( parentatorID, collidedID, collisionInfo ) { + if ( this.active ) { + // We don't want to be able to select Lights, Zone, and Particles but they are not collidable anyway so we don't have to worry about them + var collidedEntityProperties = Entities.getEntityProperties( collidedID ); + + if ( !Entities.canRez() ) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_2_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + } + + // User has just reclicked the first entity (or it's 'bounced') + if ( childEntityID == collidedID ) { + return; + } + + if ( collidedEntityProperties.locked ) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_3_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + return; + } + + // If no entity has been chosen + if ( childEntityID == 0 ) { + childEntityID = collidedID; + + // if there is a parentID, remove it + if ( collidedEntityProperties.parentID != "{00000000-0000-0000-0000-000000000000}" ) { + Entities.editEntity( collidedID, { parentID: "{00000000-0000-0000-0000-000000000000}" }); + } + + if ( collidedEntityProperties.dynamic ) { + Entities.editEntity( collidedID, { dynamic: false }); + } + + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_4_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_2 ); + } else { + parentEntityID = collidedID; + this.setParent(); + } + } + } + + Parentator.prototype.setParent = function() { + var _this = this; + Entities.editEntity( childEntityID, { parentID: parentEntityID }); + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_5_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_SUCCESS ); + Script.setTimeout( function() { + _this.turnOn(); // reset + }, 5000 ); + } + + Parentator.prototype.playSoundAtCurrentPosition = function( sound ) { + var audioProperties = { + volume: 0.3, + position: Entities.getEntityProperties( this.entityID ).position, + localOnly: true + } + Audio.playSound( sound, audioProperties ); + } + + Parentator.prototype.unload = function () { + Entities.deleteEntity( this.entityID ); + } + + // entity scripts always need to return a newly constructed object of our type + return new Parentator(); +}); diff --git a/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj b/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj new file mode 100644 index 0000000000..0daf822605 --- /dev/null +++ b/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj @@ -0,0 +1,38 @@ +# Blender v2.78 (sub 5) OBJ File: 'untitled.blend' +# www.blender.org +mtllib Parent-Tool-CollisionHull.mtl +o Cube.001 +v -0.153045 -0.072355 0.173769 +v -0.153045 0.040882 0.173769 +v -0.067387 0.040882 0.029156 +v -0.067387 -0.072355 0.029156 +v 0.062301 0.040882 0.029156 +v 0.062301 -0.072355 0.029156 +v 0.147960 -0.072355 0.173769 +v 0.147960 0.040882 0.173769 +v 0.147960 0.026770 0.337324 +v 0.147960 -0.047896 0.337324 +v -0.153045 0.026770 0.337324 +v -0.153045 -0.047896 0.337324 +vn -0.8604 0.0000 -0.5096 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -0.9890 0.1479 +vn 0.0000 0.9963 0.0860 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.8604 0.0000 -0.5096 +vn -1.0000 -0.0000 0.0000 +usemtl None +s 1 +f 1//1 2//1 3//1 4//1 +f 4//2 3//2 5//2 6//2 +f 7//3 8//3 9//3 10//3 +f 10//4 9//4 11//4 12//4 +f 1//5 7//5 10//5 12//5 +f 8//6 2//6 11//6 9//6 +f 5//7 3//7 2//7 8//7 +f 4//8 6//8 7//8 1//8 +f 6//9 5//9 8//9 7//9 +f 12//10 11//10 2//10 1//10 diff --git a/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx b/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx new file mode 100644 index 0000000000..3261bf62fd Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx differ diff --git a/unpublishedScripts/parent-ator/resources/message-0-off.png b/unpublishedScripts/parent-ator/resources/message-0-off.png new file mode 100644 index 0000000000..4985c8fda7 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-0-off.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-1-start.png b/unpublishedScripts/parent-ator/resources/message-1-start.png new file mode 100644 index 0000000000..43bf1654e6 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-1-start.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-2-noperms.png b/unpublishedScripts/parent-ator/resources/message-2-noperms.png new file mode 100644 index 0000000000..80b8f28ac2 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-2-noperms.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-3-tryagain.png b/unpublishedScripts/parent-ator/resources/message-3-tryagain.png new file mode 100644 index 0000000000..e85a5e4b17 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-3-tryagain.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-4-setparent.png b/unpublishedScripts/parent-ator/resources/message-4-setparent.png new file mode 100644 index 0000000000..c1e1143e0b Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-4-setparent.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-5-success.png b/unpublishedScripts/parent-ator/resources/message-5-success.png new file mode 100644 index 0000000000..2ee88de911 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-5-success.png differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav new file mode 100644 index 0000000000..47115503dc Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav new file mode 100644 index 0000000000..86cdd3e1e3 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav new file mode 100644 index 0000000000..1f0a2e6272 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav new file mode 100644 index 0000000000..3ffa826b9f Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav differ