diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index cad6a852cb..c2aec9b058 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include "AssetServerLogging.h" #include "BakeAssetTask.h" diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 201e24d4b9..fd3a8f7c0c 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -98,7 +98,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketType::RequestsDomainListData, PacketType::PerAvatarGainSet, PacketType::InjectorGainSet, - PacketType::AudioSoloRequest }, + PacketType::AudioSoloRequest, + PacketType::StopInjector }, this, "queueAudioPacket"); // packets whose consequences are global should be processed on the main thread @@ -246,7 +247,8 @@ void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) { if (injectorClientData) { // stage the removal of this stream, workers handle when preparing mixes for listeners - _workerSharedData.removedStreams.emplace_back(injectorClientData->getNodeID(), injectorClientData->getNodeLocalID(), + _workerSharedData.removedStreams.emplace_back(injectorClientData->getNodeID(), + injectorClientData->getNodeLocalID(), streamID); } } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 41b72c04d2..e2f28c221e 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -104,6 +104,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) { case PacketType::AudioSoloRequest: parseSoloRequest(packet, node); break; + case PacketType::StopInjector: + parseStopInjectorPacket(packet); + break; default: Q_UNREACHABLE(); } @@ -574,6 +577,19 @@ int AudioMixerClientData::checkBuffersBeforeFrameSend() { return (int)_audioStreams.size(); } +void AudioMixerClientData::parseStopInjectorPacket(QSharedPointer packet) { + auto streamID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + auto it = std::find_if(std::begin(_audioStreams), std::end(_audioStreams), [&](auto stream) { + return streamID == stream->getStreamIdentifier(); + }); + + if (it != std::end(_audioStreams)) { + _audioStreams.erase(it); + emit injectorStreamFinished(streamID); + } +} + bool AudioMixerClientData::shouldSendStats(int frameNumber) { return frameNumber == _frameToSendStats; } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 4a1ca7f9b5..4f5e8e6d68 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -67,12 +67,11 @@ public: void parseNodeIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); void parseRadiusIgnoreRequest(QSharedPointer message, const SharedNodePointer& node); void parseSoloRequest(QSharedPointer message, const SharedNodePointer& node); + void parseStopInjectorPacket(QSharedPointer packet); // attempt to pop a frame from each audio stream, and return the number of streams from this client int checkBuffersBeforeFrameSend(); - void removeDeadInjectedStreams(); - QJsonObject getAudioStreamStats(); void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode); @@ -163,7 +162,7 @@ public: // end of methods called non-concurrently from single AudioMixerSlave signals: - void injectorStreamFinished(const QUuid& streamIdentifier); + void injectorStreamFinished(const QUuid& streamID); public slots: void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec); diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index cb90df58e5..e5e9f89984 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -549,38 +549,28 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre // grab the stream from the ring buffer AudioRingBuffer::ConstIterator streamPopOutput = streamToAdd->getLastPopOutput(); - // stereo sources are not passed through HRTF if (streamToAdd->isStereo()) { - // apply the avatar gain adjustment - gain *= mixableStream.hrtf->getGainAdjustment(); + streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); - const float scale = 1 / 32768.0f; // int16_t to float - - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { - _mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale; - _mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale; - } + // stereo sources are not passed through HRTF + mixableStream.hrtf->mixStereo(_bufferSamples, _mixSamples, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); ++stats.manualStereoMixes; } else if (isEcho) { + + streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); + // echo sources are not passed through HRTF - - const float scale = 1/32768.0f; // int16_t to float - - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { - float sample = (float)streamPopOutput[i] * gain * scale; - _mixSamples[2*i+0] += sample; - _mixSamples[2*i+1] += sample; - } + mixableStream.hrtf->mixMono(_bufferSamples, _mixSamples, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); ++stats.manualEchoMixes; } else { + streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); mixableStream.hrtf->render(_bufferSamples, _mixSamples, HRTF_DATASET_INDEX, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); - ++stats.hrtfRenders; } } diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 581d854909..06632dabb0 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include "../AssignmentDynamicFactory.h" @@ -471,77 +470,7 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; EntityTreePointer tree = std::static_pointer_cast(_tree); - QHash localMap(tree->getEntityCertificateIDMap()); - - QHashIterator i(localMap); - qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; - while (i.hasNext()) { - i.next(); - const auto& certificateID = i.key(); - const auto& entityID = i.value(); - - EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); - - if (entity) { - if (!entity->getProperties().verifyStaticCertificateProperties()) { - qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed" - << "static certificate verification."; - // Delete the entity if it doesn't pass static certificate verification - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); - } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); - QJsonObject request; - request["certificate_id"] = certificateID; - networkRequest.setUrl(requestURL); - - QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - - connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] { - EntityTreePointer tree = std::static_pointer_cast(_tree); - - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); - - if (networkReply->error() == QNetworkReply::NoError) { - QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); - if (jsonObject["domain_id"].toString() != thisDomainID) { - EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); - if (!entity) { - qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID; - networkReply->deleteLater(); - return; - } - if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { - qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() - << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); - } else { - qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; - } - } else { - qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; - } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID - << "More info:" << jsonObject; - } - - networkReply->deleteLater(); - }); - } - } else { - qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!"; - } - } + tree->startDynamicDomainVerificationOnServer((float) _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS / MSECS_PER_SECOND); int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds"; diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake index 113ab53aae..bc070e057b 100644 --- a/cmake/macros/AddCrashpad.cmake +++ b/cmake/macros/AddCrashpad.cmake @@ -53,10 +53,5 @@ macro(add_crashpad) POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" ) - install( - PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} - DESTINATION ${INTERFACE_INSTALL_DIR} - COMPONENT ${CLIENT_COMPONENT} - ) endif () endmacro() diff --git a/cmake/macros/TargetOpenEXR.cmake b/cmake/macros/TargetOpenEXR.cmake new file mode 100644 index 0000000000..8d61f216e7 --- /dev/null +++ b/cmake/macros/TargetOpenEXR.cmake @@ -0,0 +1,74 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Olivier Prat on 2019/03/26 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_OPENEXR) + if (NOT ANDROID) + set(openexr_config_file "${VCPKG_INSTALL_ROOT}/include/OpenEXR/OpenEXRConfig.h") + if(EXISTS ${openexr_config_file}) + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_STRING.*$") + string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MAJOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MINOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP}) + endif() + + foreach(OPENEXR_LIB + IlmImf + IlmImfUtil + Half + Iex + IexMath + Imath + IlmThread) + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s + ${OPENEXR_LIB}_s + + PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH + ) + #mark_as_advanced(OPENEXR_${OPENEXR_LIB}_LIBRARY) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE) + list(APPEND OPENEXR_LIBRARY_RELEASE ${OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE}) + endif() + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s_d + ${OPENEXR_LIB}_s_d + + PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH + ) + #mark_as_advanced(OPENEXR_${OPENEXR_LIB}_DEBUG_LIBRARY) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG) + list(APPEND OPENEXR_LIBRARY_DEBUG ${OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG}) + endif() + endforeach(OPENEXR_LIB) + + select_library_configurations(OPENEXR) + target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARY}) + endif() +endmacro() diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index e202d0c8f8..5f860a1620 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-deps -Version: 0 +Version: 0.1 Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib +Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib diff --git a/cmake/ports/openexr/CONTROL b/cmake/ports/openexr/CONTROL new file mode 100644 index 0000000000..d59ab286e1 --- /dev/null +++ b/cmake/ports/openexr/CONTROL @@ -0,0 +1,4 @@ +Source: openexr +Version: 2.3.0-2 +Description: OpenEXR is a high dynamic-range (HDR) image file format developed by Industrial Light & Magic for use in computer imaging applications +Build-Depends: zlib \ No newline at end of file diff --git a/cmake/ports/openexr/FindOpenEXR.cmake b/cmake/ports/openexr/FindOpenEXR.cmake new file mode 100644 index 0000000000..a381c6db9a --- /dev/null +++ b/cmake/ports/openexr/FindOpenEXR.cmake @@ -0,0 +1,87 @@ +include(FindPackageHandleStandardArgs) + +find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h) +find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR) + +file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H) + +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}") +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}") +set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}") + +include(SelectLibraryConfigurations) + +if(NOT OpenEXR_BASE_LIBRARY) + find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_BASE) +endif() + +if(NOT OpenEXR_UTIL_LIBRARY) + find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_UTIL) +endif() + +if(NOT OpenEXR_HALF_LIBRARY) + find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_HALF) +endif() + +if(NOT OpenEXR_IEX_LIBRARY) + find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_IEX) +endif() + +if(NOT OpenEXR_MATH_LIBRARY) + find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_MATH) +endif() + +if(NOT OpenEXR_THREAD_LIBRARY) + find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_THREAD) +endif() + +if(NOT OpenEXR_IEXMATH_LIBRARY) + find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d) + select_library_configurations(OpenEXR_IEXMATH) +endif() + +set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}") +set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}") +set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}") +set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}") +set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}") +set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}") + +set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}") + +set(OpenEXR_LIBRARIES + ${OpenEXR_LIBRARY} + ${OpenEXR_MATH_LIBRARY} + ${OpenEXR_IEXMATH_LIBRARY} + ${OpenEXR_UTIL_LIBRARY} + ${OpenEXR_HALF_LIBRARY} + ${OpenEXR_IEX_LIBRARY} + ${OpenEXR_THREAD_LIBRARY} +) + +set(OPENEXR_LIBRARIES + ${OPENEXR_HALF_LIBRARY} + ${OPENEXR_IEX_LIBRARY} + ${OPENEXR_IMATH_LIBRARY} + ${OPENEXR_ILMIMF_LIBRARY} + ${OPENEXR_ILMTHREAD_LIBRARY} +) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS) + +if(OpenEXR_FOUND) + set(OPENEXR_FOUND 1) +endif() diff --git a/cmake/ports/openexr/fix_install_ilmimf.patch b/cmake/ports/openexr/fix_install_ilmimf.patch new file mode 100644 index 0000000000..db65be7368 --- /dev/null +++ b/cmake/ports/openexr/fix_install_ilmimf.patch @@ -0,0 +1,19 @@ +diff --git a/OpenEXR/IlmImf/CMakeLists.txt b/OpenEXR/IlmImf/CMakeLists.txt +index e1a8740..d31cf68 100644 +--- a/OpenEXR/IlmImf/CMakeLists.txt ++++ b/OpenEXR/IlmImf/CMakeLists.txt +@@ -2,14 +2,6 @@ + + SET(CMAKE_INCLUDE_CURRENT_DIR 1) + +-IF (WIN32) +- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/bin) +- SET(WORKING_DIR ${RUNTIME_DIR}) +-ELSE () +- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/lib) +- SET(WORKING_DIR .) +-ENDIF () +- + SET(BUILD_B44EXPLOGTABLE OFF) + IF (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/b44ExpLogTable.h") + SET(BUILD_B44EXPLOGTABLE ON) diff --git a/cmake/ports/openexr/portfile.cmake b/cmake/ports/openexr/portfile.cmake new file mode 100644 index 0000000000..72e1edb3e7 --- /dev/null +++ b/cmake/ports/openexr/portfile.cmake @@ -0,0 +1,74 @@ +include(vcpkg_common_functions) + +set(OPENEXR_VERSION 2.3.0) +set(OPENEXR_HASH 268ae64b40d21d662f405fba97c307dad1456b7d996a447aadafd41b640ca736d4851d9544b4741a94e7b7c335fe6e9d3b16180e710671abfc0c8b2740b147b2) + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO openexr/openexr + REF v${OPENEXR_VERSION} + SHA512 ${OPENEXR_HASH} + HEAD_REF master + PATCHES "fix_install_ilmimf.patch" +) + +set(OPENEXR_STATIC ON) +set(OPENEXR_SHARED OFF) + +vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA + OPTIONS + -DOPENEXR_BUILD_PYTHON_LIBS=OFF + -DOPENEXR_BUILD_VIEWERS=OFF + -DOPENEXR_ENABLE_TESTS=OFF + -DOPENEXR_RUN_FUZZ_TESTS=OFF + -DOPENEXR_BUILD_SHARED=${OPENEXR_SHARED} + -DOPENEXR_BUILD_STATIC=${OPENEXR_STATIC} + OPTIONS_DEBUG + -DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}/debug + OPTIONS_RELEASE + -DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}) + +vcpkg_install_cmake() + +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share) + +# NOTE: Only use ".exe" extension on Windows executables. +# Is there a cleaner way to do this? +if(WIN32) + set(EXECUTABLE_SUFFIX ".exe") +else() + set(EXECUTABLE_SUFFIX "") +endif() +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrenvmap${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrheader${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmakepreview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmaketiled${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultipart${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultiview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrstdattr${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrenvmap${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrheader${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmakepreview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmaketiled${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultipart${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultiview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrstdattr${EXECUTABLE_SUFFIX}) + +vcpkg_copy_pdbs() + +if (OPENEXR_STATIC) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin) +endif() + +if (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(OPENEXR_PORT_DIR "openexr") +else() + set(OPENEXR_PORT_DIR "OpenEXR") +endif() + +file(COPY ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}) +file(RENAME ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/LICENSE ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/copyright) + +file(COPY ${CMAKE_CURRENT_LIST_DIR}/FindOpenEXR.cmake DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 1647da045f..cdfcc40eab 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -481,3 +481,15 @@ function prepareAccessTokenPrompt(callback) { swal.close(); }); } + +function getMetaverseUrl(callback) { + $.ajax('/api/metaverse_info', { + success: function(data) { + callback(data.metaverse_url); + }, + error: function() { + callback(URLs.METAVERSE_URL); + } + }); +} + diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 2950b8de75..08d0550841 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -16,47 +16,55 @@ $(document).ready(function(){ Settings.extraGroupsAtEnd = Settings.extraDomainGroupsAtEnd; Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex; + var METAVERSE_URL = URLs.METAVERSE_URL; Settings.afterReloadActions = function() { - // call our method to setup the HF account button - setupHFAccountButton(); - // call our method to setup the place names table - setupPlacesTable(); + getMetaverseUrl(function(metaverse_url) { + METAVERSE_URL = metaverse_url; - setupDomainNetworkingSettings(); - // setupDomainLabelSetting(); + // call our method to setup the HF account button + setupHFAccountButton(); - setupSettingsBackup(); + // call our method to setup the place names table + setupPlacesTable(); - if (domainIDIsSet()) { - // now, ask the API for what places, if any, point to this domain - reloadDomainInfo(); + setupDomainNetworkingSettings(); + // setupDomainLabelSetting(); - // we need to ask the API what a shareable name for this domain is - getShareName(function(success, shareName) { - if (success) { - var shareLink = "https://hifi.place/" + shareName; - $('#visit-domain-link').attr("href", shareLink).show(); - } - }); - } + setupSettingsBackup(); - if (Settings.data.values.wizard.cloud_domain) { - $('#manage-cloud-domains-link').show(); + if (domainIDIsSet()) { + // now, ask the API for what places, if any, point to this domain + reloadDomainInfo(); - var cloudWizardExit = qs["cloud-wizard-exit"]; - if (cloudWizardExit != undefined) { - $('#cloud-domains-alert').show(); + // we need to ask the API what a shareable name for this domain is + getShareName(function(success, shareName) { + if (success) { + var shareLink = "https://hifi.place/" + shareName; + $('#visit-domain-link').attr("href", shareLink).show(); + } + }); + } else if (accessTokenIsSet()) { + $('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).show(); } - $(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("
Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page."); - } else { - // append the domain selection modal - appendDomainIDButtons(); - } + if (Settings.data.values.wizard.cloud_domain) { + $('#manage-cloud-domains-link').show(); - handleAction(); + var cloudWizardExit = qs["cloud-wizard-exit"]; + if (cloudWizardExit != undefined) { + $('#cloud-domains-alert').show(); + } + + $(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("
Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page."); + } else { + // append the domain selection modal + appendDomainIDButtons(); + } + + handleAction(); + }); } Settings.handlePostSettings = function(formJSON) { @@ -258,7 +266,7 @@ $(document).ready(function(){ buttonSetting.button_label = "Connect High Fidelity Account"; buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; - buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; + buttonSetting.href = METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; // since we do not have an access token we change hide domain ID and auto networking settings // without an access token niether of them can do anything @@ -645,7 +653,7 @@ $(document).ready(function(){ label: 'Places', html_id: Settings.PLACES_TABLE_ID, help: "The following places currently point to this domain.
To point places to this domain, " - + " go to the My Places " + + " go to the My Places " + "page in your High Fidelity Metaverse account.", read_only: true, can_add_new_rows: false, @@ -678,12 +686,9 @@ $(document).ready(function(){ var errorEl = createDomainLoadingError("There was an error retrieving your places."); $("#" + Settings.PLACES_TABLE_ID).after(errorEl); - // do we have a domain ID? - if (!domainIDIsSet()) { - // we don't have a domain ID - add a button to offer the user a chance to get a temporary one - var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); - $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); - } + var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); + temporaryPlaceButton.hide(); + $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); if (accessTokenIsSet()) { appendAddButtonToPlacesTable(); } @@ -774,8 +779,9 @@ $(document).ready(function(){ // check if we have owner_places (for a real domain) or a name (for a temporary domain) if (data.status == "success") { + $('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).hide(); $('.domain-loading-hide').show(); - if (data.domain.owner_places) { + if (data.domain.owner_places && data.domain.owner_places.length > 0) { // add a table row for each of these names _.each(data.domain.owner_places, function(place){ $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); @@ -783,8 +789,9 @@ $(document).ready(function(){ } else if (data.domain.name) { // add a table row for this temporary domain name $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); + } else { + $('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).show(); } - // Update label if (showOrHideLabel()) { var label = data.domain.label; @@ -953,7 +960,7 @@ $(document).ready(function(){ modal_buttons["success"] = { label: 'Create new domain', callback: function() { - window.open(URLs.METAVERSE_URL + "/user/domains", '_blank'); + window.open(METAVERSE_URL + "/user/domains", '_blank'); } } modal_body = "

You do not have any domains in your High Fidelity account." + @@ -1001,7 +1008,7 @@ $(document).ready(function(){ showSpinnerAlert('Creating temporary place name'); // make a get request to get a temporary domain - $.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ + $.post(METAVERSE_URL + '/api/v1/domains/temporary', function(data){ if (data.status == "success") { var domain = data.data.domain; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8d5cb165cb..f73c59dc32 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1734,7 +1734,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointerreadPrimitive(&remoteHasExistingData); if (remoteHasExistingData) { constexpr size_t UUID_SIZE_BYTES = 16; auto idData = message->read(UUID_SIZE_BYTES); id = QUuid::fromRfc4122(idData); - message->readPrimitive(&version); - qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << version << ")"; + message->readPrimitive(&dataVersion); + qCDebug(domain_server) << "Entity server does have existing data: ID(" << id << ") DataVersion(" << dataVersion << ")"; } else { qCDebug(domain_server) << "Entity server does not have existing data"; } @@ -1782,11 +1782,11 @@ void DomainServer::processOctreeDataRequestMessage(QSharedPointerwritePrimitive(false); } else { - qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.version << ")"; + qCDebug(domain_server) << "Sending newer octree data to ES: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")"; QFile file(entityFilePath); if (file.open(QIODevice::ReadOnly)) { reply->writePrimitive(true); @@ -1916,6 +1916,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_SETTINGS = "/settings"; const QString URI_CONTENT_UPLOAD = "/content/upload"; const QString URI_RESTART = "/restart"; + const QString URI_API_METAVERSE_INFO = "/api/metaverse_info"; const QString URI_API_PLACES = "/api/places"; const QString URI_API_DOMAINS = "/api/domains"; const QString URI_API_DOMAINS_ID = "/api/domains/"; @@ -2164,6 +2165,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } else if (url.path() == URI_RESTART) { connection->respond(HTTPConnection::StatusCode200); restart(); + return true; + } else if (url.path() == URI_API_METAVERSE_INFO) { + QJsonObject rootJSON { + { "metaverse_url", NetworkingConstants::METAVERSE_SERVER_URL().toString() } + }; + + QJsonDocument docJSON{ rootJSON }; + connectionPtr->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); + return true; } else if (url.path() == URI_API_DOMAINS) { return forwardMetaverseAPIRequest(connection, "/api/v1/domains", ""); diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index b818d371e3..86f559d964 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -13,11 +13,11 @@ { "from": "OculusTouch.LY", "to": "Standard.LY", "filters": [ - { "type": "deadZone", "min": 0.7 }, + { "type": "deadZone", "min": 0.15 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" }, { "from": "OculusTouch.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] @@ -29,11 +29,11 @@ { "from": "OculusTouch.RY", "to": "Standard.RY", "filters": [ - { "type": "deadZone", "min": 0.7 }, + { "type": "deadZone", "min": 0.15 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 28f15605e0..0a5bd12460 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -1,11 +1,14 @@ { "name": "Standard to Action", "channels": [ - { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LY", + "when": ["Application.RightHandDominant", "!Standard.RY"], + "to": "Actions.TranslateZ" + }, { "from": "Standard.LX", "when": [ - "Application.InHMD", "!Application.AdvancedMovement", + "Application.InHMD", "!Application.AdvancedMovement", "Application.RightHandDominant", "Application.SnapTurn", "!Standard.RX" ], "to": "Actions.StepYaw", @@ -18,14 +21,14 @@ ] }, { "from": "Standard.LX", "to": "Actions.TranslateX", - "when": [ "Application.AdvancedMovement" ] + "when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.RightHandDominant" ] }, { "from": "Standard.LX", "to": "Actions.Yaw", - "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn" ] + "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.RightHandDominant" ] }, { "from": "Standard.RX", - "when": [ "Application.SnapTurn" ], + "when": [ "Application.SnapTurn", "Application.RightHandDominant" ], "to": "Actions.StepYaw", "filters": [ @@ -36,20 +39,69 @@ ] }, { "from": "Standard.RX", "to": "Actions.Yaw", - "when": [ "!Application.SnapTurn" ] + "when": [ "!Application.SnapTurn", "Application.RightHandDominant" ] }, + { "from": "Standard.LeftSecondaryThumb", + "when": [ "Application.Grounded", "Application.LeftHandDominant" ], + "to": "Actions.Up" + }, + + { "from": "Standard.LeftSecondaryThumb", + "when": "Application.LeftHandDominant", + "to": "Actions.Up" + }, + { "from": "Standard.RY", - "when": "Application.Grounded", - "to": "Actions.Up", + "when": ["Application.LeftHandDominant", "!Standard.LY"], + "to": "Actions.TranslateZ" + }, + + { "from": "Standard.RX", + "when": [ + "Application.InHMD", "!Application.AdvancedMovement", "Application.LeftHandDominant", + "Application.SnapTurn", "!Standard.RX" + ], + "to": "Actions.StepYaw", "filters": [ - { "type": "deadZone", "min": 0.6 }, - "invert" + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.25 }, + { "type": "scale", "scale": 22.5 } ] }, + { "from": "Standard.RX", "to": "Actions.TranslateX", + "when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.LeftHandDominant" ] + }, + { "from": "Standard.RX", "to": "Actions.Yaw", + "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.LeftHandDominant" ] + }, - { "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"}, + { "from": "Standard.LX", + "when": [ "Application.SnapTurn", "Application.LeftHandDominant" ], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.25 }, + { "type": "scale", "scale": 22.5 } + ] + }, + { "from": "Standard.LX", "to": "Actions.Yaw", + "when": [ "!Application.SnapTurn", "Application.LeftHandDominant" ] + }, + + { "from": "Standard.RightSecondaryThumb", + "when": [ "Application.Grounded", "Application.RightHandDominant" ], + "to": "Actions.Up" + }, + + { "from": "Standard.RightSecondaryThumb", + "when": "Application.RightHandDominant", + "to": "Actions.Up" + }, { "from": "Standard.Back", "to": "Actions.CycleCamera" }, { "from": "Standard.Start", "to": "Actions.ContextMenu" }, @@ -128,4 +180,4 @@ { "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" }, { "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" } ] -} \ No newline at end of file +} diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 24b1587691..730e1bcb58 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -10,8 +10,9 @@ "filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ] }, - { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": ["Vive.LS", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" }, + { "from": "Vive.LX", "when": ["Vive.LS", "Vive.LSX", "!Vive.LSY", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT", "filters": [ @@ -28,8 +29,9 @@ }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": ["Vive.RS", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, + { "from": "Vive.RX", "when": ["Vive.RS", "Vive.RSX", "!Vive.RSY", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT", "filters": [ diff --git a/interface/resources/html/img/tablet-help-gamepad.jpg b/interface/resources/html/img/tablet-help-gamepad.jpg index bc6dcacee0..af44233537 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 f19c399f3a..5c4a3e37ef 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 7e2062400a..1b2a4f4198 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 27b97d71bd..9e8cca69fe 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/html/img/tablet-help-windowsMR.jpg b/interface/resources/html/img/tablet-help-windowsMR.jpg index b9d0241bec..f35c88e35b 100644 Binary files a/interface/resources/html/img/tablet-help-windowsMR.jpg and b/interface/resources/html/img/tablet-help-windowsMR.jpg differ diff --git a/interface/resources/html/tabletHelp.html b/interface/resources/html/tabletHelp.html index dd7931d0ed..1140f045db 100644 --- a/interface/resources/html/tabletHelp.html +++ b/interface/resources/html/tabletHelp.html @@ -53,6 +53,16 @@ position: absolute; top: 0; left: 0; bottom: 0; right: 0; } + + #image_button { + position: absolute; + width: 463; + height: 410; + top: 155; + left: 8; + right: 8; + bottom: 146; + } #report_problem { position: fixed; @@ -67,17 +77,23 @@ var handControllerImageURL = null; var index = 0; var count = 3; + var handControllerRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#vr-controls"; + var keyboardRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/desktop.html#movement-controls"; + var gamepadRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#gamepad"; function showKbm() { document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg"); + document.getElementById("image_button").setAttribute("href", keyboardRefURL); } function showHandControllers() { document.getElementById("main_image").setAttribute("src", handControllerImageURL); + document.getElementById("image_button").setAttribute("href", handControllerRefURL); } function showGamepad() { document.getElementById("main_image").setAttribute("src", "img/tablet-help-gamepad.jpg"); + document.getElementById("image_button").setAttribute("href", gamepadRefURL); } function cycleRight() { @@ -171,6 +187,7 @@ + Report Problem diff --git a/interface/resources/icons/tablet-icons/mic-clip-i.svg b/interface/resources/icons/tablet-icons/mic-clip-i.svg new file mode 100644 index 0000000000..f912c1e744 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-clip-i.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/mic-gate-i.svg b/interface/resources/icons/tablet-icons/mic-gate-i.svg new file mode 100644 index 0000000000..8255174532 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-gate-i.svg @@ -0,0 +1,3 @@ + + + diff --git a/interface/resources/icons/tablet-icons/mic-mute-a.svg b/interface/resources/icons/tablet-icons/mic-mute-a.svg index 9dc2c53443..67eafc27c8 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-a.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-a.svg @@ -1,25 +1,3 @@ - - - - - - - - - - - - - - - + + diff --git a/interface/resources/icons/tablet-icons/mic-mute-i.svg b/interface/resources/icons/tablet-icons/mic-mute-i.svg index 9dc2c53443..63af1b0da8 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-i.svg @@ -1,25 +1,3 @@ - - - - - - - - - - - - - - - + + diff --git a/interface/resources/icons/tablet-icons/mic-mute.svg b/interface/resources/icons/tablet-icons/mic-mute.svg deleted file mode 100644 index bd42fded05..0000000000 --- a/interface/resources/icons/tablet-icons/mic-mute.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-unmute-a.svg b/interface/resources/icons/tablet-icons/mic-unmute-a.svg index b1464f207d..0bf7677017 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-a.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-a.svg @@ -1,70 +1,3 @@ - - - -image/svg+xml \ No newline at end of file + + + diff --git a/interface/resources/icons/tablet-icons/mic-unmute-i.svg b/interface/resources/icons/tablet-icons/mic-unmute-i.svg index c4eda55cbc..0bf7677017 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-i.svg @@ -1,22 +1,3 @@ - - - - - - - - - - - - - + + diff --git a/interface/resources/icons/tablet-icons/mic.svg b/interface/resources/icons/tablet-icons/mic.svg index 30b46d18dd..c961351b14 100644 --- a/interface/resources/icons/tablet-icons/mic.svg +++ b/interface/resources/icons/tablet-icons/mic.svg @@ -59,4 +59,4 @@ d="m 27.9,20.9 c 0,0 0,-3.6 0,-3.8 0,-0.7 -0.6,-1.2 -1.3,-1.2 -0.7,0 -1.2,0.6 -1.2,1.3 0,0.2 0,3.4 0,3.7 0,2.6 -2.4,4.8 -5.3,4.8 -2.9,0 -5.3,-2.1 -5.3,-4.8 0,-0.3 0,-3.5 0,-3.8 0,-0.7 -0.5,-1.3 -1.2,-1.3 -0.7,0 -1.3,0.5 -1.3,1.2 0,0.2 0,3.9 0,3.9 0,3.6 2.9,6.6 6.6,7.2 l 0,2.4 -3.1,0 c -0.7,0 -1.3,0.6 -1.3,1.3 0,0.7 0.6,1.3 1.3,1.3 l 8.8,0 c 0.7,0 1.3,-0.6 1.3,-1.3 0,-0.7 -0.6,-1.3 -1.3,-1.3 l -3.2,0 0,-2.4 c 3.6,-0.5 6.5,-3.5 6.5,-7.2 z" id="path6952" inkscape:connector-curvature="0" - style="fill:#ffffff" /> \ No newline at end of file + style="fill:#ffffff" /> diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index 4a071d2d04..3f1f598991 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -7,24 +7,57 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.4 +import QtQuick 2.5 +import QtGraphicalEffects 1.0 import "./hifi/audio" as HifiAudio +import TabletScriptingInterface 1.0 + Item { id: root; objectName: "AvatarInputsBar" property int modality: Qt.NonModal - width: audio.width; - height: audio.height; - x: 10; y: 5; - + readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; + x: 10; + y: 5; readonly property bool shouldReposition: true; + property bool hmdActive: HMD.active; + width: hmdActive ? audio.width : audioApplication.width; + height: hmdActive ? audio.height : audioApplication.height; + + Timer { + id: hmdActiveCheckTimer; + interval: 500; + repeat: true; + onTriggered: { + root.hmdActive = HMD.active; + } + + } HifiAudio.MicBar { id: audio; - visible: AvatarInputs.showAudioTools; + visible: AvatarInputs.showAudioTools && root.hmdActive; standalone: true; - dragTarget: parent; + dragTarget: parent; + } + + HifiAudio.MicBarApplication { + id: audioApplication; + visible: AvatarInputs.showAudioTools && !root.hmdActive; + standalone: true; + dragTarget: parent; + } + + Component.onCompleted: { + HMD.displayModeChanged.connect(function(isHmdMode) { + root.hmdActive = isHmdMode; + }); + } + + BubbleIcon { + dragTarget: parent + visible: !root.hmdActive; } } diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml new file mode 100644 index 0000000000..f4e99f136c --- /dev/null +++ b/interface/resources/qml/BubbleIcon.qml @@ -0,0 +1,100 @@ +// +// Created by Bradley Austin Davis on 2015/06/19 +// 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "./hifi/audio" as HifiAudio + +import TabletScriptingInterface 1.0 + +Rectangle { + id: bubbleRect + width: bubbleIcon.width + 10 + height: bubbleIcon.height + 10 + radius: 5; + property var dragTarget: null; + property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; + + function updateOpacity() { + if (ignoreRadiusEnabled) { + bubbleRect.opacity = 1.0; + } else { + bubbleRect.opacity = 0.7; + } + } + + Component.onCompleted: { + updateOpacity(); + } + + onIgnoreRadiusEnabledChanged: { + updateOpacity(); + } + + color: "#00000000"; + border { + width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; + color: "#80FFFFFF"; + } + anchors { + left: dragTarget ? dragTarget.right : undefined + top: dragTarget ? dragTarget.top : undefined + } + + // borders are painted over fill, so reduce the fill to fit inside the border + Rectangle { + color: "#55000000"; + width: 40; + height: 40; + + radius: 5; + + anchors { + verticalCenter: parent.verticalCenter; + horizontalCenter: parent.horizontalCenter; + } + } + + MouseArea { + id: mouseArea; + anchors.fill: parent + + hoverEnabled: true; + scrollGestureEnabled: false; + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + Users.toggleIgnoreRadius(); + } + drag.target: dragTarget; + onContainsMouseChanged: { + var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7); + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + } + bubbleRect.opacity = rectOpacity; + } + } + Image { + id: bubbleIcon + source: "../icons/tablet-icons/bubble-i.svg"; + sourceSize: Qt.size(32, 32); + smooth: true; + anchors.top: parent.top + anchors.topMargin: (parent.height - bubbleIcon.height) / 2 + anchors.left: parent.left + anchors.leftMargin: (parent.width - bubbleIcon.width) / 2 + } + ColorOverlay { + id: bubbleIconOverlay + anchors.fill: bubbleIcon + source: bubbleIcon + color: "#FFFFFF"; + } +} diff --git a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml index 91d35ecd58..bd71025aa9 100644 --- a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml @@ -24,6 +24,7 @@ CheckBox { leftPadding: 0 property int colorScheme: hifi.colorSchemes.light property string color: hifi.colors.lightGrayText + property int fontSize: hifi.fontSizes.inputLabel readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property bool isRedCheck: false property bool isRound: false @@ -109,7 +110,7 @@ CheckBox { contentItem: Text { id: root - font.pixelSize: hifi.fontSizes.inputLabel + font.pixelSize: fontSize; font.family: "Raleway" font.weight: Font.DemiBold text: checkBox.text diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml index 4e1c21c456..422b08b4eb 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -21,6 +21,7 @@ Item { property int switchWidth: 70; readonly property int switchRadius: height/2; property string labelTextOff: ""; + property int labelTextSize: hifi.fontSizes.inputLabel; property string labelGlyphOffText: ""; property int labelGlyphOffSize: 32; property string labelTextOn: ""; @@ -89,7 +90,7 @@ Item { RalewaySemiBold { id: labelOff; text: labelTextOff; - size: hifi.fontSizes.inputLabel; + size: labelTextSize; color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF"; anchors.top: parent.top; anchors.right: parent.right; @@ -130,7 +131,7 @@ Item { RalewaySemiBold { id: labelOn; text: labelTextOn; - size: hifi.fontSizes.inputLabel; + size: labelTextSize; color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText; anchors.top: parent.top; anchors.left: parent.left; diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index ba5e162391..4eea3566b8 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -320,6 +320,7 @@ ModalWindow { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 6c4e32dc5a..5bcc42f101 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -285,6 +285,7 @@ TabletModalWindow { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/resources/qml/hifi/EditAvatarInputsBar.qml b/interface/resources/qml/hifi/EditAvatarInputsBar.qml new file mode 100644 index 0000000000..b27b0c8db2 --- /dev/null +++ b/interface/resources/qml/hifi/EditAvatarInputsBar.qml @@ -0,0 +1,152 @@ +// +// EditAvatarInputsBar.qml +// qml/hifi +// +// Audio setup +// +// Created by Wayne Chen on 3/20/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../windows" + +Rectangle { + id: editRect + + HifiConstants { id: hifi; } + + color: hifi.colors.baseGray; + + signal sendToScript(var message); + function emitSendToScript(message) { + sendToScript(message); + } + + function fromScript(message) { + } + + RalewayRegular { + id: title; + color: hifi.colors.white; + text: qsTr("Avatar Inputs Persistent UI Settings") + size: 20 + font.bold: true + anchors { + top: parent.top + left: parent.left + leftMargin: (parent.width - width) / 2 + } + } + + HifiControlsUit.Slider { + id: xSlider + anchors { + top: title.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + label: "X OFFSET: " + value.toFixed(2); + maximumValue: 1.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.2 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "x": value + }); + } + } + + HifiControlsUit.Slider { + id: ySlider + anchors { + top: xSlider.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + label: "Y OFFSET: " + value.toFixed(2); + maximumValue: 1.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.125 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "y": value + }); + } + } + + HifiControlsUit.Slider { + id: zSlider + anchors { + top: ySlider.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + label: "Z OFFSET: " + value.toFixed(2); + maximumValue: 0.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.5 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "z": value + }); + } + } + + HifiControlsUit.Button { + id: setVisibleButton; + text: setVisible ? "SET INVISIBLE" : "SET VISIBLE"; + width: 300; + property bool setVisible: true; + anchors { + top: zSlider.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + onClicked: { + setVisible = !setVisible; + emitSendToScript({ + "method": "setVisible", + "visible": setVisible + }); + } + } + + HifiControlsUit.Button { + id: printButton; + text: "PRINT POSITIONS"; + width: 300; + anchors { + top: setVisibleButton.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + onClicked: { + emitSendToScript({ + "method": "print", + }); + } + } +} diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 141ddf0077..7e8218b7df 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -129,6 +129,7 @@ Item { height: 40 // Anchors anchors.top: avatarImage.top + anchors.topMargin: avatarImage.visible ? 18 : 0; anchors.left: avatarImage.right anchors.leftMargin: avatarImage.visible ? 5 : 0; anchors.rightMargin: 5; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 195dd78a0e..f7e2494813 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -31,6 +31,8 @@ Rectangle { property string title: "Audio Settings" property int switchHeight: 16 property int switchWidth: 40 + property bool pushToTalk: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.mutedDesktop : AudioScriptingInterface.mutedHMD; readonly property real verticalScrollWidth: 10 readonly property real verticalScrollShaft: 8 signal sendToScript(var message); @@ -44,7 +46,7 @@ Rectangle { property bool isVR: AudioScriptingInterface.context === "VR" - property real rightMostInputLevelPos: 440 + property real rightMostInputLevelPos: root.width //placeholder for control sizes and paddings //recalculates dynamically in case of UI size is changed QtObject { @@ -103,7 +105,9 @@ Rectangle { } } - Component.onCompleted: enablePeakValues(); + Component.onCompleted: { + enablePeakValues(); + } Flickable { id: flickView; @@ -178,15 +182,25 @@ Rectangle { height: root.switchHeight; switchWidth: root.switchWidth; labelTextOn: "Mute microphone"; + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; - checked: AudioScriptingInterface.muted; + checked: muted; onClicked: { - if (AudioScriptingInterface.pushToTalk && !checked) { + if (pushToTalk && !checked) { // disable push to talk if unmuting - AudioScriptingInterface.pushToTalk = false; + if (bar.currentIndex === 0) { + AudioScriptingInterface.pushToTalkDesktop = false; + } + else { + AudioScriptingInterface.pushToTalkHMD = false; + } + } + if (bar.currentIndex === 0) { + AudioScriptingInterface.mutedDesktop = checked; + } + else { + AudioScriptingInterface.mutedHMD = checked; } - AudioScriptingInterface.muted = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } @@ -198,6 +212,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: "Noise Reduction"; + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.noiseReduction; onCheckedChanged: { @@ -213,7 +228,8 @@ Rectangle { anchors.top: noiseReductionSwitch.bottom anchors.topMargin: 24 anchors.left: parent.left - labelTextOn: qsTr("Push To Talk (T)"); + labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; onCheckedChanged: { @@ -222,13 +238,6 @@ Rectangle { } else { AudioScriptingInterface.pushToTalkHMD = checked; } - checked = Qt.binding(function() { - if (bar.currentIndex === 0) { - return AudioScriptingInterface.pushToTalkDesktop; - } else { - return AudioScriptingInterface.pushToTalkHMD; - } - }); // restore binding } } } @@ -245,7 +254,8 @@ Rectangle { switchWidth: root.switchWidth; anchors.top: parent.top anchors.left: parent.left - labelTextOn: qsTr("Warn when muted"); + labelTextOn: qsTr("Warn when muted in HMD"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.warnWhenMuted; onClicked: { @@ -263,6 +273,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: qsTr("Audio Level Meter"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AvatarInputs.showAudioTools; onCheckedChanged: { @@ -279,6 +290,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: qsTr("Stereo input"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.isStereoInput; onCheckedChanged: { @@ -314,7 +326,7 @@ Rectangle { Separator { id: secondSeparator; - anchors.top: pttTextContainer.bottom; + anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom; anchors.topMargin: 10; } @@ -341,7 +353,7 @@ Rectangle { width: margins.sizeText + margins.sizeLevel; anchors.left: parent.left; anchors.leftMargin: margins.sizeCheckBox; - size: 16; + size: 22; color: hifi.colors.white; text: qsTr("Choose input device"); } @@ -349,16 +361,17 @@ Rectangle { ListView { id: inputView; - width: parent.width - margins.paddings*2; + width: rightMostInputLevelPos; anchors.top: inputDeviceHeader.bottom; anchors.topMargin: 10; x: margins.paddings + interactive: false; height: contentHeight; spacing: 4; clip: true; model: AudioScriptingInterface.devices.input; delegate: Item { - width: rightMostInputLevelPos + width: rightMostInputLevelPos - margins.paddings*2 height: margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox : checkBoxInput.implicitHeight @@ -374,6 +387,7 @@ Rectangle { boxSize: margins.sizeCheckBox / 2 isRound: true text: devicename + fontSize: 16; onPressed: { if (!checked) { stereoInput.checked = false; @@ -407,7 +421,7 @@ Rectangle { Separator { id: thirdSeparator; - anchors.top: loopbackAudio.bottom; + anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom; anchors.topMargin: 10; } @@ -434,7 +448,7 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: margins.sizeCheckBox anchors.verticalCenter: parent.verticalCenter; - size: 16; + size: 22; color: hifi.colors.white; text: qsTr("Choose output device"); } @@ -443,7 +457,8 @@ Rectangle { ListView { id: outputView width: parent.width - margins.paddings*2 - x: margins.paddings + x: margins.paddings; + interactive: false; height: contentHeight; anchors.top: outputDeviceHeader.bottom; anchors.topMargin: 10; @@ -464,6 +479,7 @@ Rectangle { checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; checkable: !checked text: devicename + fontSize: 16 onPressed: { if (!checked) { AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1); @@ -526,7 +542,7 @@ Rectangle { RalewayRegular { // The slider for my card is special, it controls the master gain id: avatarGainSliderText; - text: "Avatar volume"; + text: "People volume"; size: 16; anchors.left: parent.left; color: hifi.colors.white; diff --git a/interface/resources/qml/hifi/audio/InputPeak.qml b/interface/resources/qml/hifi/audio/InputPeak.qml index 00f7e63528..d8b166cee4 100644 --- a/interface/resources/qml/hifi/audio/InputPeak.qml +++ b/interface/resources/qml/hifi/audio/InputPeak.qml @@ -12,24 +12,26 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -Rectangle { +Item { property var peak; width: 70; height: 8; - color: "transparent"; - - Item { + QtObject { id: colors; + readonly property string unmuted: "#FFF"; readonly property string muted: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; readonly property string red: colors.muted; + readonly property string fill: "#55000000"; } + Text { id: status; @@ -79,23 +81,19 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(70, 0); + end: Qt.point(bar.width, 0); gradient: Gradient { GradientStop { position: 0; color: colors.greenStart; } GradientStop { - position: 0.8; + position: 0.5; color: colors.greenEnd; } - GradientStop { - position: 0.801; - color: colors.red; - } GradientStop { position: 1; - color: colors.red; + color: colors.yellow; } } } diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index db0614dfdc..d3fd8ee075 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -60,11 +60,11 @@ RowLayout { } } -// RalewayRegular { -// Layout.leftMargin: 2; -// size: 14; -// color: "white"; -// font.italic: true -// text: audioLoopedBack ? qsTr("Speak in your input") : ""; -// } + RalewayRegular { + Layout.leftMargin: 2; + size: 18; + color: "white"; + font.italic: true + text: audioLoopedBack ? qsTr("Speak in your input") : ""; + } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f51da9c381..55378589ec 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -16,14 +16,33 @@ import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + id: micBar HifiConstants { id: hifi; } + property var muted: AudioScriptingInterface.muted; readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var clipping: AudioScriptingInterface.clipping; + property var pushToTalk: AudioScriptingInterface.pushToTalk; + property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + HMD.displayModeChanged.connect(function() { + muted = AudioScriptingInterface.muted; + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + AudioScriptingInterface.mutedChanged.connect(function() { + muted = AudioScriptingInterface.muted; + }); + AudioScriptingInterface.pushToTalkChanged.connect(function() { + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + AudioScriptingInterface.pushingToTalkChanged.connect(function() { + pushingToTalk = AudioScriptingInterface.pushingToTalk; + }); } property bool standalone: false; @@ -67,10 +86,10 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { - if (AudioScriptingInterface.pushToTalk) { + if (pushToTalk) { return; } - AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + AudioScriptingInterface.muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; @@ -84,16 +103,16 @@ Rectangle { QtObject { id: colors; - readonly property string unmuted: "#FFF"; - readonly property string muted: "#E2334D"; + readonly property string unmutedColor: "#FFF"; + readonly property string mutedColor: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; readonly property string yellow: "#C0C000"; - readonly property string red: colors.muted; + readonly property string red: colors.mutedColor; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; + readonly property string icon: muted ? colors.mutedColor : unmutedColor; } Item { @@ -113,9 +132,12 @@ Rectangle { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; + readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; + readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; id: image; - source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : + clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 30; height: 30; @@ -138,9 +160,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - - visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; + visible: (pushToTalk && !pushingToTalk) || muted; anchors { left: parent.left; @@ -157,9 +177,9 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: parent.color; + color: colors.icon; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -169,9 +189,9 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } Rectangle { @@ -180,9 +200,9 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } } diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml new file mode 100644 index 0000000000..bc3f4dff89 --- /dev/null +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -0,0 +1,255 @@ +// +// MicBarApplication.qml +// qml/hifi/audio +// +// Created by Zach Pomerantz on 6/14/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 QtGraphicalEffects 1.0 + +import stylesUit 1.0 +import TabletScriptingInterface 1.0 + +Rectangle { + id: micBar; + readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var clipping: AudioScriptingInterface.clipping; + property var muted: AudioScriptingInterface.muted; + property var pushToTalk: AudioScriptingInterface.pushToTalk; + property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + readonly property var userSpeakingLevel: 0.4; + property bool gated: false; + Component.onCompleted: { + AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + HMD.displayModeChanged.connect(function() { + muted = AudioScriptingInterface.muted; + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + AudioScriptingInterface.mutedChanged.connect(function() { + muted = AudioScriptingInterface.muted; + }); + AudioScriptingInterface.pushToTalkChanged.connect(function() { + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + } + + readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; + readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; + readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; + readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; + property bool standalone: false; + property var dragTarget: null; + + width: 44; + height: 44; + + radius: 5; + opacity: 0.7; + + onLevelChanged: { + var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7; + if (pushToTalk && !pushingToTalk) { + rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7; + } else if (mouseArea.containsMouse && rectOpacity != 1.0) { + rectOpacity = 1.0; + } + micBar.opacity = rectOpacity; + } + + color: "#00000000"; + border { + width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; + color: colors.border; + } + + // borders are painted over fill, so reduce the fill to fit inside the border + Rectangle { + color: standalone ? colors.fill : "#00000000"; + width: 40; + height: 40; + + radius: 5; + + anchors { + verticalCenter: parent.verticalCenter; + horizontalCenter: parent.horizontalCenter; + } + } + + MouseArea { + id: mouseArea; + + anchors { + left: icon.left; + right: bar.right; + top: icon.top; + bottom: icon.bottom; + } + + hoverEnabled: true; + scrollGestureEnabled: false; + onClicked: { + if (pushToTalk) { + return; + } + AudioScriptingInterface.muted = !muted; + Tablet.playSound(TabletEnums.ButtonClick); + muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding + } + drag.target: dragTarget; + onContainsMouseChanged: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + } + + QtObject { + id: colors; + + readonly property string unmutedColor: "#FFF"; + readonly property string gatedColor: "#00BDFF"; + readonly property string mutedColor: "#E2334D"; + readonly property string gutter: "#575757"; + readonly property string greenStart: "#39A38F"; + readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; + readonly property string fill: "#55000000"; + readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; + readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor; + } + + Item { + id: icon; + + anchors { + left: parent.left; + top: parent.top; + } + + width: 40; + height: 40; + + Item { + Image { + id: image; + source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : + clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; + width: 29; + height: 32; + anchors { + left: parent.left; + top: parent.top; + topMargin: 5; + } + } + + ColorOverlay { + id: imageOverlay + anchors { fill: image } + source: image; + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon; + } + } + } + + Item { + id: status; + + visible: pushToTalk || (muted && (level >= userSpeakingLevel)); + + anchors { + left: parent.left; + top: icon.bottom; + topMargin: 2; + } + + width: parent.width; + height: statusTextMetrics.height; + + TextMetrics { + id: statusTextMetrics + text: statusText.text + font: statusText.font + } + + RalewaySemiBold { + id: statusText + anchors { + horizontalCenter: parent.horizontalCenter; + verticalCenter: parent.verticalCenter; + } + + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; + font.bold: true + + text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); + size: 12; + } + } + + Item { + id: bar; + + anchors { + right: parent.right; + rightMargin: 7; + top: parent.top + topMargin: 5 + } + + width: 8; + height: 32; + + Rectangle { // base + id: baseBar + radius: 4; + anchors { fill: parent } + color: colors.gutter; + } + + Rectangle { // mask + id: mask; + visible: (!(pushToTalk && !pushingToTalk)) + height: parent.height * level; + width: parent.width; + radius: 5; + anchors { + bottom: parent.bottom; + bottomMargin: 0; + left: parent.left; + leftMargin: 0; + } + } + + LinearGradient { + anchors { fill: mask } + visible: (!(pushToTalk && !pushingToTalk)) + source: mask + start: Qt.point(0, 0); + end: Qt.point(0, bar.height); + rotation: 180 + gradient: Gradient { + GradientStop { + position: 0.0; + color: colors.greenStart; + } + GradientStop { + position: 0.5; + color: colors.greenEnd; + } + GradientStop { + position: 1.0; + color: colors.yellow; + } + } + } + } +} diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 033af99d04..f1351b8cbf 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -64,11 +64,11 @@ RowLayout { height: 32; } -// RalewayRegular { -// Layout.leftMargin: 2; -// size: 14; -// color: "white"; -// font.italic: true -// text: isPlaying ? qsTr("Listen to your output") : ""; -// } + RalewayRegular { + Layout.leftMargin: 2; + size: 18; + color: "white"; + font.italic: true + text: isPlaying ? qsTr("Listen to your output") : ""; + } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 619547ef43..5f8cc2eefb 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -336,6 +336,8 @@ Rectangle { case Qt.Key_Return: case Qt.Key_Enter: event.accepted = true; + keypressTimer.stop(); + root.searchString = searchField.text; searchField.text = ""; getMarketplaceItems(); diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index a1da69a44a..1a1e0a96ff 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -40,7 +40,7 @@ Item { } } - HifiAudio.MicBar { + HifiAudio.MicBarApplication { anchors { left: parent.left leftMargin: 30 diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index a27c7b59dc..36a37134bf 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 +import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 1.4 as QQC1 @@ -279,6 +279,7 @@ Rectangle { FolderListModel { id: folderListModel nameFilters: selectionType.currentFilter + caseSensitive: false showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 47f3b774b2..d1add60647 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -338,6 +338,10 @@ Setting::Handle maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE Setting::Handle loginDialogPoppedUp{"loginDialogPoppedUp", false}; +static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml"); +static const QUrl MIC_BAR_APPLICATION_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml"); +static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml"); + static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action"; static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)"; static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json"; @@ -676,6 +680,8 @@ private: *
InHMDnumbernumberThe user is in HMD mode. * AdvancedMovementnumbernumberAdvanced movement controls are enabled. * + * LeftHandDominantnumbernumberDominant hand set to left. + * RightHandDominantnumbernumberDominant hand set to right. * SnapTurnnumbernumberSnap turn is enabled. * GroundednumbernumberThe user's avatar is on the ground. * NavigationFocusednumbernumberNot used. @@ -697,6 +703,9 @@ static const QString STATE_NAV_FOCUSED = "NavigationFocused"; static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows"; static const QString STATE_PLATFORM_MAC = "PlatformMac"; static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid"; +static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant"; +static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant"; +static const QString STATE_STRAFE_ENABLED = "StrafeEnabled"; // Statically provided display and input plugins extern DisplayPluginList getDisplayPlugins(); @@ -898,7 +907,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR, STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED, - STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } }); + STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } }); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1442,6 +1451,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _overlays.init(); // do this before scripts load DependencyManager::set(); + auto offscreenUi = getOffscreenUI(); + connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() { + // Now that we've loaded the menu and thus switched to the previous display plugin + // we can unlock the desktop repositioning code, since all the positions will be + // relative to the desktop size for this plugin + auto offscreenUi = getOffscreenUI(); + auto desktop = offscreenUi->getDesktop(); + if (desktop) { + desktop->setProperty("repositionLocked", false); + } + }); + + connect(offscreenUi.data(), &OffscreenUi::keyboardFocusActive, [this]() { +#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML) + // Do not show login dialog if requested not to on the command line + QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY); + int index = arguments().indexOf(hifiNoLoginCommandLineKey); + if (index != -1) { + resumeAfterLoginDialogActionTaken(); + return; + } + + showLoginScreen(); +#else + resumeAfterLoginDialogActionTaken(); +#endif + }); + // Initialize the user interface and menu system // Needs to happen AFTER the render engine initialization to access its configuration initializeUi(); @@ -1736,6 +1773,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float { return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0; }); + _applicationStateDevice->setInputVariant(STATE_LEFT_HAND_DOMINANT, []() -> float { + return qApp->getMyAvatar()->getDominantHand() == "left" ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_RIGHT_HAND_DOMINANT, []() -> float { + return qApp->getMyAvatar()->getDominantHand() == "right" ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_STRAFE_ENABLED, []() -> float { + return qApp->getMyAvatar()->getStrafeEnabled() ? 1 : 0; + }); _applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float { return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0; @@ -1787,34 +1833,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateVerboseLogging(); - // Now that we've loaded the menu and thus switched to the previous display plugin - // we can unlock the desktop repositioning code, since all the positions will be - // relative to the desktop size for this plugin - auto offscreenUi = getOffscreenUI(); - connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() { - auto offscreenUi = getOffscreenUI(); - auto desktop = offscreenUi->getDesktop(); - if (desktop) { - desktop->setProperty("repositionLocked", false); - } - }); - - connect(offscreenUi.data(), &OffscreenUi::keyboardFocusActive, [this]() { -#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML) - // Do not show login dialog if requested not to on the command line - QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY); - int index = arguments().indexOf(hifiNoLoginCommandLineKey); - if (index != -1) { - resumeAfterLoginDialogActionTaken(); - return; - } - - showLoginScreen(); -#else - resumeAfterLoginDialogActionTaken(); -#endif - }); - // Make sure we don't time out during slow operations at startup updateHeartbeat(); QTimer* settingsTimer = new QTimer(); @@ -2372,7 +2390,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); }); auto rootItemLoadedFunctor = [webSurface, url, isTablet] { - Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString()); + Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() || + url == BUBBLE_ICON_QML.toString()); }; if (webSurface->getRootItem()) { rootItemLoadedFunctor(); @@ -2878,11 +2897,19 @@ void Application::initializeGL() { } #if !defined(DISABLE_QML) + QStringList chromiumFlags; + // Bug 21993: disable microphone and camera input + chromiumFlags << "--use-fake-device-for-media-stream"; // Disable signed distance field font rendering on ATI/AMD GPUs, due to // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app std::string vendor{ (const char*)glGetString(GL_VENDOR) }; if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + chromiumFlags << "--disable-distance-field-text"; + } + + // Ensure all Qt webengine processes launched from us have the appropriate command line flags + if (!chromiumFlags.empty()) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.join(' ').toLocal8Bit()); } #endif @@ -3291,6 +3318,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml"); offscreenUi->show(qml, "AvatarInputsBar"); #endif + _desktopRootItemCreated = true; } void Application::userKickConfirmation(const QUuid& nodeID) { @@ -3722,14 +3750,11 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (_firstRun.get()) { -#if !defined(Q_OS_ANDROID) DependencyManager::get()->goToEntry(); sentTo = SENT_TO_ENTRY; -#endif _firstRun.set(false); } else { -#if !defined(Q_OS_ANDROID) QString goingTo = ""; if (addressLookupString.isEmpty()) { if (Menu::getInstance()->isOptionChecked(MenuOption::HomeLocation)) { @@ -3743,7 +3768,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(!goingTo.isEmpty() ? goingTo : addressLookupString); DependencyManager::get()->loadSettings(addressLookupString); sentTo = SENT_TO_PREVIOUS_LOCATION; -#endif } UserActivityLogger::getInstance().logAction("startup_sent_to", { @@ -3957,6 +3981,15 @@ static void dumpEventQueue(QThread* thread) { } #endif // DEBUG_EVENT_QUEUE +bool Application::notify(QObject * object, QEvent * event) { + if (thread() == QThread::currentThread()) { + PROFILE_RANGE_IF_LONGER(app, "notify", 2) + return QApplication::notify(object, event); + } + + return QApplication::notify(object, event); +} + bool Application::event(QEvent* event) { if (_aboutToQuit) { @@ -5448,6 +5481,13 @@ void Application::pauseUntilLoginDetermined() { // disconnect domain handler. nodeList->getDomainHandler().disconnect(); + // From now on, it's permissible to call resumeAfterLoginDialogActionTaken() + _resumeAfterLoginDialogActionTaken_SafeToRun = true; + + if (_resumeAfterLoginDialogActionTaken_WasPostponed) { + // resumeAfterLoginDialogActionTaken() was already called, but it aborted. Now it's safe to call it again. + resumeAfterLoginDialogActionTaken(); + } } void Application::resumeAfterLoginDialogActionTaken() { @@ -5456,6 +5496,11 @@ void Application::resumeAfterLoginDialogActionTaken() { return; } + if (!_resumeAfterLoginDialogActionTaken_SafeToRun) { + _resumeAfterLoginDialogActionTaken_WasPostponed = true; + return; + } + if (!isHMDMode() && getDesktopTabletBecomesToolbarSetting()) { auto toolbar = DependencyManager::get()->getToolbar("com.highfidelity.interface.toolbar.system"); toolbar->writeProperty("visible", true); @@ -8972,6 +9017,38 @@ void Application::updateLoginDialogPosition() { } } +void Application::createAvatarInputsBar() { + const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 }; + // DEFAULT_DPI / tablet scale percentage + const float DPI = 31.0f / (75.0f / 100.0f); + + EntityItemProperties properties; + properties.setType(EntityTypes::Web); + properties.setName("AvatarInputsBarEntity"); + properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString()); + properties.setParentID(getMyAvatar()->getSelfID()); + properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX")); + properties.setPosition(LOCAL_POSITION); + properties.setLocalRotation(Quaternions::IDENTITY); + //properties.setDimensions(LOGIN_DIMENSIONS); + properties.setPrimitiveMode(PrimitiveMode::SOLID); + properties.getGrab().setGrabbable(false); + properties.setIgnorePickIntersection(false); + properties.setAlpha(1.0f); + properties.setDPI(DPI); + properties.setVisible(true); + + auto entityScriptingInterface = DependencyManager::get(); + _avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); +} + +void Application::destroyAvatarInputsBar() { + auto entityScriptingInterface = DependencyManager::get(); + if (!_avatarInputsBarID.isNull()) { + entityScriptingInterface->deleteEntity(_avatarInputsBarID); + } +} + bool Application::hasRiftControllers() { return PluginUtils::isOculusTouchControllerAvailable(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 762ac9585a..e0e8821037 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -1,4 +1,4 @@ -// +// // Application.h // interface/src // @@ -156,6 +156,7 @@ public: void updateCamera(RenderArgs& renderArgs, float deltaTime); void resizeGL(); + bool notify(QObject *, QEvent *) override; bool event(QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override; @@ -330,6 +331,9 @@ public: void createLoginDialog(); void updateLoginDialogPosition(); + void createAvatarInputsBar(); + void destroyAvatarInputsBar(); + // Check if a headset is connected bool hasRiftControllers(); bool hasViveControllers(); @@ -704,12 +708,14 @@ private: int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS; bool _interstitialModeEnabled{ false }; - bool _loginDialogPoppedUp = false; + bool _loginDialogPoppedUp{ false }; + bool _desktopRootItemCreated{ false }; bool _developerMenuVisible{ false }; QString _previousAvatarSkeletonModel; float _previousAvatarTargetScale; CameraMode _previousCameraMode; QUuid _loginDialogID; + QUuid _avatarInputsBarID; LoginStateManager _loginStateManager; quint64 _lastFaceTrackerUpdate; @@ -802,5 +808,8 @@ private: bool _showTrackedObjects { false }; bool _prevShowTrackedObjects { false }; + + bool _resumeAfterLoginDialogActionTaken_WasPostponed { false }; + bool _resumeAfterLoginDialogActionTaken_SafeToRun { false }; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 394c07e842..e9aadea2b6 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -116,7 +116,7 @@ Menu::Menu() { // Edit > Delete auto deleteAction = addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete); connect(deleteAction, &QAction::triggered, [] { - QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::ControlModifier); + QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier); QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); }); diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 43e50ea049..01a40e89fd 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -92,7 +92,7 @@ void AvatarDoctor::startDiagnosing() { _model = resource; const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); - if (!avatarModel.originalURL.endsWith(".fbx")) { + if (!avatarModel.originalURL.toLower().endsWith(".fbx")) { addError("Unsupported avatar model format.", "unsupported-format"); emit complete(getErrors()); return; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 35022c882f..cf4b377642 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "Application.h" #include "InterfaceLogging.h" @@ -84,7 +85,6 @@ AvatarManager::AvatarManager(QObject* parent) : AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); - const auto otherAvatar = std::static_pointer_cast(avatar); if (otherAvatar && _space) { std::unique_lock lock(_spaceLock); @@ -210,7 +210,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { { // lock the hash for read to check the size QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFadeOut.isEmpty()) { + if (_avatarHash.size() < 2) { return; } } @@ -375,19 +375,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { qApp->getMain3DScene()->enqueueTransaction(renderTransaction); } - if (!_spaceProxiesToDelete.empty() && _space) { - std::unique_lock lock(_spaceLock); - workloadTransaction.remove(_spaceProxiesToDelete); - _spaceProxiesToDelete.clear(); - } _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAvatarsNotUpdated; _numHeroAvatarsUpdated = numHerosUpdated; - simulateAvatarFades(deltaTime); - _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; } @@ -400,31 +393,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } -void AvatarManager::simulateAvatarFades(float deltaTime) { - if (_avatarsToFadeOut.empty()) { - return; - } - - QReadLocker locker(&_hashLock); - QVector::iterator avatarItr = _avatarsToFadeOut.begin(); - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - while (avatarItr != _avatarsToFadeOut.end()) { - auto avatar = std::static_pointer_cast(*avatarItr); - avatar->updateFadingStatus(); - if (!avatar->isFading()) { - // fading to zero is such a rare event we push a unique transaction for each - if (avatar->isInScene()) { - avatar->removeFromScene(*avatarItr, scene, transaction); - } - avatarItr = _avatarsToFadeOut.erase(avatarItr); - } else { - ++avatarItr; - } - } - scene->enqueueTransaction(transaction); -} - AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { auto otherAvatar = new OtherAvatar(qApp->thread()); otherAvatar->setSessionUUID(sessionUUID); @@ -452,7 +420,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToRemove.push_back(mState); } avatar->resetDetailedMotionStates(); - } else { if (avatar->getDetailedMotionStates().size() == 0) { avatar->createDetailedMotionStates(avatar); @@ -520,10 +487,6 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities) void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { auto avatar = std::static_pointer_cast(removedAvatar); - { - std::unique_lock lock(_spaceLock); - _spaceProxiesToDelete.push_back(avatar->getSpaceIndex()); - } AvatarHashMap::handleRemovedAvatar(avatar, removalReason); avatar->tearDownGrabs(); @@ -534,16 +497,39 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // it might not fire until after we create a new instance for the same remote avatar, which creates a race // on the creation of entities for that avatar instance and the deletion of entities for this instance avatar->removeAvatarEntitiesFromTree(); - - if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { + if (removalReason != KillAvatarReason::AvatarDisconnected) { + emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID()); emit DependencyManager::get()->enteredIgnoreRadius(); - } else if (removalReason == KillAvatarReason::AvatarDisconnected) { + + workload::Transaction workloadTransaction; + workloadTransaction.remove(avatar->getSpaceIndex()); + _space->enqueueTransaction(workloadTransaction); + + const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; + avatar->removeFromScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); + } else { // remove from node sets, if present DependencyManager::get()->removeFromIgnoreMuteSets(avatar->getSessionUUID()); DependencyManager::get()->avatarDisconnected(avatar->getSessionUUID()); - avatar->fadeOut(qApp->getMain3DScene(), removalReason); + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + avatar->fadeOut(transaction, removalReason); + + workload::SpacePointer space = _space; + transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() { + const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; + avatar->removeFromScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); + + workload::Transaction workloadTransaction; + workloadTransaction.remove(avatar->getSpaceIndex()); + space->enqueueTransaction(workloadTransaction); + }); + scene->enqueueTransaction(transaction); } - _avatarsToFadeOut.push_back(removedAvatar); } void AvatarManager::clearOtherAvatars() { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 64bcb8dceb..678dc5a3e2 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -269,8 +269,6 @@ private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); - void simulateAvatarFades(float deltaTime); - AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override; // called only from the AvatarHashMap thread - cannot be called while this thread holds the @@ -280,8 +278,6 @@ private: KillAvatarReason removalReason = KillAvatarReason::NoReason) override; void handleTransitAnimations(AvatarTransit::Status status); - QVector _avatarsToFadeOut; - using SetOfOtherAvatars = std::set; SetOfOtherAvatars _avatarsToChangeInPhysics; @@ -301,7 +297,6 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space; - std::vector _spaceProxiesToDelete; AvatarTransit::TransitConfig _transitConfig; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 27151d0615..c0cf63d7e4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -155,6 +155,7 @@ MyAvatar::MyAvatar(QThread* thread) : _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _strafeEnabledSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "strafeEnabled", DEFAULT_STRAFE_ENABLED), _hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE), _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), @@ -169,7 +170,16 @@ MyAvatar::MyAvatar(QThread* thread) : _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn), _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT), _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD), + _movementReferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "movementReference", _movementReference), _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0), + _driveGear1Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear1", _driveGear1), + _driveGear2Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear2", _driveGear2), + _driveGear3Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear3", _driveGear3), + _driveGear4Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear4", _driveGear4), + _driveGear5Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear5", _driveGear5), + _analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()), + _analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()), + _controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex), _userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); @@ -322,6 +332,14 @@ QString MyAvatar::getDominantHand() const { return _dominantHand.get(); } +void MyAvatar::setStrafeEnabled(bool enabled) { + _strafeEnabled.set(enabled); +} + +bool MyAvatar::getStrafeEnabled() const { + return _strafeEnabled.get(); +} + void MyAvatar::setDominantHand(const QString& hand) { if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) { bool changed = (hand != _dominantHand.get()); @@ -800,20 +818,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) { if (_cauterizationNeedsUpdate) { _cauterizationNeedsUpdate = false; - // Redisplay cauterized entities that are no longer children of the avatar. - auto cauterizedChild = _cauterizedChildrenOfHead.begin(); - if (cauterizedChild != _cauterizedChildrenOfHead.end()) { - auto children = getChildren(); - while (cauterizedChild != _cauterizedChildrenOfHead.end()) { - if (!children.contains(*cauterizedChild)) { - updateChildCauterization(*cauterizedChild, false); - cauterizedChild = _cauterizedChildrenOfHead.erase(cauterizedChild); - } else { - ++cauterizedChild; - } - } - } - + auto objectsToUncauterize = _cauterizedChildrenOfHead; + _cauterizedChildrenOfHead.clear(); // Update cauterization of entities that are children of the avatar. auto headBoneSet = _skeletonModel->getCauterizeBoneSet(); forEachChild([&](SpatiallyNestablePointer object) { @@ -825,15 +831,19 @@ void MyAvatar::simulate(float deltaTime, bool inView) { updateChildCauterization(descendant, !_prevShouldDrawHead); }); _cauterizedChildrenOfHead.insert(object); - } else if (_cauterizedChildrenOfHead.find(object) != _cauterizedChildrenOfHead.end()) { - // Redisplay cauterized children that are not longer children of the head. - updateChildCauterization(object, false); + objectsToUncauterize.erase(object); + } else if (objectsToUncauterize.find(object) == objectsToUncauterize.end()) { + objectsToUncauterize.insert(object); object->forEachDescendant([&](SpatiallyNestablePointer descendant) { - updateChildCauterization(descendant, false); + objectsToUncauterize.insert(descendant); }); - _cauterizedChildrenOfHead.erase(object); } }); + + // Redisplay cauterized entities that are no longer children of the avatar. + for (auto cauterizedChild = objectsToUncauterize.begin(); cauterizedChild != objectsToUncauterize.end(); cauterizedChild++) { + updateChildCauterization(*cauterizedChild, false); + } } { @@ -942,8 +952,6 @@ void MyAvatar::simulate(float deltaTime, bool inView) { } handleChangedAvatarEntityData(); - - updateFadingStatus(); } // As far as I know no HMD system supports a play area of a kilometer in radius. @@ -1258,6 +1266,7 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { void MyAvatar::saveData() { _dominantHandSetting.set(getDominantHand()); + _strafeEnabledSetting.set(getStrafeEnabled()); _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); _scaleSetting.set(_targetScale); @@ -1281,6 +1290,15 @@ void MyAvatar::saveData() { _useSnapTurnSetting.set(_useSnapTurn); _userHeightSetting.set(getUserHeight()); _flyingHMDSetting.set(getFlyingHMDPref()); + _movementReferenceSetting.set(getMovementReference()); + _driveGear1Setting.set(getDriveGear1()); + _driveGear2Setting.set(getDriveGear2()); + _driveGear3Setting.set(getDriveGear3()); + _driveGear4Setting.set(getDriveGear4()); + _driveGear5Setting.set(getDriveGear5()); + _analogWalkSpeedSetting.set(getAnalogWalkSpeed()); + _analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed()); + _controlSchemeIndexSetting.set(getControlSchemeIndex()); _userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); auto hmdInterface = DependencyManager::get(); @@ -1858,12 +1876,22 @@ void MyAvatar::loadData() { // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get()); + setMovementReference(firstRunVal.get() ? false : _movementReferenceSetting.get()); + setDriveGear1(firstRunVal.get() ? DEFAULT_GEAR_1 : _driveGear1Setting.get()); + setDriveGear2(firstRunVal.get() ? DEFAULT_GEAR_2 : _driveGear2Setting.get()); + setDriveGear3(firstRunVal.get() ? DEFAULT_GEAR_3 : _driveGear3Setting.get()); + setDriveGear4(firstRunVal.get() ? DEFAULT_GEAR_4 : _driveGear4Setting.get()); + setDriveGear5(firstRunVal.get() ? DEFAULT_GEAR_5 : _driveGear5Setting.get()); + setControlSchemeIndex(firstRunVal.get() ? LocomotionControlsMode::CONTROLS_DEFAULT : _controlSchemeIndexSetting.get()); + setAnalogWalkSpeed(firstRunVal.get() ? ANALOG_AVATAR_MAX_WALKING_SPEED : _analogWalkSpeedSetting.get()); + setAnalogPlusWalkSpeed(firstRunVal.get() ? ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED : _analogPlusWalkSpeedSetting.get()); setFlyingEnabled(getFlyingEnabled()); setDisplayName(_displayNameSetting.get()); setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); setSnapTurn(_useSnapTurnSetting.get()); setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setStrafeEnabled(_strafeEnabledSetting.get(DEFAULT_STRAFE_ENABLED)); setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower()); setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); @@ -2521,6 +2549,12 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act } } +glm::quat MyAvatar::getOffHandRotation() const { + auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; + auto pose = getControllerPoseInAvatarFrame(hand); + return pose.rotation; +} + void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; @@ -3287,21 +3321,131 @@ void MyAvatar::updateOrientation(float deltaTime) { } } -static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) { - // for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0 - float fwdScale = forwardSpeed * forwardSpeed; - float backScale = backwardSpeed * backwardSpeed; - float scaledX = velocityDirection.x * backwardSpeed; - float scaledSpeed = forwardSpeed; - if (velocityDirection.y < 0.0f) { - if (backScale > 0.0f) { - float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale))); - scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue)); +float MyAvatar::calculateGearedSpeed(const float driveKey) { + float absDriveKey = abs(driveKey); + float sign = (driveKey < 0.0f) ? -1.0f : 1.0f; + if (absDriveKey > getDriveGear5()) { + return sign * 1.0f; + } + else if (absDriveKey > getDriveGear4()) { + return sign * 0.8f; + } + else if (absDriveKey > getDriveGear3()) { + return sign * 0.6f; + } + else if (absDriveKey > getDriveGear2()) { + return sign * 0.4f; + } + else if (absDriveKey > getDriveGear1()) { + return sign * 0.2f; + } + else { + return sign * 0.0f; + } +} + +glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) { + float stickFullOn = 0.85f; + auto zSpeed = getDriveKey(TRANSLATE_Z); + auto xSpeed = getDriveKey(TRANSLATE_X); + glm::vec3 direction; + if (!useAdvancedMovementControls() && qApp->isHMDMode()) { + // Walking disabled in settings. + return Vectors::ZERO; + } else if (qApp->isHMDMode()) { + // HMD advanced movement controls. + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + // No acceleration curve for this one, constant speed. + if (zSpeed || xSpeed) { + direction = (zSpeed * forward) + (xSpeed * right); + // Normalize direction. + auto length = glm::length(direction); + if (length > EPSILON) { + direction /= length; + } + return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar; + } else { + return Vectors::ZERO; + } + case LocomotionControlsMode::CONTROLS_ANALOG: + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + if (zSpeed || xSpeed) { + glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; + glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; + direction = scaledForward + scaledRight; + return direction; + } else { + return Vectors::ZERO; + } + default: + qDebug() << "Invalid control scheme index."; + return Vectors::ZERO; } } else { - scaledSpeed = backwardSpeed; + // Desktop mode. + direction = (zSpeed * forward) + (xSpeed * right); + auto length = glm::length(direction); + if (length > EPSILON) { + direction /= length; + } + direction *= getWalkSpeed() * _walkSpeedScalar; + return direction; } - return scaledSpeed; +} + +glm::vec3 MyAvatar::calculateScaledDirection(){ + CharacterController::State state = _characterController.getState(); + + // compute action input + // Determine if we're head or controller relative... + glm::vec3 forward, right; + + if (qApp->isHMDMode()) { + auto handRotation = getOffHandRotation(); + glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); + glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f)); + glm::vec3 transform; + switch (getMovementReference()) { + case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE: + forward = (handRotation * controllerForward); + right = (handRotation * controllerRight); + break; + case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED: + forward = (handRotation * controllerForward); + transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y); + if (glm::length(transform) > EPSILON) { + forward = glm::normalize(transform); + } else { + forward = Vectors::ZERO; + } + right = (handRotation * controllerRight); + transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y); + if (glm::length(transform) > EPSILON) { + right = glm::normalize(transform); + } else { + right = Vectors::ZERO; + } + break; + case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE: + default: + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + } else { + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + + glm::vec3 direction = scaleMotorSpeed(forward, right); + + if (state == CharacterController::State::Hover || + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { + glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; + direction += up; + } + + return direction; } void MyAvatar::updateActionMotor(float deltaTime) { @@ -3321,25 +3465,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { CharacterController::State state = _characterController.getState(); - // compute action input - glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD; - glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT; - - glm::vec3 direction = forward + right; - if (state == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; - direction += up; - } + glm::vec3 direction = calculateScaledDirection(); _wasPushing = _isPushing; float directionLength = glm::length(direction); _isPushing = directionLength > EPSILON; - // normalize direction - if (_isPushing) { - direction /= directionLength; - } else { + if (!_isPushing) { direction = Vectors::ZERO; } @@ -3355,6 +3487,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { + direction /= directionLength; if (motorSpeed < maxBoostSpeed) { // an active action motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; @@ -3365,11 +3498,17 @@ void MyAvatar::updateActionMotor(float deltaTime) { } _actionMotorVelocity = motorSpeed * direction; } else { - // we're interacting with a floor --> simple horizontal speed and exponential decay - const glm::vec2 currentVel = { direction.x, direction.z }; - float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get()); - // _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0 - _actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction; + _actionMotorVelocity = direction; + } + + float previousBoomLength = _boomLength; + float boomChange = getDriveKey(ZOOM); + _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; + _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); + + // May need to change view if boom length has changed + if (previousBoomLength != _boomLength) { + qApp->changeViewAsNeeded(_boomLength); } } @@ -3742,7 +3881,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders QVariantMap extraInfo; EntityItemID entityID = entityTree->evalRayIntersection(startPointIn, directionIn, include, ignore, - PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE) | PickFilter::getBitMask(PickFilter::FlagBit::PRECISE)), + PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE) | PickFilter::getBitMask(PickFilter::FlagBit::PRECISE) + | PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), // exclude Local entities element, distance, face, normalOut, extraInfo, lockType, accurateResult); if (entityID.isNull()) { return false; @@ -3882,6 +4022,136 @@ void MyAvatar::setFlyingHMDPref(bool enabled) { _flyingPrefHMD = enabled; } +void MyAvatar::setMovementReference(int enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setMovementReference", Q_ARG(bool, enabled)); + return; + } + _movementReference = enabled; +} + +int MyAvatar::getMovementReference() { + return _movementReference; +} + +void MyAvatar::setControlSchemeIndex(int index){ + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setControlSchemeIndex", Q_ARG(int, index)); + return; + } + // Need to add checks for valid indices. + _controlSchemeIndex = index; +} + +int MyAvatar::getControlSchemeIndex() { + return _controlSchemeIndex; +} + +void MyAvatar::setDriveGear1(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear1", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear1 = (shiftPoint < _driveGear2) ? shiftPoint : _driveGear1; +} + +float MyAvatar::getDriveGear1() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_1; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear1; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear2(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear2", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear2 = (shiftPoint < _driveGear3 && shiftPoint >= _driveGear1) ? shiftPoint : _driveGear2; +} + +float MyAvatar::getDriveGear2() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_2; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear2; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear3(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear3", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear3 = (shiftPoint < _driveGear4 && shiftPoint >= _driveGear2) ? shiftPoint : _driveGear3; +} + +float MyAvatar::getDriveGear3() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_3; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear3; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear4(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear4", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear4 = (shiftPoint < _driveGear5 && shiftPoint >= _driveGear3) ? shiftPoint : _driveGear4; +} + +float MyAvatar::getDriveGear4() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_4; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear4; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear5(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear5", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear5 = (shiftPoint > _driveGear4) ? shiftPoint : _driveGear5; +} + +float MyAvatar::getDriveGear5() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_5; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear5; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + bool MyAvatar::getFlyingHMDPref() { return _flyingPrefHMD; } @@ -4490,11 +4760,37 @@ bool MyAvatar::getIsSitStandStateLocked() const { } float MyAvatar::getWalkSpeed() const { - return _walkSpeed.get() * _walkSpeedScalar; + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogWalkSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusWalkSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultWalkSpeed.get(); + } + } else { + return _defaultWalkSpeed.get(); + } + } float MyAvatar::getWalkBackwardSpeed() const { - return _walkSpeed.get() * _walkSpeedScalar; + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogWalkBackwardSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusWalkBackwardSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultWalkBackwardSpeed.get(); + } + } else { + return _defaultWalkBackwardSpeed.get(); + } + } bool MyAvatar::isReadyForPhysics() const { @@ -4502,7 +4798,7 @@ bool MyAvatar::isReadyForPhysics() const { } void MyAvatar::setSprintMode(bool sprint) { - _walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR; + _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } void MyAvatar::setIsInWalkingState(bool isWalking) { @@ -4565,19 +4861,103 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) { } void MyAvatar::setWalkSpeed(float value) { - _walkSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultWalkSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogWalkSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusWalkSpeed.set(value); + break; + default: + break; + } } void MyAvatar::setWalkBackwardSpeed(float value) { - _walkBackwardSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultWalkBackwardSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogWalkBackwardSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusWalkBackwardSpeed.set(value); + break; + default: + break; + } } void MyAvatar::setSprintSpeed(float value) { - _sprintSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultSprintSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogSprintSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusSprintSpeed.set(value); + break; + default: + break; + } } float MyAvatar::getSprintSpeed() const { - return _sprintSpeed.get(); + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogSprintSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusSprintSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultSprintSpeed.get(); + } + } else { + return _defaultSprintSpeed.get(); + } +} + +void MyAvatar::setAnalogWalkSpeed(float value) { + _analogWalkSpeed.set(value); + // Sprint speed for Analog should be double walk speed. + _analogSprintSpeed.set(value * 2.0f); +} + +float MyAvatar::getAnalogWalkSpeed() const { + return _analogWalkSpeed.get(); +} + +void MyAvatar::setAnalogSprintSpeed(float value) { + _analogSprintSpeed.set(value); +} + +float MyAvatar::getAnalogSprintSpeed() const { + return _analogSprintSpeed.get(); +} + +void MyAvatar::setAnalogPlusWalkSpeed(float value) { + _analogPlusWalkSpeed.set(value); + // Sprint speed for Analog Plus should be double walk speed. + _analogPlusSprintSpeed.set(value * 2.0f); +} + +float MyAvatar::getAnalogPlusWalkSpeed() const { + return _analogPlusWalkSpeed.get(); +} + +void MyAvatar::setAnalogPlusSprintSpeed(float value) { + _analogPlusSprintSpeed.set(value); +} + +float MyAvatar::getAnalogPlusSprintSpeed() const { + return _analogPlusSprintSpeed.get(); } void MyAvatar::setSitStandStateChange(bool stateChanged) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0859c20153..804e2687e7 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -39,6 +39,18 @@ class ModelItemID; class MyHead; class DetailedMotionState; +enum LocomotionControlsMode { + CONTROLS_DEFAULT = 0, + CONTROLS_ANALOG, + CONTROLS_ANALOG_PLUS +}; + +enum LocomotionRelativeMovementMode { + MOVEMENT_HMD_RELATIVE = 0, + MOVEMENT_HAND_RELATIVE, + MOVEMENT_HAND_RELATIVE_LEVELED +}; + enum eyeContactTarget { LEFT_EYE, RIGHT_EYE, @@ -371,6 +383,13 @@ class MyAvatar : public Avatar { using Clock = std::chrono::system_clock; using TimePoint = Clock::time_point; + const float DEFAULT_GEAR_1 = 0.2f; + const float DEFAULT_GEAR_2 = 0.4f; + const float DEFAULT_GEAR_3 = 0.8f; + const float DEFAULT_GEAR_4 = 0.9f; + const float DEFAULT_GEAR_5 = 1.0f; + + const bool DEFAULT_STRAFE_ENABLED = true; public: /**jsdoc @@ -729,7 +748,17 @@ public: */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } + /** + * @function MyAvatar.getControlScheme + * @returns {number} + */ + Q_INVOKABLE int getControlScheme() const { return _controlSchemeIndex; } + /** + * @function MyAvatar.setControlScheme + * @param {number} index + */ + Q_INVOKABLE void setControlScheme(int index) { _controlSchemeIndex = (index >= 0 && index <= 2) ? index : 0; } /**jsdoc * Sets the avatar's dominant hand. * @function MyAvatar.setDominantHand @@ -744,7 +773,16 @@ public: * @returns {string} "left" for the left hand, "right" for the right hand. */ Q_INVOKABLE QString getDominantHand() const; - + /**jsdoc + * @function MyAVatar.setStrafeEnabled + * @param {bool} enabled + */ + Q_INVOKABLE void setStrafeEnabled(bool enabled); + /**jsdoc + * @function MyAvatar.getStrafeEnabled + * @returns {bool} + */ + Q_INVOKABLE bool getStrafeEnabled() const; /**jsdoc * @function MyAvatar.setHmdAvatarAlignmentType * @param {string} type - "head" to align your head and your avatar's head, "eyes" to align your @@ -1235,6 +1273,7 @@ public: controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; + glm::quat getOffHandRotation() const; bool hasDriveInput() const; @@ -1317,6 +1356,106 @@ public: */ Q_INVOKABLE bool getFlyingHMDPref(); + /**jsdoc + * Set your preference for hand-relative movement. + * @function MyAvatar.setHandRelativeMovement + * @param {number} enabled - Set true if you want to enable hand-relative movement, otherwise set to false. + * + */ + Q_INVOKABLE void setMovementReference(int enabled); + + /**jsdoc + * Get your preference for hand-relative movement. + * @function MyAvatar.getHandRelativeMovement + * @returns {number} true if your preference is for user locomotion to be relative to the direction your + * controller is pointing, otherwise false. + */ + Q_INVOKABLE int getMovementReference(); + + /**jsdoc + * Set the first 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear1 + * @param {number} shiftPoint - Set the first shift point for analog movement acceleration step function, between [0.0, 1.0]. Must be less than or equal to Gear 2. + */ + Q_INVOKABLE void setDriveGear1(float shiftPoint); + + /**jsdoc + * Get the first 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear1 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear1(); + + /**jsdoc + * Set the second 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear2 + * @param {number} shiftPoint - Defines the second shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 1 and less than or equal to Gear 2. + */ + Q_INVOKABLE void setDriveGear2(float shiftPoint); + + /**jsdoc + * Get the second 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear2 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear2(); + + /**jsdoc + * Set the third 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear3 + * @param {number} shiftPoint - Defines the third shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 2 and less than or equal to Gear 4. + */ + Q_INVOKABLE void setDriveGear3(float shiftPoint); + + /**jsdoc + * Get the third 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear3 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear3(); + + /**jsdoc + * Set the fourth 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear4 + * @param {number} shiftPoint - Defines the fourth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than Gear 3 and less than Gear 5. + */ + Q_INVOKABLE void setDriveGear4(float shiftPoint); + + /**jsdoc + * Get the fourth 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear4 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear4(); + + /**jsdoc + * Set the fifth 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear5 + * @param {number} shiftPoint - Defines the fifth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 4. + */ + Q_INVOKABLE void setDriveGear5(float shiftPoint); + + /**jsdoc + * Get the fifth 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear5 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear5(); + + /**jsdoc + * Choose the control scheme. + * @function MyAvatar.setControlSchemeIndex + * @param {number} Choose the control scheme to be used. + */ + void setControlSchemeIndex(int index); + + /**jsdoc + * Check what control scheme is in use. + * @function MyAvatar.getControlSchemeIndex + * @returns {number} Returns the index associated with a given control scheme. + */ + int getControlSchemeIndex(); + /**jsdoc * Gets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on * permissible scale values imposed by the domain. @@ -1490,6 +1629,14 @@ public: float getWalkBackwardSpeed() const; void setSprintSpeed(float value); float getSprintSpeed() const; + void setAnalogWalkSpeed(float value); + float getAnalogWalkSpeed() const; + void setAnalogSprintSpeed(float value); + float getAnalogSprintSpeed() const; + void setAnalogPlusWalkSpeed(float value); + float getAnalogPlusWalkSpeed() const; + void setAnalogPlusSprintSpeed(float value); + float getAnalogPlusSprintSpeed() const; void setSitStandStateChange(bool stateChanged); float getSitStandStateChange() const; void updateSitStandState(float newHeightReading, float dt); @@ -2230,6 +2377,13 @@ private: float _boomLength { ZOOM_DEFAULT }; float _yawSpeed; // degrees/sec float _pitchSpeed; // degrees/sec + float _driveGear1 { DEFAULT_GEAR_1 }; + float _driveGear2 { DEFAULT_GEAR_2 }; + float _driveGear3 { DEFAULT_GEAR_3 }; + float _driveGear4 { DEFAULT_GEAR_4 }; + float _driveGear5 { DEFAULT_GEAR_5 }; + int _controlSchemeIndex { CONTROLS_DEFAULT }; + int _movementReference{ 0 }; glm::vec3 _thrust { 0.0f }; // impulse accumulator for outside sources @@ -2270,6 +2424,9 @@ private: // private methods void updateOrientation(float deltaTime); + glm::vec3 calculateScaledDirection(); + float calculateGearedSpeed(const float driveKey); + glm::vec3 scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right); void updateActionMotor(float deltaTime); void updatePosition(float deltaTime); void updateViewBoom(); @@ -2287,6 +2444,7 @@ private: bool _useSnapTurn { true }; ThreadSafeValueCache _dominantHand { DOMINANT_RIGHT_HAND }; ThreadSafeValueCache _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE }; + ThreadSafeValueCache _strafeEnabled{ DEFAULT_STRAFE_ENABLED }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec @@ -2438,9 +2596,16 @@ private: ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed - ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; - ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; - ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; + ThreadSafeValueCache _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _defaultWalkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _defaultSprintSpeed { DEFAULT_AVATAR_MAX_SPRINT_SPEED }; + ThreadSafeValueCache _analogWalkSpeed { ANALOG_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _analogWalkBackwardSpeed { ANALOG_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _analogSprintSpeed { ANALOG_AVATAR_MAX_SPRINT_SPEED }; + ThreadSafeValueCache _analogPlusWalkSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _analogPlusWalkBackwardSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _analogPlusSprintSpeed { ANALOG_PLUS_AVATAR_MAX_SPRINT_SPEED }; + float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; @@ -2460,6 +2625,7 @@ private: TimePoint _nextTraitsSendWindow; Setting::Handle _dominantHandSetting; + Setting::Handle _strafeEnabledSetting; Setting::Handle _hmdAvatarAlignmentTypeSetting; Setting::Handle _headPitchSetting; Setting::Handle _scaleSetting; @@ -2473,8 +2639,17 @@ private: Setting::Handle _useSnapTurnSetting; Setting::Handle _userHeightSetting; Setting::Handle _flyingHMDSetting; + Setting::Handle _movementReferenceSetting; Setting::Handle _avatarEntityCountSetting; Setting::Handle _allowTeleportingSetting { "allowTeleporting", true }; + Setting::Handle _driveGear1Setting; + Setting::Handle _driveGear2Setting; + Setting::Handle _driveGear3Setting; + Setting::Handle _driveGear4Setting; + Setting::Handle _driveGear5Setting; + Setting::Handle _analogWalkSpeedSetting; + Setting::Handle _analogPlusWalkSpeedSetting; + Setting::Handle _controlSchemeIndexSetting; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; Setting::Handle _userRecenterModelSetting; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index b100b33dc8..2e775a20c3 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -356,7 +356,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "grabs"); applyGrabChanges(); } - updateFadingStatus(); } void OtherAvatar::handleChangedAvatarEntityData() { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 2e18f34c8d..64528e617d 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "AccountManager.h" @@ -65,7 +66,7 @@ signals: void availableUpdatesResult(QJsonObject result); void updateItemResult(QJsonObject result); - void updateCertificateStatus(const QString& certID, uint certStatus); + void updateCertificateStatus(const EntityItemID& entityID, uint certStatus); public slots: void buySuccess(QNetworkReply* reply); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 3217b8a1f9..99b3e32e8b 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -19,6 +19,7 @@ #include +#include #include class QmlCommerce : public QObject, public Dependency { @@ -49,7 +50,7 @@ signals: void availableUpdatesResult(QJsonObject result); void updateItemResult(QJsonObject result); - void updateCertificateStatus(const QString& certID, uint certStatus); + void updateCertificateStatus(const EntityItemID& entityID, uint certStatus); void transferAssetToNodeResult(QJsonObject result); void transferAssetToUsernameResult(QJsonObject result); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 0e9ad7d79a..37f28960e5 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -816,18 +816,18 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; int status; - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int challengingNodeUUIDByteArraySize; - packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&idByteArraySize); packet->readPrimitive(&textByteArraySize); // returns a cast char*, size if (challengeOriginatedFromClient) { packet->readPrimitive(&challengingNodeUUIDByteArraySize); } // "encryptedText" is now a series of random bytes, a nonce - QByteArray certID = packet->read(certIDByteArraySize); + QByteArray id = packet->read(idByteArraySize); QByteArray text = packet->read(textByteArraySize); QByteArray challengingNodeUUID; if (challengeOriginatedFromClient) { @@ -853,32 +853,32 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack textByteArray = sig.toUtf8(); } textByteArraySize = textByteArray.size(); - int certIDSize = certID.size(); + int idSize = id.size(); // setup the packet if (challengeOriginatedFromClient) { auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), true); - textPacket->writePrimitive(certIDSize); + textPacket->writePrimitive(idSize); textPacket->writePrimitive(textByteArraySize); textPacket->writePrimitive(challengingNodeUUIDByteArraySize); - textPacket->write(certID); + textPacket->write(id); textPacket->write(textByteArray); textPacket->write(challengingNodeUUID); - qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID; + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id; nodeList->sendPacket(std::move(textPacket), *sendingNode); } else { - auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true); + auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true); - textPacket->writePrimitive(certIDSize); + textPacket->writePrimitive(idSize); textPacket->writePrimitive(textByteArraySize); - textPacket->write(certID); + textPacket->write(id); textPacket->write(textByteArray); - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID; + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id; nodeList->sendPacket(std::move(textPacket), *sendingNode); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 5af0a9371d..b2be010544 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -302,8 +302,11 @@ int main(int argc, const char* argv[]) { PROFILE_SYNC_BEGIN(startup, "app full ctor", ""); Application app(argcExtended, const_cast(argvExtended.data()), startupTime, runningMarkerExisted); PROFILE_SYNC_END(startup, "app full ctor", ""); - - + +#if defined(Q_OS_LINUX) + app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/hifi-logo.svg")); +#endif + QTimer exitTimer; if (traceDuration > 0.0f) { exitTimer.setSingleShot(true); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 4f2171d451..caae946116 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -88,44 +88,44 @@ void Audio::setMuted(bool isMuted) { void Audio::setMutedDesktop(bool isMuted) { bool changed = false; withWriteLock([&] { - if (_desktopMuted != isMuted) { + if (_mutedDesktop != isMuted) { changed = true; - _desktopMuted = isMuted; + _mutedDesktop = isMuted; auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); } }); if (changed) { emit mutedChanged(isMuted); - emit desktopMutedChanged(isMuted); + emit mutedDesktopChanged(isMuted); } } bool Audio::getMutedDesktop() const { return resultWithReadLock([&] { - return _desktopMuted; + return _mutedDesktop; }); } void Audio::setMutedHMD(bool isMuted) { bool changed = false; withWriteLock([&] { - if (_hmdMuted != isMuted) { + if (_mutedHMD != isMuted) { changed = true; - _hmdMuted = isMuted; + _mutedHMD = isMuted; auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); } }); if (changed) { emit mutedChanged(isMuted); - emit hmdMutedChanged(isMuted); + emit mutedHMDChanged(isMuted); } } bool Audio::getMutedHMD() const { return resultWithReadLock([&] { - return _hmdMuted; + return _mutedHMD; }); } @@ -217,17 +217,17 @@ void Audio::setPTTHMD(bool enabled) { } void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); + _mutedDesktopSetting.set(getMutedDesktop()); + _mutedHMDSetting.set(getMutedHMD()); _pttDesktopSetting.set(getPTTDesktop()); _pttHMDSetting.set(getPTTHMD()); } void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); + setMutedDesktop(_mutedDesktopSetting.get()); + setMutedHMD(_mutedHMDSetting.get()); + setPTTDesktop(_pttDesktopSetting.get()); + setPTTHMD(_pttHMDSetting.get()); auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false)); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index d6823ea452..00da566b30 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-assignment-client * * @property {boolean} muted - true if the audio input is muted, otherwise false. + * @property {boolean} mutedDesktop - true if the audio input is muted, otherwise false. * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just * above the noise floor. @@ -68,8 +69,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) - Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) - Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool mutedDesktop READ getMutedDesktop WRITE setMutedDesktop NOTIFY mutedDesktopChanged) + Q_PROPERTY(bool mutedHMD READ getMutedHMD WRITE setMutedHMD NOTIFY mutedHMDChanged) Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) @@ -287,19 +288,19 @@ signals: /**jsdoc * Triggered when desktop audio input is muted or unmuted. - * @function Audio.desktopMutedChanged + * @function Audio.mutedDesktopChanged * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. * @returns {Signal} */ - void desktopMutedChanged(bool isMuted); + void mutedDesktopChanged(bool isMuted); /**jsdoc * Triggered when HMD audio input is muted or unmuted. - * @function Audio.hmdMutedChanged + * @function Audio.mutedHMDChanged * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. * @returns {Signal} */ - void hmdMutedChanged(bool isMuted); + void mutedHMDChanged(bool isMuted); /** * Triggered when Push-to-Talk has been enabled or disabled. @@ -418,12 +419,12 @@ private: bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; - Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; - Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _mutedDesktopSetting{ QStringList { Audio::AUDIO, "mutedDesktop" }, true }; + Setting::Handle _mutedHMDSetting{ QStringList { Audio::AUDIO, "mutedHMD" }, true }; Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; - bool _desktopMuted{ true }; - bool _hmdMuted{ false }; + bool _mutedDesktop{ true }; + bool _mutedHMD{ false }; bool _pttDesktop{ false }; bool _pttHMD{ false }; bool _pushingToTalk{ false }; diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 0aa352de23..80604f354b 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "Application.h" #include "Menu.h" @@ -30,6 +31,10 @@ AvatarInputs* AvatarInputs::getInstance() { AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); + auto nodeList = DependencyManager::get(); + auto usersScriptingInterface = DependencyManager::get(); + connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged); + connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged); } #define AI_UPDATE(name, src) \ @@ -83,6 +88,10 @@ void AvatarInputs::setShowAudioTools(bool showAudioTools) { emit showAudioToolsChanged(_showAudioTools); } +bool AvatarInputs::getIgnoreRadiusEnabled() const { + return DependencyManager::get()->getIgnoreRadiusEnabled(); +} + void AvatarInputs::toggleCameraMute() { FaceTracker* faceTracker = qApp->getSelectedFaceTracker(); if (faceTracker) { diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 6569792807..f53adc1749 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -42,6 +42,8 @@ class AvatarInputs : public QObject { AI_PROPERTY(bool, isHMD, false) Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) + Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged) + //Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged) public: static AvatarInputs* getInstance(); @@ -55,7 +57,9 @@ public: AvatarInputs(QObject* parent = nullptr); void update(); - bool showAudioTools() const { return _showAudioTools; } + bool showAudioTools() const { return _showAudioTools; } + bool getIgnoreRadiusEnabled() const; + //bool getEnteredIgnoreRadius() const; public slots: @@ -93,6 +97,34 @@ signals: */ void showAudioToolsChanged(bool show); + /**jsdoc + * @function AvatarInputs.avatarEnteredIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarEnteredIgnoreRadius(QUuid avatarID); + + /**jsdoc + * @function AvatarInputs.avatarLeftIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarLeftIgnoreRadius(QUuid avatarID); + + /**jsdoc + * @function AvatarInputs.ignoreRadiusEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void ignoreRadiusEnabledChanged(bool enabled); + + /**jsdoc + * @function AvatarInputs.enteredIgnoreRadiusChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void enteredIgnoreRadiusChanged(); + protected: /**jsdoc @@ -106,6 +138,8 @@ protected: Q_INVOKABLE void toggleCameraMute(); private: + void onAvatarEnteredIgnoreRadius(); + void onAvatarLeftIgnoreRadius(); float _trailingAudioLoudness{ 0 }; bool _showAudioTools { false }; }; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index a3a875ac40..e34b82e0a1 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -156,10 +156,10 @@ void DialogsManager::hmdTools(bool showTools) { } _hmdToolsDialog->show(); _hmdToolsDialog->raise(); + qApp->getWindow()->activateWindow(); } else { hmdToolsClosed(); } - qApp->getWindow()->activateWindow(); } void DialogsManager::hmdToolsClosed() { @@ -207,4 +207,4 @@ void DialogsManager::showDomainConnectionDialog() { _domainConnectionDialog->show(); _domainConnectionDialog->raise(); -} \ No newline at end of file +} diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index dbd24573ee..75279ef889 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -266,6 +266,11 @@ void setupPreferences() { auto preference = new CheckPreference(VR_MOVEMENT, "Walking", getter, setter); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->bool { return myAvatar->getStrafeEnabled(); }; + auto setter = [myAvatar](bool value) { myAvatar->setStrafeEnabled(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Strafing", getter, setter)); + } { auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; @@ -273,6 +278,22 @@ void setupPreferences() { preference->setIndented(true); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { return myAvatar->getMovementReference(); }; + auto setter = [myAvatar](int value) { myAvatar->setMovementReference(value); }; + //auto preference = new CheckPreference(VR_MOVEMENT, "Hand-Relative Movement", getter, setter); + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Movement Direction", getter, setter); + QStringList items; + items << "HMD-Relative" << "Hand-Relative" << "Hand-Relative (Leveled)"; + preference->setHeading("Movement Direction"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); }; + preferences->addPreference(new PrimaryHandPreference(VR_MOVEMENT, "Dominant Hand", getter, setter)); + } { auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; @@ -283,6 +304,26 @@ void setupPreferences() { preference->setItems(items); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { return myAvatar->getControlScheme(); }; + auto setter = [myAvatar](int index) { myAvatar->setControlScheme(index); }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Control Scheme", getter, setter); + QStringList items; + items << "Default" << "Analog" << "Analog++"; + preference->setHeading("Control Scheme Selection"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [myAvatar]()->float { return myAvatar->getAnalogPlusWalkSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setAnalogPlusWalkSpeed(value); }; + auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Analog++ Walk Speed", getter, setter); + preference->setMin(6.0f); + preference->setMax(30.0f); + preference->setStep(1); + preference->setDecimals(2); + preferences->addPreference(preference); + } { auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); }; auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); }; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index e5cec70f64..1c8a9019ea 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID setLastInspectedEntity(entityID); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags); auto nodeList = DependencyManager::get(); @@ -328,31 +328,31 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } else { QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); + QByteArray id = entityID.toByteArray(); + QByteArray text = DependencyManager::get()->getTree()->computeNonce(entityID, ownerKey); QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - int certIDByteArraySize = certID.length(); + int idByteArraySize = id.length(); int textByteArraySize = text.length(); int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + idByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(textByteArraySize); challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(id); challengeOwnershipPacket->write(text); challengeOwnershipPacket->write(nodeToChallengeByteArray); nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityID)); return; } else { - startChallengeOwnershipTimer(); + startChallengeOwnershipTimer(entityID); } } } else { @@ -370,14 +370,14 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID // so they always pass Ownership Verification. It's necessary to emit this signal // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } else { auto ledger = DependencyManager::get(); _challengeOwnershipTimeoutTimer.stop(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); - qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(entityID); + qCDebug(context_overlay) << "Entity" << entityID << "failed static certificate verification!"; } } @@ -395,14 +395,14 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { } } -void ContextOverlayInterface::startChallengeOwnershipTimer() { +void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) { auto ledger = DependencyManager::get(); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { - qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + qCDebug(entities) << "Ownership challenge timed out for" << entityItemID; + emit ledger->updateCertificateStatus(entityItemID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + emit DependencyManager::get()->ownershipVerificationFailed(entityItemID); }); _challengeOwnershipTimeoutTimer.start(5000); @@ -413,23 +413,22 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer _challengeOwnershipTimeoutTimer.stop(); - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; - packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&idByteArraySize); packet->readPrimitive(&textByteArraySize); - QString certID(packet->read(certIDByteArraySize)); + EntityItemID id(packet->read(idByteArraySize)); QString text(packet->read(textByteArraySize)); - EntityItemID id; - bool verificationSuccess = DependencyManager::get()->getTree()->verifyNonce(certID, text, id); + bool verificationSuccess = DependencyManager::get()->getTree()->verifyNonce(id, text); if (verificationSuccess) { - emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); - emit DependencyManager::get()->ownershipVerificationSuccess(_lastInspectedEntity); + emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + emit DependencyManager::get()->ownershipVerificationSuccess(id); } else { - emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(id); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b1b62aa0c4..e688d1c115 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -46,7 +46,7 @@ public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } - void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } + void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } @@ -83,7 +83,6 @@ private: EntityItemID _mouseDownEntity; quint64 _mouseDownEntityTimestamp; EntityItemID _currentEntityWithContextOverlay; - EntityItemID _lastInspectedEntity; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; @@ -94,7 +93,7 @@ private: void deletingEntity(const EntityItemID& entityItemID); - Q_INVOKABLE void startChallengeOwnershipTimer(); + Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); QTimer _challengeOwnershipTimeoutTimer; }; diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 41742c7485..e8bfa75ecb 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -45,18 +45,6 @@ private: Q_DECLARE_METATYPE(AnimationPointer) -/**jsdoc - * @class AnimationObject - * - * @hifi-interface - * @hifi-client-entity - * @hifi-avatar - * @hifi-server-entity - * @hifi-assignment-client - * - * @property {string[]} jointNames - * @property {FBXAnimationFrame[]} frames - */ /// An animation loaded from the network. class Animation : public Resource { Q_OBJECT @@ -72,16 +60,8 @@ public: virtual bool isLoaded() const override; - /**jsdoc - * @function AnimationObject.getJointNames - * @returns {string[]} - */ Q_INVOKABLE QStringList getJointNames() const; - /**jsdoc - * @function AnimationObject.getFrames - * @returns {FBXAnimationFrame[]} - */ Q_INVOKABLE QVector getFrames() const; const QVector& getFramesReference() const; diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.h b/libraries/animation/src/AnimationCacheScriptingInterface.h index 06db5ef352..0ceb302913 100644 --- a/libraries/animation/src/AnimationCacheScriptingInterface.h +++ b/libraries/animation/src/AnimationCacheScriptingInterface.h @@ -25,7 +25,8 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * API to manage animation cache resources. + * The AnimationCache API manages animation cache resources. + * * @namespace AnimationCache * * @hifi-interface @@ -48,10 +49,10 @@ public: AnimationCacheScriptingInterface(); /**jsdoc - * Returns animation resource for particular animation. + * Gets information about an animation resource. * @function AnimationCache.getAnimation - * @param {string} url - URL to load. - * @returns {AnimationObject} animation + * @param {string} url - The URL of the animation. + * @returns {AnimationObject} An animation object. */ Q_INVOKABLE AnimationPointer getAnimation(const QString& url); }; diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index fc3a351832..303d0e0d9e 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -19,6 +19,20 @@ class QScriptEngine; +/**jsdoc + * Information about an animation resource, created by {@link AnimationCache.getAnimation}. + * + * @class AnimationObject + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {string[]} jointNames - The names of the joints that are animated. Read-only. + * @property {AnimationFrameObject[]} frames - The frames in the animation. Read-only. + */ /// Scriptable wrapper for animation pointers. class AnimationObject : public QObject, protected QScriptable { Q_OBJECT @@ -27,11 +41,34 @@ class AnimationObject : public QObject, protected QScriptable { public: + /**jsdoc + * Gets the names of the joints that are animated. + * @function AnimationObject.getJointNames + * @returns {string[]} The names of the joints that are animated. + */ Q_INVOKABLE QStringList getJointNames() const; + /**jsdoc + * Gets the frames in the animation. + * @function AnimationObject.getFrames + * @returns {AnimationFrameObject[]} The frames in the animation. + */ Q_INVOKABLE QVector getFrames() const; }; +/**jsdoc + * Joint rotations in one frame of an animation. + * + * @class AnimationFrameObject + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {Quat[]} rotations - Joint rotations. Read-only. + */ /// Scriptable wrapper for animation frames. class AnimationFrameObject : public QObject, protected QScriptable { Q_OBJECT @@ -39,6 +76,11 @@ class AnimationFrameObject : public QObject, protected QScriptable { public: + /**jsdoc + * Gets the joint rotations in the animation frame. + * @function AnimationFrameObject.getRotations + * @returns {Quat[]} The joint rotations in the animation frame. + */ Q_INVOKABLE QVector getRotations() const; }; diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 1f9e72bf28..41db9d5606 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -499,12 +499,12 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, bool toFloatSuccess; QStringRef(&name, (int)(name.size() - j), 1).toString().toFloat(&toFloatSuccess); if (!toFloatSuccess && (name.size() - j) > (int)simPrefix.size()) { - group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1)).toString(); + group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1) - (int)simPrefix.size()).toString(); break; } } if (group.isEmpty()) { - group = QStringRef(&name, (int)simPrefix.size(), name.size() - 1).toString(); + group = QStringRef(&name, (int)simPrefix.size(), name.size() - (int)simPrefix.size()).toString(); } qCDebug(animation) << "Sim joint added to flow: " << name; } else { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c537fea646..4d3311b065 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1397,7 +1397,6 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { // spatialize into mixBuffer injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX, qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); - } else if (options.stereo) { if (options.positionSet) { @@ -1409,11 +1408,8 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } // direct mix into mixBuffer - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { - mixBuffer[2*i+0] += convertToFloat(_localScratchBuffer[2*i+0]) * gain; - mixBuffer[2*i+1] += convertToFloat(_localScratchBuffer[2*i+1]) * gain; - } - + injector->getLocalHRTF().mixStereo(_localScratchBuffer, mixBuffer, gain, + AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); } else { // injector is mono if (options.positionSet) { @@ -1431,11 +1427,8 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else { // direct mix into mixBuffer - for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) { - float sample = convertToFloat(_localScratchBuffer[i]) * gain; - mixBuffer[2*i+0] += sample; - mixBuffer[2*i+1] += sample; - } + injector->getLocalHRTF().mixMono(_localScratchBuffer, mixBuffer, gain, + AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); } } diff --git a/libraries/audio/src/AudioFOA.cpp b/libraries/audio/src/AudioFOA.cpp index 30d29b72b7..0dd61fbd02 100644 --- a/libraries/audio/src/AudioFOA.cpp +++ b/libraries/audio/src/AudioFOA.cpp @@ -882,14 +882,16 @@ static void convertInput_ref(int16_t* src, float *dst[4], float gain, int numFra #endif -// in-place rotation of the soundfield -// crossfade between old and new rotation, to prevent artifacts -static void rotate_3x3_ref(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) { +// in-place rotation and scaling of the soundfield +// crossfade between old and new matrix, to prevent artifacts +static void rotate_4x4_ref(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) { - const float md[3][3] = { - { m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2] }, - { m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2] }, - { m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2] }, + // matrix difference + const float md[4][4] = { + { m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2], m0[0][3] - m1[0][3] }, + { m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2], m0[1][3] - m1[1][3] }, + { m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2], m0[2][3] - m1[2][3] }, + { m0[3][0] - m1[3][0], m0[3][1] - m1[3][1], m0[3][2] - m1[3][2], m0[3][3] - m1[3][3] }, }; for (int i = 0; i < numFrames; i++) { @@ -898,22 +900,27 @@ static void rotate_3x3_ref(float* buf[4], const float m0[3][3], const float m1[3 // interpolate the matrix float m00 = m1[0][0] + frac * md[0][0]; - float m10 = m1[1][0] + frac * md[1][0]; - float m20 = m1[2][0] + frac * md[2][0]; - float m01 = m1[0][1] + frac * md[0][1]; float m11 = m1[1][1] + frac * md[1][1]; float m21 = m1[2][1] + frac * md[2][1]; + float m31 = m1[3][1] + frac * md[3][1]; - float m02 = m1[0][2] + frac * md[0][2]; float m12 = m1[1][2] + frac * md[1][2]; float m22 = m1[2][2] + frac * md[2][2]; + float m32 = m1[3][2] + frac * md[3][2]; + + float m13 = m1[1][3] + frac * md[1][3]; + float m23 = m1[2][3] + frac * md[2][3]; + float m33 = m1[3][3] + frac * md[3][3]; // matrix multiply - float x = m00 * buf[1][i] + m01 * buf[2][i] + m02 * buf[3][i]; - float y = m10 * buf[1][i] + m11 * buf[2][i] + m12 * buf[3][i]; - float z = m20 * buf[1][i] + m21 * buf[2][i] + m22 * buf[3][i]; + float w = m00 * buf[0][i]; + float x = m11 * buf[1][i] + m12 * buf[2][i] + m13 * buf[3][i]; + float y = m21 * buf[1][i] + m22 * buf[2][i] + m23 * buf[3][i]; + float z = m31 * buf[1][i] + m32 * buf[2][i] + m33 * buf[3][i]; + + buf[0][i] = w; buf[1][i] = x; buf[2][i] = y; buf[3][i] = z; @@ -932,7 +939,7 @@ void rfft512_AVX2(float buf[512]); void rifft512_AVX2(float buf[512]); void rfft512_cmadd_1X2_AVX2(const float src[512], const float coef0[512], const float coef1[512], float dst0[512], float dst1[512]); void convertInput_AVX2(int16_t* src, float *dst[4], float gain, int numFrames); -void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames); +void rotate_4x4_AVX2(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames); static void rfft512(float buf[512]) { static auto f = cpuSupportsAVX2() ? rfft512_AVX2 : rfft512_ref; @@ -954,8 +961,8 @@ static void convertInput(int16_t* src, float *dst[4], float gain, int numFrames) (*f)(src, dst, gain, numFrames); // dispatch } -static void rotate_3x3(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) { - static auto f = cpuSupportsAVX2() ? rotate_3x3_AVX2 : rotate_3x3_ref; +static void rotate_4x4(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) { + static auto f = cpuSupportsAVX2() ? rotate_4x4_AVX2 : rotate_4x4_ref; (*f)(buf, m0, m1, win, numFrames); // dispatch } @@ -965,7 +972,7 @@ static auto& rfft512 = rfft512_ref; static auto& rifft512 = rifft512_ref; static auto& rfft512_cmadd_1X2 = rfft512_cmadd_1X2_ref; static auto& convertInput = convertInput_ref; -static auto& rotate_3x3 = rotate_3x3_ref; +static auto& rotate_4x4 = rotate_4x4_ref; #endif @@ -1007,8 +1014,8 @@ ALIGN32 static const float crossfadeTable[FOA_BLOCK] = { 0.0020975362f, 0.0015413331f, 0.0010705384f, 0.0006852326f, 0.0003854819f, 0.0001713375f, 0.0000428362f, 0.0000000000f, }; -// convert quaternion to a column-major 3x3 rotation matrix -static void quatToMatrix_3x3(float w, float x, float y, float z, float m[3][3]) { +// convert quaternion to a column-major 4x4 rotation matrix +static void quatToMatrix_4x4(float w, float x, float y, float z, float m[4][4]) { float xx = x * (x + x); float xy = x * (y + y); @@ -1022,17 +1029,33 @@ static void quatToMatrix_3x3(float w, float x, float y, float z, float m[3][3]) float wy = w * (y + y); float wz = w * (z + z); - m[0][0] = 1.0f - (yy + zz); - m[0][1] = xy - wz; - m[0][2] = xz + wy; + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; - m[1][0] = xy + wz; - m[1][1] = 1.0f - (xx + zz); - m[1][2] = yz - wx; + m[1][0] = 0.0f; + m[1][1] = 1.0f - (yy + zz); + m[1][2] = xy - wz; + m[1][3] = xz + wy; - m[2][0] = xz - wy; - m[2][1] = yz + wx; - m[2][2] = 1.0f - (xx + yy); + m[2][0] = 0.0f; + m[2][1] = xy + wz; + m[2][2] = 1.0f - (xx + zz); + m[2][3] = yz - wx; + + m[3][0] = 0.0f; + m[3][1] = xz - wy; + m[3][2] = yz + wx; + m[3][3] = 1.0f - (xx + yy); +} + +static void scaleMatrix_4x4(float scale, float m[4][4]) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + m[i][j] *= scale; + } + } } // Ambisonic to binaural render @@ -1047,18 +1070,26 @@ void AudioFOA::render(int16_t* input, float* output, int index, float qw, float ALIGN32 float inBuffer[4][FOA_BLOCK]; // deinterleaved input buffers float* in[4] = { inBuffer[0], inBuffer[1], inBuffer[2], inBuffer[3] }; - float rotation[3][3]; + float rotation[4][4]; // convert input to deinterleaved float - convertInput(input, in, FOA_GAIN * gain, FOA_BLOCK); + convertInput(input, in, FOA_GAIN, FOA_BLOCK); - // convert quaternion to 3x3 rotation - quatToMatrix_3x3(qw, qx, qy, qz, rotation); + // convert quaternion to 4x4 rotation + quatToMatrix_4x4(qw, qx, qy, qz, rotation); - // rotate the soundfield - rotate_3x3(in, _rotationState, rotation, crossfadeTable, FOA_BLOCK); + // apply gain as uniform scale + scaleMatrix_4x4(gain, rotation); - // rotation history update + // disable interpolation from reset state + if (_resetState) { + memcpy(_rotationState, rotation, sizeof(_rotationState)); + } + + // rotate and scale the soundfield + rotate_4x4(in, _rotationState, rotation, crossfadeTable, FOA_BLOCK); + + // new parameters become old memcpy(_rotationState, rotation, sizeof(_rotationState)); // @@ -1093,4 +1124,6 @@ void AudioFOA::render(int16_t* input, float* output, int index, float qw, float output[2*i+0] += accBuffer[0][i + FOA_OVERLAP]; output[2*i+1] += accBuffer[1][i + FOA_OVERLAP]; } + + _resetState = false; } diff --git a/libraries/audio/src/AudioFOA.h b/libraries/audio/src/AudioFOA.h index 9eccc35bce..e8cacc22ab 100644 --- a/libraries/audio/src/AudioFOA.h +++ b/libraries/audio/src/AudioFOA.h @@ -28,12 +28,7 @@ static_assert((FOA_BLOCK + FOA_OVERLAP) == FOA_NFFT, "FFT convolution requires L class AudioFOA { public: - AudioFOA() { - // identity matrix - _rotationState[0][0] = 1.0f; - _rotationState[1][1] = 1.0f; - _rotationState[2][2] = 1.0f; - }; + AudioFOA() {}; // // input: interleaved First-Order Ambisonic source @@ -55,8 +50,10 @@ private: // input history, for overlap-save float _fftState[4][FOA_OVERLAP] = {}; - // orientation history - float _rotationState[3][3] = {}; + // orientation and gain history + float _rotationState[4][4] = {}; + + bool _resetState = true; }; #endif // AudioFOA_h diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 9de6440d67..e5e32781b0 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -750,6 +750,43 @@ static void interpolate(const float* src0, const float* src1, float* dst, float #endif +// apply gain crossfade with accumulation (interleaved) +static void gainfade_1x2(int16_t* src, float* dst, const float* win, float gain0, float gain1, int numFrames) { + + gain0 *= (1/32768.0f); // int16_t to float + gain1 *= (1/32768.0f); + + for (int i = 0; i < numFrames; i++) { + + float frac = win[i]; + float gain = gain1 + frac * (gain0 - gain1); + + float x0 = (float)src[i] * gain; + + dst[2*i+0] += x0; + dst[2*i+1] += x0; + } +} + +// apply gain crossfade with accumulation (interleaved) +static void gainfade_2x2(int16_t* src, float* dst, const float* win, float gain0, float gain1, int numFrames) { + + gain0 *= (1/32768.0f); // int16_t to float + gain1 *= (1/32768.0f); + + for (int i = 0; i < numFrames; i++) { + + float frac = win[i]; + float gain = gain1 + frac * (gain0 - gain1); + + float x0 = (float)src[2*i+0] * gain; + float x1 = (float)src[2*i+1] * gain; + + dst[2*i+0] += x0; + dst[2*i+1] += x1; + } +} + // design a 2nd order Thiran allpass static void ThiranBiquad(float f, float& b0, float& b1, float& b2, float& a1, float& a2) { @@ -1104,6 +1141,13 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, // apply global and local gain adjustment gain *= _gainAdjust; + // disable interpolation from reset state + if (_resetState) { + _azimuthState = azimuth; + _distanceState = distance; + _gainState = gain; + } + // to avoid polluting the cache, old filters are recomputed instead of stored setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, L0); @@ -1175,3 +1219,45 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth, _resetState = false; } + +void AudioHRTF::mixMono(int16_t* input, float* output, float gain, int numFrames) { + + assert(numFrames == HRTF_BLOCK); + + // apply global and local gain adjustment + gain *= _gainAdjust; + + // disable interpolation from reset state + if (_resetState) { + _gainState = gain; + } + + // crossfade gain and accumulate + gainfade_1x2(input, output, crossfadeTable, _gainState, gain, HRTF_BLOCK); + + // new parameters become old + _gainState = gain; + + _resetState = false; +} + +void AudioHRTF::mixStereo(int16_t* input, float* output, float gain, int numFrames) { + + assert(numFrames == HRTF_BLOCK); + + // apply global and local gain adjustment + gain *= _gainAdjust; + + // disable interpolation from reset state + if (_resetState) { + _gainState = gain; + } + + // crossfade gain and accumulate + gainfade_2x2(input, output, crossfadeTable, _gainState, gain, HRTF_BLOCK); + + // new parameters become old + _gainState = gain; + + _resetState = false; +} diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index 7d23f4825a..436d6318a5 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -50,6 +50,12 @@ public: // void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames); + // + // Non-spatialized direct mix (accumulates into existing output) + // + void mixMono(int16_t* input, float* output, float gain, int numFrames); + void mixStereo(int16_t* input, float* output, float gain, int numFrames); + // // Fast path when input is known to be silent and state as been flushed // diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 4911917bf0..c09dba6190 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -103,6 +103,8 @@ void AudioInjector::finishLocalInjection() { void AudioInjector::finish() { withWriteLock([&] { + _state |= AudioInjectorState::LocalInjectionFinished; + _state |= AudioInjectorState::NetworkInjectionFinished; _state |= AudioInjectorState::Finished; }); emit finished(); @@ -252,7 +254,7 @@ int64_t AudioInjector::injectNextFrame() { writeStringToStream(noCodecForInjectors, audioPacketStream); // pack stream identifier (a generated UUID) - audioPacketStream << QUuid::createUuid(); + audioPacketStream << _streamID; // pack the stereo/mono type of the stream audioPacketStream << options.stereo; @@ -402,4 +404,17 @@ int64_t AudioInjector::injectNextFrame() { int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS; return std::max(INT64_C(0), playNextFrameAt - currentTime); -} \ No newline at end of file +} + + +void AudioInjector::sendStopInjectorPacket() { + auto nodeList = DependencyManager::get(); + if (auto audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer)) { + // Build packet + auto stopInjectorPacket = NLPacket::create(PacketType::StopInjector); + stopInjectorPacket->write(_streamID.toRfc4122()); + + // Send packet + nodeList->sendUnreliablePacket(*stopInjectorPacket, *audioMixer); + } +} diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 555af84025..7c5e167dc5 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -100,6 +100,7 @@ private: int64_t injectNextFrame(); bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)); bool injectLocally(); + void sendStopInjectorPacket(); static AbstractAudioInterface* _localAudioInterface; @@ -120,6 +121,9 @@ private: // when the injector is local, we need this AudioHRTF _localHRTF; AudioFOA _localFOA; + + QUuid _streamID { QUuid::createUuid() }; + friend class AudioInjectorManager; }; diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index e5ffc77798..04e5666106 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -105,6 +105,8 @@ void AudioInjectorManager::run() { if (nextCallDelta >= 0 && !injector->isFinished()) { // enqueue the injector with the correct timing in our holding queue heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector); + } else { + injector->sendStopInjectorPacket(); } } @@ -354,4 +356,4 @@ void AudioInjectorManager::stop(const AudioInjectorPointer& injector) { size_t AudioInjectorManager::getNumInjectors() { Lock lock(_injectorsMutex); return _injectors.size(); -} \ No newline at end of file +} diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index f5f47add32..e5ba322599 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -124,9 +124,9 @@ typedef QSharedPointer SharedSoundPointer; * An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}. *

Supported formats:

*
    - *
  • WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2(stereo), or 4 (ambisonic) channels.
  • + *
  • WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2 (stereo), or 4 (ambisonic) channels.
  • *
  • MP3: Mono or stereo, at any sample rate.
  • - *
  • RAW: 48khz 16-bit mono or stereo. Filename must include ".stereo" to be interpreted as stereo.
  • + *
  • RAW: 48khz 16-bit mono or stereo. File name must include ".stereo" to be interpreted as stereo.
  • *
* * @class SoundObject @@ -138,8 +138,8 @@ typedef QSharedPointer SharedSoundPointer; * @hifi-assignment-client * * @property {boolean} downloaded - true if the sound has been downloaded and is ready to be played, otherwise - * false. - * @property {number} duration - The duration of the sound, in seconds. + * false. Read-only. + * @property {number} duration - The duration of the sound, in seconds. Read-only. */ class SoundScriptingInterface : public QObject { Q_OBJECT diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index 9caa7a8066..ea767e00b4 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -25,7 +25,8 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * API to manage sound cache resources. + * The SoundCache API manages sound cache resources. + * * @namespace SoundCache * * @hifi-interface diff --git a/libraries/audio/src/avx2/AudioFOA_avx2.cpp b/libraries/audio/src/avx2/AudioFOA_avx2.cpp index 70f9b0e5f6..15d37fcc3a 100644 --- a/libraries/audio/src/avx2/AudioFOA_avx2.cpp +++ b/libraries/audio/src/avx2/AudioFOA_avx2.cpp @@ -1289,14 +1289,16 @@ void convertInput_AVX2(int16_t* src, float *dst[4], float gain, int numFrames) { #endif -// in-place rotation of the soundfield -// crossfade between old and new rotation, to prevent artifacts -void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) { +// in-place rotation and scaling of the soundfield +// crossfade between old and new matrix, to prevent artifacts +void rotate_4x4_AVX2(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) { - const float md[3][3] = { - { m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2] }, - { m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2] }, - { m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2] }, + // matrix difference + const float md[4][4] = { + { m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2], m0[0][3] - m1[0][3] }, + { m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2], m0[1][3] - m1[1][3] }, + { m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2], m0[2][3] - m1[2][3] }, + { m0[3][0] - m1[3][0], m0[3][1] - m1[3][1], m0[3][2] - m1[3][2], m0[3][3] - m1[3][3] }, }; assert(numFrames % 8 == 0); @@ -1307,30 +1309,35 @@ void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3], // interpolate the matrix __m256 m00 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][0]), _mm256_broadcast_ss(&m1[0][0])); - __m256 m10 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][0]), _mm256_broadcast_ss(&m1[1][0])); - __m256 m20 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][0]), _mm256_broadcast_ss(&m1[2][0])); - __m256 m01 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][1]), _mm256_broadcast_ss(&m1[0][1])); __m256 m11 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][1]), _mm256_broadcast_ss(&m1[1][1])); __m256 m21 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][1]), _mm256_broadcast_ss(&m1[2][1])); + __m256 m31 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][1]), _mm256_broadcast_ss(&m1[3][1])); - __m256 m02 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][2]), _mm256_broadcast_ss(&m1[0][2])); __m256 m12 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][2]), _mm256_broadcast_ss(&m1[1][2])); __m256 m22 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][2]), _mm256_broadcast_ss(&m1[2][2])); + __m256 m32 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][2]), _mm256_broadcast_ss(&m1[3][2])); + + __m256 m13 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][3]), _mm256_broadcast_ss(&m1[1][3])); + __m256 m23 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][3]), _mm256_broadcast_ss(&m1[2][3])); + __m256 m33 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][3]), _mm256_broadcast_ss(&m1[3][3])); // matrix multiply - __m256 x = _mm256_mul_ps(m00, _mm256_loadu_ps(&buf[1][i])); - __m256 y = _mm256_mul_ps(m10, _mm256_loadu_ps(&buf[1][i])); - __m256 z = _mm256_mul_ps(m20, _mm256_loadu_ps(&buf[1][i])); + __m256 w = _mm256_mul_ps(m00, _mm256_loadu_ps(&buf[0][i])); - x = _mm256_fmadd_ps(m01, _mm256_loadu_ps(&buf[2][i]), x); - y = _mm256_fmadd_ps(m11, _mm256_loadu_ps(&buf[2][i]), y); - z = _mm256_fmadd_ps(m21, _mm256_loadu_ps(&buf[2][i]), z); + __m256 x = _mm256_mul_ps(m11, _mm256_loadu_ps(&buf[1][i])); + __m256 y = _mm256_mul_ps(m21, _mm256_loadu_ps(&buf[1][i])); + __m256 z = _mm256_mul_ps(m31, _mm256_loadu_ps(&buf[1][i])); - x = _mm256_fmadd_ps(m02, _mm256_loadu_ps(&buf[3][i]), x); - y = _mm256_fmadd_ps(m12, _mm256_loadu_ps(&buf[3][i]), y); - z = _mm256_fmadd_ps(m22, _mm256_loadu_ps(&buf[3][i]), z); + x = _mm256_fmadd_ps(m12, _mm256_loadu_ps(&buf[2][i]), x); + y = _mm256_fmadd_ps(m22, _mm256_loadu_ps(&buf[2][i]), y); + z = _mm256_fmadd_ps(m32, _mm256_loadu_ps(&buf[2][i]), z); + x = _mm256_fmadd_ps(m13, _mm256_loadu_ps(&buf[3][i]), x); + y = _mm256_fmadd_ps(m23, _mm256_loadu_ps(&buf[3][i]), y); + z = _mm256_fmadd_ps(m33, _mm256_loadu_ps(&buf[3][i]), z); + + _mm256_storeu_ps(&buf[0][i], w); _mm256_storeu_ps(&buf[1][i], x); _mm256_storeu_ps(&buf[2][i], y); _mm256_storeu_ps(&buf[3][i], z); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d083dfb41b..b86c56bb0c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -672,9 +672,8 @@ void Avatar::fadeIn(render::ScenePointer scene) { scene->enqueueTransaction(transaction); } -void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) { +void Avatar::fadeOut(render::Transaction& transaction, KillAvatarReason reason) { render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN; - render::Transaction transaction; if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) { transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER; @@ -682,7 +681,6 @@ void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) { transitionType = render::Transition::BUBBLE_ISECT_OWNER; } fade(transaction, transitionType); - scene->enqueueTransaction(transaction); } void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) { @@ -692,19 +690,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ transaction.addTransitionToItem(itemId, type, _renderItemID); } } - _isFading = true; -} - -void Avatar::updateFadingStatus() { - if (_isFading) { - render::Transaction transaction; - transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) { - if (!transition || transition->isFinished) { - _isFading = false; - } - }); - AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); - } } void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { @@ -1560,8 +1545,8 @@ void Avatar::rigReset() { void Avatar::computeMultiSphereShapes() { const Rig& rig = getSkeletonModel()->getRig(); - glm::vec3 scale = extractScale(rig.getGeometryOffsetPose()); const HFMModel& geometry = getSkeletonModel()->getHFMModel(); + glm::vec3 geometryScale = extractScale(rig.getGeometryOffsetPose()); int jointCount = rig.getJointStateCount(); _multiSphereShapes.clear(); _multiSphereShapes.reserve(jointCount); @@ -1570,9 +1555,10 @@ void Avatar::computeMultiSphereShapes() { std::vector btPoints; int lineCount = (int)shapeInfo.debugLines.size(); btPoints.reserve(lineCount); + glm::vec3 jointScale = rig.getJointPose(i).scale() / extractScale(rig.getGeometryToRigTransform()); for (int j = 0; j < lineCount; j++) { const glm::vec3 &point = shapeInfo.debugLines[j]; - auto rigPoint = scale * point; + auto rigPoint = jointScale * geometryScale * point; btVector3 btPoint = glmToBullet(rigPoint); btPoints.push_back(btPoint); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 7c4cde1f4d..3d14418157 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -520,9 +520,7 @@ public: bool isMoving() const { return _moving; } void fadeIn(render::ScenePointer scene); - void fadeOut(render::ScenePointer scene, KillAvatarReason reason); - bool isFading() const { return _isFading; } - void updateFadingStatus(); + void fadeOut(render::Transaction& transaction, KillAvatarReason reason); // JSDoc is in AvatarData.h. Q_INVOKABLE virtual float getEyeHeight() const override; @@ -727,7 +725,6 @@ protected: bool _initialized { false }; bool _isAnimatingScale { false }; bool _mustFadeIn { false }; - bool _isFading { false }; bool _reconstructSoftEntitiesJointMap { false }; float _modelScale { 1.0f }; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 802e5c953e..ffb37d6de9 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1708,6 +1708,7 @@ protected: glm::vec3 _globalBoundingBoxOffset; AABox _defaultBubbleBox; + AABox _fitBoundingBox; mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c16d65506a..a8f1448a3f 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -229,8 +229,9 @@ AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const { QReadLocker locker(&_hashLock); - if (_avatarHash.contains(sessionUUID)) { - return _avatarHash.value(sessionUUID); + auto avatarIter = _avatarHash.find(sessionUUID); + if (avatarIter != _avatarHash.end()) { + return avatarIter.value(); } return nullptr; } @@ -439,7 +440,6 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo } auto removedAvatar = _avatarHash.take(sessionUUID); - if (removedAvatar) { removedAvatars.push_back(removedAvatar); } diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index 2189e7bdc3..b7eb56c921 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -33,9 +33,8 @@ #include "ModelBakingLoggingCategory.h" #include "TextureBaker.h" -FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : - ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { +FBXBaker::FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : + ModelBaker(inputModelURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { if (hasBeenBaked) { // Look for the original model file one directory higher. Perhaps this is an oven output directory. QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION)); @@ -45,15 +44,6 @@ FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputText } void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { - _hfmModel = hfmModel; - - if (shouldStop()) { - return; - } - - // enumerate the models and textures found in the scene and start a bake for them - rewriteAndBakeSceneTextures(); - if (shouldStop()) { return; } @@ -114,15 +104,15 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const int meshIndex = 0; for (FBXNode& rootChild : _rootNode.children) { if (rootChild.name == "Objects") { - for (FBXNode& object : rootChild.children) { - if (object.name == "Geometry") { - if (object.properties.at(2) == "Mesh") { + for (auto object = rootChild.children.begin(); object != rootChild.children.end(); object++) { + if (object->name == "Geometry") { + if (object->properties.at(2) == "Mesh") { int meshNum = meshIndexToRuntimeOrder[meshIndex]; - replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); + replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); meshIndex++; } - } else if (object.name == "Model") { - for (FBXNode& modelChild : object.children) { + } else if (object->name == "Model") { + for (FBXNode& modelChild : object->children) { if (modelChild.name == "Properties60" || modelChild.name == "Properties70") { // This is a properties node // Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer @@ -142,10 +132,13 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const } else if (modelChild.name == "Vertices") { // This model is also a mesh int meshNum = meshIndexToRuntimeOrder[meshIndex]; - replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); + replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); meshIndex++; } } + } else if (object->name == "Texture" || object->name == "Video") { + // this is an embedded texture, we need to remove it from the FBX + object = rootChild.children.erase(object); } if (hasErrors()) { @@ -154,82 +147,4 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const } } } -} - -void FBXBaker::rewriteAndBakeSceneTextures() { - using namespace image::TextureUsage; - QHash textureTypes; - - // enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID - for (const auto& material : _hfmModel->materials) { - if (material.normalTexture.isBumpmap) { - textureTypes[material.normalTexture.id] = BUMP_TEXTURE; - } else { - textureTypes[material.normalTexture.id] = NORMAL_TEXTURE; - } - - textureTypes[material.albedoTexture.id] = ALBEDO_TEXTURE; - textureTypes[material.glossTexture.id] = GLOSS_TEXTURE; - textureTypes[material.roughnessTexture.id] = ROUGHNESS_TEXTURE; - textureTypes[material.specularTexture.id] = SPECULAR_TEXTURE; - textureTypes[material.metallicTexture.id] = METALLIC_TEXTURE; - textureTypes[material.emissiveTexture.id] = EMISSIVE_TEXTURE; - textureTypes[material.occlusionTexture.id] = OCCLUSION_TEXTURE; - textureTypes[material.lightmapTexture.id] = LIGHTMAP_TEXTURE; - } - - // enumerate the children of the root node - for (FBXNode& rootChild : _rootNode.children) { - - if (rootChild.name == "Objects") { - - // enumerate the objects - auto object = rootChild.children.begin(); - while (object != rootChild.children.end()) { - if (object->name == "Texture") { - - // double check that we didn't get an abort while baking the last texture - if (shouldStop()) { - return; - } - - // enumerate the texture children - for (FBXNode& textureChild : object->children) { - - if (textureChild.name == "RelativeFilename") { - QString hfmTextureFileName { textureChild.properties.at(0).toString() }; - - // grab the ID for this texture so we can figure out the - // texture type from the loaded materials - auto textureID { object->properties[0].toString() }; - auto textureType = textureTypes[textureID]; - - // Compress the texture information and return the new filename to be added into the FBX scene - auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType); - - // If no errors or warnings have occurred during texture compression add the filename to the FBX scene - if (!bakedTextureFile.isNull()) { - textureChild.properties[0] = bakedTextureFile; - } else { - // if bake fails - return, if there were errors and continue, if there were warnings. - if (hasErrors()) { - return; - } else if (hasWarnings()) { - continue; - } - } - } - } - - ++object; - - } else if (object->name == "Video") { - // this is an embedded texture, we need to remove it from the FBX - object = rootChild.children.erase(object); - } else { - ++object; - } - } - } - } -} +} \ No newline at end of file diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index 59ef5e349d..5daf8a8adf 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -31,20 +31,14 @@ using TextureBakerThreadGetter = std::function; class FBXBaker : public ModelBaker { Q_OBJECT public: - FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: void rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); - void rewriteAndBakeSceneTextures(); void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); - - hfm::Model::Pointer _hfmModel; - - bool _pendingErrorEmission { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index dd1ba55e54..9fcd7d0354 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -27,21 +27,11 @@ std::function MaterialBaker::_getNextOvenWorkerThreadOperator; static int materialNum = 0; -namespace std { - template <> - struct hash { - size_t operator()(const graphics::Material::MapChannel& a) const { - return std::hash()((size_t)a); - } - }; -}; - -MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath) : +MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir) : _materialData(materialData), _isURL(isURL), _bakedOutputDir(bakedOutputDir), - _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)), - _destinationPath(destinationPath) + _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)) { } @@ -64,6 +54,14 @@ void MaterialBaker::bake() { } } +void MaterialBaker::abort() { + Baker::abort(); + + for (auto& textureBaker : _textureBakers) { + textureBaker->abort(); + } +} + void MaterialBaker::loadMaterial() { if (!_isURL) { qCDebug(material_baking) << "Loading local material" << _materialData; @@ -104,45 +102,42 @@ void MaterialBaker::processMaterial() { for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) { if (networkMaterial.second) { - auto textureMaps = networkMaterial.second->getTextureMaps(); - for (auto textureMap : textureMaps) { - if (textureMap.second && textureMap.second->getTextureSource()) { - graphics::Material::MapChannel mapChannel = textureMap.first; - auto texture = textureMap.second->getTextureSource(); + auto textures = networkMaterial.second->getTextures(); + for (auto texturePair : textures) { + auto mapChannel = texturePair.first; + auto textureMap = texturePair.second; + if (textureMap.texture && textureMap.texture->_textureSource) { + auto type = textureMap.texture->getTextureType(); - QUrl url = texture->getUrl(); - QString cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString(); + QByteArray content; + QUrl textureURL; + { + bool foundEmbeddedTexture = false; + auto textureContentMapIter = _textureContentMap.find(networkMaterial.second->getName()); + if (textureContentMapIter != _textureContentMap.end()) { + auto textureUsageIter = textureContentMapIter->second.find(type); + if (textureUsageIter != textureContentMapIter->second.end()) { + content = textureUsageIter->second.first; + textureURL = textureUsageIter->second.second; + foundEmbeddedTexture = true; + } + } + if (!foundEmbeddedTexture && textureMap.texture->_textureSource) { + textureURL = textureMap.texture->_textureSource->getUrl().adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + } + } + + QString cleanURL = textureURL.toDisplayString(); auto idx = cleanURL.lastIndexOf('.'); - auto extension = idx >= 0 ? url.toDisplayString().mid(idx + 1).toLower() : ""; + QString extension = idx >= 0 ? cleanURL.mid(idx + 1).toLower() : ""; if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) { - QUrl textureURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); - - // FIXME: this isn't properly handling bumpMaps or glossMaps - static std::unordered_map MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP; - if (MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.empty()) { - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::EMISSIVE_MAP] = image::TextureUsage::EMISSIVE_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ALBEDO_MAP] = image::TextureUsage::ALBEDO_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::METALLIC_MAP] = image::TextureUsage::METALLIC_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ROUGHNESS_MAP] = image::TextureUsage::ROUGHNESS_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::NORMAL_MAP] = image::TextureUsage::NORMAL_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::OCCLUSION_MAP] = image::TextureUsage::OCCLUSION_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::LIGHTMAP_MAP] = image::TextureUsage::LIGHTMAP_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::SCATTERING_MAP] = image::TextureUsage::SCATTERING_TEXTURE; - } - - auto it = MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.find(mapChannel); - if (it == MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.end()) { - handleError("Unknown map channel"); - return; - } - - QPair textureKey(textureURL, it->second); + QPair textureKey(textureURL, type); if (!_textureBakers.contains(textureKey)) { - auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), it->second); + auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type); QSharedPointer textureBaker { - new TextureBaker(textureURL, it->second, _textureOutputDir, "", baseTextureFileName), + new TextureBaker(textureURL, type, _textureOutputDir, "", baseTextureFileName, content), &TextureBaker::deleteLater }; textureBaker->setMapChannel(mapChannel); @@ -179,7 +174,7 @@ void MaterialBaker::handleFinishedTextureBaker() { // Replace the old texture URLs for (auto networkMaterial : _materialsNeedingRewrite.values(textureKey)) { - networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(_destinationPath.resolved(relativeURL)); + networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(relativeURL); } } else { // this texture failed to bake - this doesn't fail the entire bake but we need to add the errors from @@ -245,3 +240,30 @@ void MaterialBaker::outputMaterial() { // emit signal to indicate the material baking is finished emit finished(); } + +void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture) { + auto& textureUsageMap = _textureContentMap[materialName.toStdString()]; + if (textureUsageMap.find(textureUsage) == textureUsageMap.end() && !texture.content.isEmpty()) { + textureUsageMap[textureUsage] = { texture.content, texture.filename }; + } +}; + +void MaterialBaker::setMaterials(const QHash& materials, const QString& baseURL) { + _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + for (auto& material : materials) { + _materialResource->parsedMaterials.names.push_back(material.name.toStdString()); + _materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared(material, baseURL); + + // Store any embedded texture content + addTexture(material.name, image::TextureUsage::NORMAL_TEXTURE, material.normalTexture); + addTexture(material.name, image::TextureUsage::ALBEDO_TEXTURE, material.albedoTexture); + addTexture(material.name, image::TextureUsage::GLOSS_TEXTURE, material.glossTexture); + addTexture(material.name, image::TextureUsage::ROUGHNESS_TEXTURE, material.roughnessTexture); + addTexture(material.name, image::TextureUsage::SPECULAR_TEXTURE, material.specularTexture); + addTexture(material.name, image::TextureUsage::METALLIC_TEXTURE, material.metallicTexture); + addTexture(material.name, image::TextureUsage::EMISSIVE_TEXTURE, material.emissiveTexture); + addTexture(material.name, image::TextureUsage::OCCLUSION_TEXTURE, material.occlusionTexture); + addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture); + addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture); + } +} \ No newline at end of file diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h index 41ce31380e..ab2a0a5901 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -24,16 +24,19 @@ static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; class MaterialBaker : public Baker { Q_OBJECT public: - MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath); + MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir); QString getMaterialData() const { return _materialData; } bool isURL() const { return _isURL; } QString getBakedMaterialData() const { return _bakedMaterialData; } + void setMaterials(const QHash& materials, const QString& baseURL); + static void setNextOvenWorkerThreadOperator(std::function getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; } public slots: virtual void bake() override; + virtual void abort() override; signals: void originalMaterialLoaded(); @@ -57,11 +60,18 @@ private: QString _bakedOutputDir; QString _textureOutputDir; QString _bakedMaterialData; - QUrl _destinationPath; QScriptEngine _scriptEngine; static std::function _getNextOvenWorkerThreadOperator; TextureFileNamer _textureFileNamer; + + void addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture); + struct TextureUsageHash { + std::size_t operator()(image::TextureUsage::Type textureUsage) const { + return static_cast(textureUsage); + } + }; + std::unordered_map, TextureUsageHash>> _textureContentMap; }; #endif // !hifi_MaterialBaker_h diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 9568a81578..e58ec00afa 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -42,12 +42,12 @@ #include "baking/BakerLibrary.h" -ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : +#include + +ModelBaker::ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : _modelURL(inputModelURL), _bakedOutputDir(bakedOutputDirectory), _originalOutputDir(originalOutputDirectory), - _textureThreadGetter(inputTextureThreadGetter), _hasBeenBaked(hasBeenBaked) { auto bakedFilename = _modelURL.fileName(); @@ -209,7 +209,6 @@ void ModelBaker::bakeSourceCopy() { } hifi::ByteArray modelData = modelFile.readAll(); - hfm::Model::Pointer bakedModel; std::vector dracoMeshes; std::vector> dracoMaterialLists; // Material order for per-mesh material lookup used by dracoMeshes @@ -245,40 +244,76 @@ void ModelBaker::bakeSourceCopy() { // Begin hfm baking baker.run(); - bakedModel = baker.getHFMModel(); + _hfmModel = baker.getHFMModel(); dracoMeshes = baker.getDracoMeshes(); dracoMaterialLists = baker.getDracoMaterialLists(); } - // Populate _textureContentMap with path to content mappings, for quick lookup by URL - for (auto materialIt = bakedModel->materials.cbegin(); materialIt != bakedModel->materials.cend(); materialIt++) { - static const auto addTexture = [](QHash& textureContentMap, const hfm::Texture& texture) { - if (!textureContentMap.contains(texture.filename)) { - // Content may be empty, unless the data is inlined - textureContentMap[texture.filename] = texture.content; - } - }; - const hfm::Material& material = *materialIt; - addTexture(_textureContentMap, material.normalTexture); - addTexture(_textureContentMap, material.albedoTexture); - addTexture(_textureContentMap, material.opacityTexture); - addTexture(_textureContentMap, material.glossTexture); - addTexture(_textureContentMap, material.roughnessTexture); - addTexture(_textureContentMap, material.specularTexture); - addTexture(_textureContentMap, material.metallicTexture); - addTexture(_textureContentMap, material.emissiveTexture); - addTexture(_textureContentMap, material.occlusionTexture); - addTexture(_textureContentMap, material.scatteringTexture); - addTexture(_textureContentMap, material.lightmapTexture); - } - // Do format-specific baking - bakeProcessedSource(bakedModel, dracoMeshes, dracoMaterialLists); + bakeProcessedSource(_hfmModel, dracoMeshes, dracoMaterialLists); if (shouldStop()) { return; } + if (_hfmModel->materials.size() > 0) { + _materialBaker = QSharedPointer( + new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir), + &MaterialBaker::deleteLater + ); + _materialBaker->setMaterials(_hfmModel->materials, _modelURL.toString()); + connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker); + _materialBaker->bake(); + } else { + outputBakedFST(); + } +} + +void ModelBaker::handleFinishedMaterialBaker() { + auto baker = qobject_cast(sender()); + + if (baker) { + if (!baker->hasErrors()) { + // this MaterialBaker is done and everything went according to plan + qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData(); + + QString relativeBakedMaterialURL = _modelURL.fileName(); + auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.')); + relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION; + + // First we add the materials in the model + QJsonArray materialMapping; + for (auto material : _hfmModel->materials) { + QJsonObject json; + json["mat::" + material.name] = relativeBakedMaterialURL + "#" + material.name; + materialMapping.push_back(json); + } + + // The we add any existing mappings from the mapping + if (_mapping.contains(MATERIAL_MAPPING_FIELD)) { + QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray(); + QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object(); + for (auto key : oldMaterialMapping.keys()) { + QJsonObject json; + json[key] = oldMaterialMapping[key]; + materialMapping.push_back(json); + } + } + + _mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact); + } else { + // this material failed to bake - this doesn't fail the entire bake but we need to add the errors from + // the material to our warnings + _warningList << baker->getWarnings(); + } + } else { + handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString()); + } + + outputBakedFST(); +} + +void ModelBaker::outputBakedFST() { // Output FST file, copying over input mappings if available QString outputFSTFilename = !_mappingURL.isEmpty() ? _mappingURL.fileName() : _modelURL.fileName(); auto extensionStart = outputFSTFilename.indexOf("."); @@ -291,8 +326,7 @@ void ModelBaker::bakeSourceCopy() { auto outputMapping = _mapping; outputMapping[FST_VERSION_FIELD] = FST_VERSION; outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName(); - // All textures will be found in the same directory as the model - outputMapping[TEXDIR_FIELD] = "."; + outputMapping.remove(TEXDIR_FIELD); hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping); QFile fstOutputFile { outputFSTURL }; @@ -307,17 +341,16 @@ void ModelBaker::bakeSourceCopy() { _outputFiles.push_back(outputFSTURL); _outputMappingURL = outputFSTURL; - // check if we're already done with textures (in case we had none to re-write) - checkIfTexturesFinished(); + exportScene(); + qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL; + emit finished(); } void ModelBaker::abort() { Baker::abort(); - // tell our underlying TextureBaker instances to abort - // the ModelBaker will wait until all are aborted before emitting its own abort signal - for (auto& textureBaker : _bakingTextures) { - textureBaker->abort(); + if (_materialBaker) { + _materialBaker->abort(); } } @@ -354,247 +387,6 @@ bool ModelBaker::buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dr return true; } -QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) { - - QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") }; - - if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) { - // re-baking a model that already references baked textures - // this is an error - return from here - handleError("Cannot re-bake a file that already references compressed textures"); - return QString::null; - } - - if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { - // this is a texture format we don't bake, skip it - handleWarning(modelTextureFileName + " is not a bakeable texture format"); - return QString::null; - } - - // make sure this texture points to something and isn't one we've already re-mapped - QString textureChild { QString::null }; - if (!modelTextureFileInfo.filePath().isEmpty()) { - // check if this was an embedded texture that we already have in-memory content for - QByteArray textureContent; - - // figure out the URL to this texture, embedded or external - if (!modelTextureFileInfo.filePath().isEmpty()) { - textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit()); - } - auto urlToTexture = getTextureURL(modelTextureFileInfo, !textureContent.isNull()); - - TextureKey textureKey { urlToTexture, textureType }; - auto bakingTextureIt = _bakingTextures.find(textureKey); - if (bakingTextureIt == _bakingTextures.cend()) { - // construct the new baked texture file name and file path - // ensuring that the baked texture will have a unique name - // even if there was another texture with the same name at a different path - QString baseTextureFileName = _textureFileNamer.createBaseTextureFileName(modelTextureFileInfo, textureType); - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX - }; - - textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX; - - _outputFiles.push_back(bakedTextureFilePath); - - // bake this texture asynchronously - bakeTexture(textureKey, _bakedOutputDir, baseTextureFileName, textureContent); - } else { - // Fetch existing texture meta name - textureChild = (*bakingTextureIt)->getBaseFilename() + BAKED_META_TEXTURE_SUFFIX; - } - } - - qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName - << "to" << textureChild; - - return textureChild; -} - -void ModelBaker::bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) { - // start a bake for this texture and add it to our list to keep track of - QSharedPointer bakingTexture{ - new TextureBaker(textureKey.first, textureKey.second, outputDir, "../", bakedFilename, textureContent), - &TextureBaker::deleteLater - }; - - // make sure we hear when the baking texture is done or aborted - connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture); - connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture); - - // keep a shared pointer to the baking texture - _bakingTextures.insert(textureKey, bakingTexture); - - // start baking the texture on one of our available worker threads - bakingTexture->moveToThread(_textureThreadGetter()); - QMetaObject::invokeMethod(bakingTexture.data(), "bake"); -} - -void ModelBaker::handleBakedTexture() { - TextureBaker* bakedTexture = qobject_cast(sender()); - qDebug() << "Handling baked texture" << bakedTexture->getTextureURL(); - - // make sure we haven't already run into errors, and that this is a valid texture - if (bakedTexture) { - if (!shouldStop()) { - if (!bakedTexture->hasErrors()) { - if (!_originalOutputDir.isEmpty()) { - // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture - - // use the path to the texture being baked to determine if this was an embedded or a linked texture - - // it is embeddded if the texure being baked was inside a folder with the name of the model - // since that is the fake URL we provide when baking external textures - - if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original model - - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL()); - - QFile originalTextureFile{ - _originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; - - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path - } - - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _modelURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _modelURL.toString()); - return; - } - } - } - - - // now that this texture has been baked and handled, we can remove that TextureBaker from our hash - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - checkIfTexturesFinished(); - } else { - // there was an error baking this texture - add it to our list of errors - _errorList.append(bakedTexture->getErrors()); - - // we don't emit finished yet so that the other textures can finish baking first - _pendingErrorEmission = true; - - // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); - } - } else { - // we have errors to attend to, so we don't do extra processing for this texture - // but we do need to remove that TextureBaker from our list - // and then check if we're done with all textures - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - checkIfTexturesFinished(); - } - } -} - -void ModelBaker::handleAbortedTexture() { - // grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore - TextureBaker* bakedTexture = qobject_cast(sender()); - - qDebug() << "Texture aborted: " << bakedTexture->getTextureURL(); - - if (bakedTexture) { - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - } - - // since a texture we were baking aborted, our status is also aborted - _shouldAbort.store(true); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); -} - -QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded) { - QUrl urlToTexture; - - if (isEmbedded) { - urlToTexture = _modelURL.toString() + "/" + textureFileInfo.filePath(); - } else { - if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // set the texture URL to the local texture that we have confirmed exists - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // external texture that we'll need to download or find - - // this is a relative file path which will require different handling - // depending on the location of the original model - if (_modelURL.isLocalFile() && textureFileInfo.exists() && textureFileInfo.isFile()) { - // the absolute path we ran into for the texture in the model exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the model to match the behaviour of interface - urlToTexture = _modelURL.resolved(textureFileInfo.fileName()); - } - } - } - - return urlToTexture; -} - -QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) { - auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - - if (texturePath.startsWith(modelPath)) { - // texture path is a child of the model path, return the texture path without the model path - return texturePath.mid(modelPath.length()); - } else { - // the texture path was not a child of the model path, return the empty string - return ""; - } -} - -void ModelBaker::checkIfTexturesFinished() { - // check if we're done everything we need to do for this model - // and emit our finished signal if we're done - - if (_bakingTextures.isEmpty()) { - if (shouldStop()) { - // if we're checking for completion but we have errors - // that means one or more of our texture baking operations failed - - if (_pendingErrorEmission) { - setIsFinished(true); - } - - return; - } else { - qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL; - - texturesFinished(); - - setIsFinished(true); - } - } -} - void ModelBaker::setWasAborted(bool wasAborted) { if (wasAborted != _wasAborted.load()) { Baker::setWasAborted(wasAborted); @@ -605,70 +397,6 @@ void ModelBaker::setWasAborted(bool wasAborted) { } } -void ModelBaker::texturesFinished() { - embedTextureMetaData(); - exportScene(); -} - -void ModelBaker::embedTextureMetaData() { - std::vector embeddedTextureNodes; - - for (FBXNode& rootChild : _rootNode.children) { - if (rootChild.name == "Objects") { - qlonglong maxId = 0; - for (auto &child : rootChild.children) { - if (child.properties.length() == 3) { - maxId = std::max(maxId, child.properties[0].toLongLong()); - } - } - - for (auto& object : rootChild.children) { - if (object.name == "Texture") { - QVariant relativeFilename; - for (auto& child : object.children) { - if (child.name == "RelativeFilename") { - relativeFilename = child.properties[0]; - break; - } - } - - if (relativeFilename.isNull() - || !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) { - continue; - } - if (object.properties.length() < 2) { - qWarning() << "Found texture with unexpected number of properties: " << object.name; - continue; - } - - FBXNode videoNode; - videoNode.name = "Video"; - videoNode.properties.append(++maxId); - videoNode.properties.append(object.properties[1]); - videoNode.properties.append("Clip"); - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + relativeFilename.toString() - }; - - QFile textureFile { bakedTextureFilePath }; - if (!textureFile.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open: " << bakedTextureFilePath; - continue; - } - - videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } }); - videoNode.children.append({ "Content", { textureFile.readAll() }, { } }); - - rootChild.children.append(videoNode); - - textureFile.close(); - } - } - } - } -} - void ModelBaker::exportScene() { auto fbxData = FBXWriter::encodeFBX(_rootNode); diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h index d9a559392f..d612a0a43a 100644 --- a/libraries/baking/src/ModelBaker.h +++ b/libraries/baking/src/ModelBaker.h @@ -18,17 +18,13 @@ #include #include "Baker.h" -#include "TextureBaker.h" -#include "baking/TextureFileNamer.h" +#include "MaterialBaker.h" #include "ModelBakingLoggingCategory.h" -#include - #include #include -using TextureBakerThreadGetter = std::function; using GetMaterialIDCallback = std::function ; static const QString FST_EXTENSION { ".fst" }; @@ -42,10 +38,7 @@ class ModelBaker : public Baker { Q_OBJECT public: - using TextureKey = QPair; - - ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); void setOutputURLSuffix(const QUrl& urlSuffix); void setMappingURL(const QUrl& mappingURL); @@ -54,7 +47,6 @@ public: void initializeOutputDirs(); bool buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); - QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); virtual void setWasAborted(bool wasAborted) override; QUrl getModelURL() const { return _modelURL; } @@ -71,20 +63,15 @@ public slots: protected: void saveSourceModel(); virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) = 0; - void checkIfTexturesFinished(); - void texturesFinished(); - void embedTextureMetaData(); void exportScene(); FBXNode _rootNode; - QHash _textureContentMap; QUrl _modelURL; QUrl _outputURLSuffix; QUrl _mappingURL; hifi::VariantHash _mapping; QString _bakedOutputDir; QString _originalOutputDir; - TextureBakerThreadGetter _textureThreadGetter; QString _originalOutputModelPath; QString _outputMappingURL; QUrl _bakedModelURL; @@ -92,23 +79,15 @@ protected: protected slots: void handleModelNetworkReply(); virtual void bakeSourceCopy(); - -private slots: - void handleBakedTexture(); - void handleAbortedTexture(); + void handleFinishedMaterialBaker(); private: - QUrl getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded = false); - void bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent); - QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL); - - QMultiHash> _bakingTextures; - QHash _textureNameMatchCount; - bool _pendingErrorEmission { false }; + void outputBakedFST(); bool _hasBeenBaked { false }; - TextureFileNamer _textureFileNamer; + hfm::Model::Pointer _hfmModel; + QSharedPointer _materialBaker; }; #endif // hifi_ModelBaker_h diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index 70bdeb2071..a2d0ab1094 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -132,55 +132,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h handleWarning("Baked mesh for OBJ model '" + _modelURL.toString() + "' is empty"); } - // Generating Texture Node - // iterate through mesh parts and process the associated textures - auto size = meshParts.size(); - for (int i = 0; i < size; i++) { - QString material = meshParts[i].materialID; - HFMMaterial currentMaterial = hfmModel->materials[material]; - if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { - auto textureID = nextNodeID(); - _mapTextureMaterial.emplace_back(textureID, i); - - FBXNode textureNode; - { - textureNode.name = TEXTURE_NODE_NAME; - textureNode.properties = { textureID, "texture" + QString::number(textureID) }; - } - - // Texture node child - TextureName node - FBXNode textureNameNode; - { - textureNameNode.name = TEXTURENAME_NODE_NAME; - QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka"; - textureNameNode.properties = { propertyString }; - } - - // Texture node child - Relative Filename node - FBXNode relativeFilenameNode; - { - relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME; - } - - QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename; - - auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE; - - // Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node - auto textureFile = compressTexture(textureFileName, textureType); - if (textureFile.isNull()) { - // Baking failed return - handleError("Failed to compress texture: " + textureFileName); - return; - } - relativeFilenameNode.properties = { textureFile }; - - textureNode.children = { textureNameNode, relativeFilenameNode }; - - objectNode.children.append(textureNode); - } - } - // Generating Connections node connectionsNode.name = CONNECTIONS_NODE_NAME; @@ -199,29 +150,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID }; connectionsNode.children.append(cNode); } - - // Connect textures to materials - for (const auto& texMat : _mapTextureMaterial) { - FBXNode cAmbientNode; - cAmbientNode.name = C_NODE_NAME; - cAmbientNode.properties = { - CONNECTIONS_NODE_PROPERTY_1, - texMat.first, - _materialIDs[texMat.second], - "AmbientFactor" - }; - connectionsNode.children.append(cAmbientNode); - - FBXNode cDiffuseNode; - cDiffuseNode.name = C_NODE_NAME; - cDiffuseNode.properties = { - CONNECTIONS_NODE_PROPERTY_1, - texMat.first, - _materialIDs[texMat.second], - "DiffuseColor" - }; - connectionsNode.children.append(cDiffuseNode); - } } // Set properties for material nodes diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index d1eced5452..9bd1431d28 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -35,9 +35,7 @@ private: void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel); NodeID nextNodeID() { return _nodeID++; } - NodeID _nodeID { 0 }; std::vector _materialIDs; - std::vector> _mapTextureMaterial; }; #endif // hifi_OBJBaker_h diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index d097b4765b..3756ae86de 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index 4fc9680653..13ad82cff4 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include "Baker.h" diff --git a/libraries/baking/src/baking/BakerLibrary.cpp b/libraries/baking/src/baking/BakerLibrary.cpp index 2afeef4800..e500f9d83f 100644 --- a/libraries/baking/src/baking/BakerLibrary.cpp +++ b/libraries/baking/src/baking/BakerLibrary.cpp @@ -26,11 +26,10 @@ QUrl getBakeableModelURL(const QUrl& url) { GLTF_EXTENSION }; - QUrl cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); - QString cleanURLString = cleanURL.fileName(); + QString filename = url.fileName(); for (auto& extension : extensionsToBake) { - if (cleanURLString.endsWith(extension, Qt::CaseInsensitive)) { - return cleanURL; + if (filename.endsWith(extension, Qt::CaseInsensitive)) { + return url; } } @@ -45,7 +44,7 @@ bool isModelBaked(const QUrl& bakeableModelURL) { return beforeModelExtension.endsWith(".baked"); } -std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) { +std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath) { auto filename = bakeableModelURL.fileName(); // Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique @@ -59,20 +58,20 @@ std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureB QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked"; QString originalOutputDirectory = contentOutputPath + subDirName + "/original"; - return getModelBakerWithOutputDirectories(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); + return getModelBakerWithOutputDirectories(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory); } -std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) { +std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) { auto filename = bakeableModelURL.fileName(); std::unique_ptr baker; if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive)); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive)); } else if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive)); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive)); } else if (filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory); //} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) { //baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); } else { diff --git a/libraries/baking/src/baking/BakerLibrary.h b/libraries/baking/src/baking/BakerLibrary.h index a646c8d36a..8f82661b25 100644 --- a/libraries/baking/src/baking/BakerLibrary.h +++ b/libraries/baking/src/baking/BakerLibrary.h @@ -23,9 +23,9 @@ bool isModelBaked(const QUrl& bakeableModelURL); // Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored // Returns an empty pointer if a baker could not be created -std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath); +std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath); // Similar to getModelBaker, but gives control over where the output folders will be -std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory); +std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory); #endif // hifi_BakerLibrary_h diff --git a/libraries/baking/src/baking/FSTBaker.cpp b/libraries/baking/src/baking/FSTBaker.cpp index acf3bfe1c7..176c35c059 100644 --- a/libraries/baking/src/baking/FSTBaker.cpp +++ b/libraries/baking/src/baking/FSTBaker.cpp @@ -18,9 +18,8 @@ #include -FSTBaker::FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : - ModelBaker(inputMappingURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { +FSTBaker::FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : + ModelBaker(inputMappingURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { if (hasBeenBaked) { // Look for the original model file one directory higher. Perhaps this is an oven output directory. QUrl originalRelativePath = QUrl("../original/" + inputMappingURL.fileName().replace(BAKED_FST_EXTENSION, FST_EXTENSION)); @@ -70,7 +69,7 @@ void FSTBaker::bakeSourceCopy() { return; } - auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _textureThreadGetter, _bakedOutputDir, _originalOutputDir); + auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _bakedOutputDir, _originalOutputDir); _modelBaker = std::unique_ptr(dynamic_cast(baker.release())); if (!_modelBaker) { handleError("The model url '" + bakeableModelURL.toString() + "' from the FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') could not be used to initialize a valid model baker"); diff --git a/libraries/baking/src/baking/FSTBaker.h b/libraries/baking/src/baking/FSTBaker.h index 85c7c93a37..32997680f5 100644 --- a/libraries/baking/src/baking/FSTBaker.h +++ b/libraries/baking/src/baking/FSTBaker.h @@ -18,8 +18,7 @@ class FSTBaker : public ModelBaker { Q_OBJECT public: - FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); virtual QUrl getFullOutputMappingURL() const override; diff --git a/libraries/baking/src/baking/TextureFileNamer.h b/libraries/baking/src/baking/TextureFileNamer.h index 9049588ef1..319d943a03 100644 --- a/libraries/baking/src/baking/TextureFileNamer.h +++ b/libraries/baking/src/baking/TextureFileNamer.h @@ -15,7 +15,7 @@ #include #include -#include +#include class TextureFileNamer { public: diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index f55f5919f5..9828a8beda 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -109,7 +109,7 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { return Parent::internalActivate(); } -void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPointer& compositeFramebuffer) { +void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { #if defined(Q_OS_ANDROID) auto& virtualPadManager = VirtualPad::Manager::instance(); if(virtualPadManager.getLeftVirtualPad()->isShown()) { @@ -121,7 +121,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin render([&](gpu::Batch& batch) { batch.enableStereo(false); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); batch.setPipeline(_cursorPipeline); @@ -140,7 +140,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin }); } #endif - Parent::compositeExtra(compositeFramebuffer); + Parent::compositeExtra(); } static const uint32_t MIN_THROTTLE_CHECK_FRAMES = 60; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index d4c321a571..cc304c19c2 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -33,7 +33,7 @@ public: virtual bool isThrottled() const override; - virtual void compositeExtra(const gpu::FramebufferPointer&) override; + virtual void compositeExtra() override; virtual void pluginUpdate() override {}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 5bc84acc6a..a91a952bb4 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -109,7 +109,6 @@ public: Q_ASSERT(_context); _context->makeCurrent(); CHECK_GL_ERROR(); - _context->doneCurrent(); while (!_shutdown) { if (_pendingOtherThreadOperation) { PROFILE_RANGE(render, "MainThreadOp") @@ -129,6 +128,7 @@ public: Lock lock(_mutex); _condition.wait(lock, [&] { return _finishedOtherThreadOperation; }); } + _context->makeCurrent(); } // Check for a new display plugin @@ -140,18 +140,16 @@ public: if (newPlugin != currentPlugin) { // Deactivate the old plugin if (currentPlugin != nullptr) { - _context->makeCurrent(); currentPlugin->uncustomizeContext(); CHECK_GL_ERROR(); - _context->doneCurrent(); + // Force completion of all pending GL commands + glFinish(); } if (newPlugin) { bool hasVsync = true; QThread::setPriority(newPlugin->getPresentPriority()); bool wantVsync = newPlugin->wantVsync(); - _context->makeCurrent(); - CHECK_GL_ERROR(); #if defined(Q_OS_MAC) newPlugin->swapBuffers(); #endif @@ -163,7 +161,8 @@ public: newPlugin->setVsyncEnabled(hasVsync); newPlugin->customizeContext(); CHECK_GL_ERROR(); - _context->doneCurrent(); + // Force completion of all pending GL commands + glFinish(); } currentPlugin = newPlugin; _newPluginQueue.pop(); @@ -180,7 +179,6 @@ public: } // Execute the frame and present it to the display device. - _context->makeCurrent(); { PROFILE_RANGE(render, "PluginPresent") gl::globalLock(); @@ -188,9 +186,9 @@ public: gl::globalRelease(false); CHECK_GL_ERROR(); } - _context->doneCurrent(); } + _context->doneCurrent(); Lock lock(_mutex); _context->moveToThread(qApp->thread()); _shutdown = false; @@ -379,6 +377,14 @@ void OpenGLDisplayPlugin::customizeContext() { scissorState->setDepthTest(gpu::State::DepthTest(false)); scissorState->setScissorEnable(true); + { +#ifdef Q_OS_ANDROID + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); +#else + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); +#endif + _simplePipeline = gpu::Pipeline::create(program, scissorState); + } { #ifdef Q_OS_ANDROID gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); @@ -388,59 +394,29 @@ void OpenGLDisplayPlugin::customizeContext() { _presentPipeline = gpu::Pipeline::create(program, scissorState); } - - // HUD operator { - gpu::PipelinePointer hudPipeline; - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); - hudPipeline = gpu::Pipeline::create(program, blendState); - } - - gpu::PipelinePointer hudMirrorPipeline; - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); - hudMirrorPipeline = gpu::Pipeline::create(program, blendState); - } - - - _hudOperator = [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer& compositeFramebuffer, bool mirror) { - auto hudStereo = isStereo(); - auto hudCompositeFramebufferSize = compositeFramebuffer->getSize(); - std::array hudEyeViewports; - for_each_eye([&](Eye eye) { - hudEyeViewports[eye] = eyeViewport(eye); - }); - if (hudPipeline && hudTexture) { - batch.enableStereo(false); - batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); - batch.setResourceTexture(0, hudTexture); - if (hudStereo) { - for_each_eye([&](Eye eye) { - batch.setViewportTransform(hudEyeViewports[eye]); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - } else { - batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize)); - batch.draw(gpu::TRIANGLE_STRIP, 4); - } - } - }; - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); + _hudPipeline = gpu::Pipeline::create(program, blendState); + } + { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); + _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); } - { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture); _cursorPipeline = gpu::Pipeline::create(program, blendState); } } + updateCompositeFramebuffer(); } void OpenGLDisplayPlugin::uncustomizeContext() { _presentPipeline.reset(); _cursorPipeline.reset(); - _hudOperator = DEFAULT_HUD_OPERATOR; + _hudPipeline.reset(); + _mirrorHUDPipeline.reset(); + _compositeFramebuffer.reset(); withPresentThreadLock([&] { _currentFrame.reset(); _lastFrame = nullptr; @@ -532,16 +508,24 @@ void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { }); } +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) { + renderFromTexture(batch, texture, viewport, scissor, nullptr); +} -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { + auto fbo = gpu::FramebufferPointer(); batch.enableStereo(false); batch.resetViewTransform(); - batch.setFramebuffer(destFbo); + batch.setFramebuffer(fbo); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); +#ifndef USE_GLES batch.setPipeline(_presentPipeline); +#else + batch.setPipeline(_simplePipeline); +#endif batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); @@ -567,7 +551,7 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setViewportTransform(copyFboRect); batch.setStateScissorRect(copyFboRect); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, {0.0f, 0.0f, 0.0f, 1.0f}); - batch.blit(destFbo, sourceRect, copyFbo, copyRect); + batch.blit(fbo, sourceRect, copyFbo, copyRect); } } @@ -595,14 +579,41 @@ void OpenGLDisplayPlugin::updateFrameData() { }); } -void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) { +std::function OpenGLDisplayPlugin::getHUDOperator() { + auto hudPipeline = _hudPipeline; + auto hudMirrorPipeline = _mirrorHUDPipeline; + auto hudStereo = isStereo(); + auto hudCompositeFramebufferSize = _compositeFramebuffer->getSize(); + std::array hudEyeViewports; + for_each_eye([&](Eye eye) { + hudEyeViewports[eye] = eyeViewport(eye); + }); + return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { + if (hudPipeline && hudTexture) { + batch.enableStereo(false); + batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); + batch.setResourceTexture(0, hudTexture); + if (hudStereo) { + for_each_eye([&](Eye eye) { + batch.setViewportTransform(hudEyeViewports[eye]); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } else { + batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + } + }; +} + +void OpenGLDisplayPlugin::compositePointer() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; auto cursorTransform = DependencyManager::get()->getReticleTransform(glm::mat4()); render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.setProjectionTransform(mat4()); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.setPipeline(_cursorPipeline); batch.setResourceTexture(0, cursorData.texture); batch.resetViewTransform(); @@ -613,13 +624,34 @@ void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compos batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), compositeFramebuffer->getSize())); + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } }); } -void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenGLDisplayPlugin::compositeScene() { + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.setViewportTransform(ivec4(uvec2(), _compositeFramebuffer->getSize())); + batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); + batch.resetViewTransform(); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_simplePipeline); + batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); +} + +void OpenGLDisplayPlugin::compositeLayers() { + updateCompositeFramebuffer(); + + { + PROFILE_RANGE_EX(render_detail, "compositeScene", 0xff0077ff, (uint64_t)presentCount()) + compositeScene(); + } + #ifdef HIFI_ENABLE_NSIGHT_DEBUG if (false) // do not draw the HUD if running nsight debug #endif @@ -633,35 +665,23 @@ void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) - compositeExtra(compositeFramebuffer); + compositeExtra(); } // Draw the pointer last so it's on top of everything auto compositorHelper = DependencyManager::get(); if (compositorHelper->getReticleVisible()) { PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) - compositePointer(compositeFramebuffer); + compositePointer(); } } -void OpenGLDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenGLDisplayPlugin::internalPresent() { render([&](gpu::Batch& batch) { // Note: _displayTexture must currently be the same size as the display. uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels(); auto viewport = ivec4(uvec2(0), dims); - - gpu::TexturePointer finalTexture; - if (_displayTexture) { - finalTexture = _displayTexture; - } else if (compositeFramebuffer) { - finalTexture = compositeFramebuffer->getRenderBuffer(0); - } else { - qCWarning(displayPlugins) << "No valid texture for output"; - } - - if (finalTexture) { - renderFromTexture(batch, finalTexture, viewport, viewport); - } + renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport); }); swapBuffers(); _presentRate.increment(); @@ -678,7 +698,7 @@ void OpenGLDisplayPlugin::present() { } incrementPresentCount(); - if (_currentFrame && _currentFrame->framebuffer) { + if (_currentFrame) { auto correction = getViewCorrection(); getGLBackend()->setCameraCorrection(correction, _prevRenderView); _prevRenderView = correction * _currentFrame->view; @@ -698,18 +718,18 @@ void OpenGLDisplayPlugin::present() { // Write all layers to a local framebuffer { PROFILE_RANGE_EX(render, "composite", 0xff00ffff, frameId) - compositeLayers(_currentFrame->framebuffer); + compositeLayers(); } // Take the composite framebuffer and send it to the output device { PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId) - internalPresent(_currentFrame->framebuffer); + internalPresent(); } gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); } else if (alwaysPresent()) { - internalPresent(nullptr); + internalPresent(); } _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } @@ -766,12 +786,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) { } QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { - if (!_currentFrame || !_currentFrame->framebuffer) { - return QImage(); - } - - auto compositeFramebuffer = _currentFrame->framebuffer; - auto size = compositeFramebuffer->getSize(); + auto size = _compositeFramebuffer->getSize(); if (isHmd()) { size.x /= 2; } @@ -789,7 +804,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32); withOtherThreadContext([&] { - glBackend->downloadFramebuffer(compositeFramebuffer, ivec4(corner, bestSize), screenshot); + glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); }); return screenshot.mirrored(false, true); } @@ -841,7 +856,7 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const { - auto vpSize = glm::uvec2(getRecommendedRenderSize()); + uvec2 vpSize = _compositeFramebuffer->getSize(); vpSize.x /= 2; uvec2 vpPos; if (eye == Eye::Right) { @@ -874,6 +889,14 @@ void OpenGLDisplayPlugin::render(std::function f) { OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { } +void OpenGLDisplayPlugin::updateCompositeFramebuffer() { + auto renderSize = glm::uvec2(getRecommendedRenderSize()); + if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); + // _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y)); + } +} + void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) { #if !defined(USE_GLES) auto glBackend = const_cast(*this).getGLBackend(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 3c48e8fc48..49a38ecb4c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -94,10 +94,14 @@ protected: // is not populated virtual bool alwaysPresent() const { return false; } + void updateCompositeFramebuffer(); + virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } - virtual void compositeLayers(const gpu::FramebufferPointer&); - virtual void compositePointer(const gpu::FramebufferPointer&); - virtual void compositeExtra(const gpu::FramebufferPointer&) {}; + virtual void compositeLayers(); + virtual void compositeScene(); + virtual std::function getHUDOperator(); + virtual void compositePointer(); + virtual void compositeExtra() {}; // These functions must only be called on the presentation thread virtual void customizeContext(); @@ -112,10 +116,10 @@ protected: virtual void deactivateSession() {} // Plugin specific functionality to send the composed scene to the output window or device - virtual void internalPresent(const gpu::FramebufferPointer&); + virtual void internalPresent(); - - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo = nullptr, const gpu::FramebufferPointer& copyFbo = nullptr); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor); virtual void updateFrameData(); virtual glm::mat4 getViewCorrection() { return glm::mat4(); } @@ -138,8 +142,14 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; mat4 _prevRenderView; + gpu::FramebufferPointer _compositeFramebuffer; + gpu::PipelinePointer _hudPipeline; + gpu::PipelinePointer _mirrorHUDPipeline; + gpu::ShaderPointer _mirrorHUDPS; + gpu::PipelinePointer _simplePipeline; + gpu::PipelinePointer _presentPipeline; gpu::PipelinePointer _cursorPipeline; - gpu::TexturePointer _displayTexture; + gpu::TexturePointer _displayTexture{}; float _compositeHUDAlpha { 1.0f }; struct CursorData { @@ -175,9 +185,5 @@ protected: // be serialized through this mutex mutable Mutex _presentMutex; float _hudAlpha{ 1.0f }; - -private: - gpu::PipelinePointer _presentPipeline; - }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index 95592cc490..f2b1f36419 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -24,7 +24,7 @@ public: protected: void updatePresentPose() override; - void hmdPresent(const gpu::FramebufferPointer&) override {} + void hmdPresent() override {} bool isHmdMounted() const override { return true; } bool internalActivate() override; private: diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index a515232b3f..321bcc3fd2 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -114,23 +114,20 @@ void HmdDisplayPlugin::internalDeactivate() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); - _hudOperator = _hudRenderer.build(); + _hudRenderer.build(); } void HmdDisplayPlugin::uncustomizeContext() { // This stops the weirdness where if the preview was disabled, on switching back to 2D, // the vsync was stuck in the disabled state. No idea why that happens though. _disablePreview = false; - if (_currentFrame && _currentFrame->framebuffer) { - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.resetViewTransform(); - batch.setFramebuffer(_currentFrame->framebuffer); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); - }); - - } - _hudRenderer = {}; + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.resetViewTransform(); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); + }); + _hudRenderer = HUDRenderer(); _previewTexture.reset(); Parent::uncustomizeContext(); } @@ -177,11 +174,11 @@ float HmdDisplayPlugin::getLeftCenterPixel() const { return leftCenterPixel; } -void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) // Composite together the scene, hud and mouse cursor - hmdPresent(compositeFramebuffer); + hmdPresent(); if (_displayTexture) { // Note: _displayTexture must currently be the same size as the display. @@ -263,7 +260,7 @@ void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeF viewport.z *= 2; } - renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, scissor, nullptr, fbo); + renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor, fbo); }); swapBuffers(); @@ -348,7 +345,7 @@ glm::mat4 HmdDisplayPlugin::getViewCorrection() { } } -DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { +void HmdDisplayPlugin::HUDRenderer::build() { vertices = std::make_shared(); indices = std::make_shared(); @@ -383,7 +380,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { indexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; // Compute indices order - std::vector indexData; + std::vector indices; for (int i = 0; i < stacks - 1; i++) { for (int j = 0; j < slices - 1; j++) { GLushort bottomLeftIndex = i * slices + j; @@ -391,21 +388,24 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { GLushort topLeftIndex = bottomLeftIndex + slices; GLushort topRightIndex = topLeftIndex + 1; // FIXME make a z-order curve for better vertex cache locality - indexData.push_back(topLeftIndex); - indexData.push_back(bottomLeftIndex); - indexData.push_back(topRightIndex); + indices.push_back(topLeftIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(topRightIndex); - indexData.push_back(topRightIndex); - indexData.push_back(bottomLeftIndex); - indexData.push_back(bottomRightIndex); + indices.push_back(topRightIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(bottomRightIndex); } } - indices->append(indexData); + this->indices->append(indices); format = std::make_shared(); // 1 for everyone format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); + updatePipeline(); +} +void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -416,6 +416,10 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { pipeline = gpu::Pipeline::create(program, state); } +} + +std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { + updatePipeline(); auto hudPipeline = pipeline; auto hudFormat = format; @@ -424,9 +428,9 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { auto hudUniformBuffer = uniformsBuffer; auto hudUniforms = uniforms; auto hudIndexCount = indexCount; - return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer&, const bool mirror) { - if (pipeline && hudTexture) { - batch.setPipeline(pipeline); + return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { + if (hudPipeline && hudTexture) { + batch.setPipeline(hudPipeline); batch.setInputFormat(hudFormat); gpu::BufferView posView(hudVertices, VERTEX_OFFSET, hudVertices->getSize(), VERTEX_STRIDE, hudFormat->getAttributes().at(gpu::Stream::POSITION)._element); @@ -450,7 +454,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { }; } -void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) { +void HmdDisplayPlugin::compositePointer() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; auto compositorHelper = DependencyManager::get(); @@ -459,7 +463,7 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite render([&](gpu::Batch& batch) { // FIXME use standard gpu stereo rendering for this. batch.enableStereo(false); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.setPipeline(_cursorPipeline); batch.setResourceTexture(0, cursorData.texture); batch.resetViewTransform(); @@ -474,6 +478,10 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite }); } +std::function HmdDisplayPlugin::getHUDOperator() { + return _hudRenderer.render(*this); +} + HmdDisplayPlugin::~HmdDisplayPlugin() { } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 6755c5b7e0..d8c0ce8e1d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -53,15 +53,16 @@ signals: void hmdVisibleChanged(bool visible); protected: - virtual void hmdPresent(const gpu::FramebufferPointer&) = 0; + virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; virtual void postPreview() {}; virtual void updatePresentPose(); bool internalActivate() override; void internalDeactivate() override; - void compositePointer(const gpu::FramebufferPointer&) override; - void internalPresent(const gpu::FramebufferPointer&) override; + std::function getHUDOperator() override; + void compositePointer() override; + void internalPresent() override; void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; @@ -119,6 +120,8 @@ private: static const size_t TEXTURE_OFFSET { offsetof(Vertex, uv) }; static const int VERTEX_STRIDE { sizeof(Vertex) }; - HUDOperator build(); + void build(); + void updatePipeline(); + std::function render(HmdDisplayPlugin& plugin); } _hudRenderer; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 69aa7fc344..0ae0f9b1b6 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -37,13 +37,13 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { return result; } -void InterleavedStereoDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void InterleavedStereoDisplayPlugin::internalPresent() { render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.resetViewTransform(); batch.setFramebuffer(gpu::FramebufferPointer()); batch.setViewportTransform(ivec4(uvec2(0), getSurfacePixels())); - batch.setResourceTexture(0, compositeFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.setPipeline(_interleavedPresentPipeline); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 52dfa8f402..debd340f24 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -21,7 +21,7 @@ protected: // initialize OpenGL context settings needed by the plugin void customizeContext() override; void uncustomizeContext() override; - void internalPresent(const gpu::FramebufferPointer&) override; + void internalPresent() override; private: static const QString NAME; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c235460404..6cfff7bc41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -221,17 +221,15 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { // remove all entities from the scene auto scene = _viewState->getMain3DScene(); if (scene) { - render::Transaction transaction; for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { - renderer->removeFromScene(scene, transaction); + fadeOutRenderable(renderer); } else { savedEntities[entry.first] = entry.second; } } - scene->enqueueTransaction(transaction); } _renderablesToUpdate = savedEntities; @@ -258,12 +256,10 @@ void EntityTreeRenderer::clear() { // remove all entities from the scene auto scene = _viewState->getMain3DScene(); if (scene) { - render::Transaction transaction; for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; - renderer->removeFromScene(scene, transaction); + fadeOutRenderable(renderer); } - scene->enqueueTransaction(transaction); } else { qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; } @@ -1016,10 +1012,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities - // here's where we remove the entity payload from the scene - render::Transaction transaction; - renderable->removeFromScene(scene, transaction); - scene->enqueueTransaction(transaction); + fadeOutRenderable(renderable); } void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { @@ -1057,13 +1050,26 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool } } +void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderable) { + render::Transaction transaction; + auto scene = _viewState->getMain3DScene(); + + transaction.transitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() { + render::Transaction transaction; + renderable->removeFromScene(scene, transaction); + scene->enqueueTransaction(transaction); + }); + + scene->enqueueTransaction(transaction); +} + void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision) { assert((bool)entity); auto renderable = renderableForEntity(entity); - if (!renderable) { - return; + if (!renderable) { + return; } - + SharedSoundPointer collisionSound = renderable->getCollisionSound(); if (!collisionSound) { return; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a511d73210..cee91ad1c7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -93,6 +93,8 @@ public: /// reloads the entity scripts, calling unload and preload void reloadEntityScripts(); + void fadeOutRenderable(const EntityRendererPointer& renderable); + // event handles which may generate entity related events QUuid mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); @@ -255,6 +257,7 @@ private: std::unordered_map _renderablesToUpdate; std::unordered_map _entitiesInScene; std::unordered_map _entitiesToAdd; + // For Scene.shouldRenderEntities QList _entityIDsLastInScene; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index a6826da91b..fb6fbad2ac 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -148,7 +148,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entit }); } -EntityRenderer::~EntityRenderer() { } +EntityRenderer::~EntityRenderer() {} // // Smart payload proxy members, implementing the payload interface @@ -166,7 +166,10 @@ ShapeKey EntityRenderer::getShapeKey() { } render::hifi::Tag EntityRenderer::getTagMask() const { - return _isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW; + render::hifi::Tag mask = render::hifi::TAG_NONE; + mask = (render::hifi::Tag)(mask | (!_cauterized * render::hifi::TAG_MAIN_VIEW)); + mask = (render::hifi::Tag)(mask | (_isVisibleInSecondaryCamera * render::hifi::TAG_SECONDARY_VIEW)); + return mask; } render::hifi::Layer EntityRenderer::getHifiRenderLayer() const { @@ -215,12 +218,7 @@ void EntityRenderer::render(RenderArgs* args) { emit requestRenderUpdate(); } - auto& renderMode = args->_renderMode; - bool cauterized = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && - renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE && - _cauterized); - - if (_visible && !cauterized) { + if (_visible && (args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || !_cauterized)) { doRender(args); } } @@ -421,6 +419,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa if (fading) { _isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; } + _prevIsTransparent = transparent; updateModelTransformAndBound(); @@ -493,4 +492,4 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls } return result; -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index b474fb2266..01d1098daa 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -121,7 +121,11 @@ void MaterialEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo QString materialURL = entity->getMaterialURL(); if (materialURL != _materialURL) { _materialURL = materialURL; - if (_materialURL.contains("?")) { + if (_materialURL.contains("#")) { + auto split = _materialURL.split("#"); + newCurrentMaterialName = split.last().toStdString(); + } else if (_materialURL.contains("?")) { + qDebug() << "DEPRECATED: Use # instead of ? for material URLS:" << _materialURL; auto split = _materialURL.split("?"); newCurrentMaterialName = split.last().toStdString(); } @@ -358,7 +362,13 @@ void MaterialEntityRenderer::deleteMaterial(const QUuid& oldParentID, const QStr return; } - // if a remove fails, our parent is gone, so we don't need to retry + // if a remove fails, our parent is gone, so we don't need to retry, EXCEPT: + // MyAvatar can change UUIDs when you switch domains, which leads to a timing issue. Let's just make + // sure we weren't attached to MyAvatar by trying this (if we weren't, this will have no effect) + if (EntityTreeRenderer::removeMaterialFromAvatar(AVATAR_SELF_ID, material, oldParentMaterialNameStd)) { + _appliedMaterial = nullptr; + return; + } } void MaterialEntityRenderer::applyTextureTransform(std::shared_ptr& material) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3308ce020d..bfbbe12ea6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) { } void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { - // because the caching system only allows one Geometry per url, and because this url might also be used - // as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it - // will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and - // parse it twice. auto currentCompoundShapeURL = getCompoundShapeURL(); ModelEntityItem::setCompoundShapeURL(url); if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) { @@ -1070,13 +1066,6 @@ ItemKey ModelEntityRenderer::getKey() { return _itemKey; } -render::hifi::Tag ModelEntityRenderer::getTagMask() const { - // Default behavior for model is to not be visible in main view if cauterized (aka parented to the avatar's neck joint) - return _cauterized ? - (_isVisibleInSecondaryCamera ? render::hifi::TAG_SECONDARY_VIEW : render::hifi::TAG_NONE) : - Parent::getTagMask(); // calculate which views to be shown in -} - uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { if (_model) { auto metaSubItems = _model->fetchRenderItemIDs(); @@ -1413,6 +1402,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce model->setVisibleInScene(_visible, scene); } + if (model->isCauterized() != _cauterized) { + model->setCauterized(_cauterized, scene); + } + render::hifi::Tag tagMask = getTagMask(); if (model->getTagMask() != tagMask) { model->setTagMask(tagMask, scene); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 2fd1041c5f..ee6e7d0b04 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -161,8 +161,6 @@ protected: virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - render::hifi::Tag getTagMask() const override; - void setIsVisibleInSecondaryCamera(bool value) override; void setRenderLayer(RenderLayer value) override; void setPrimitiveMode(PrimitiveMode value) override; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index c139fbf320..d517ecd026 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -1,4 +1,4 @@ -// +// // RenderableParticleEffectEntityItem.cpp // interface/src // @@ -9,12 +9,12 @@ // #include "RenderableParticleEffectEntityItem.h" - #include #include #include +#include using namespace render; using namespace render::entities; @@ -79,6 +79,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE return true; } + if (_shapeType != entity->getShapeType()) { + return true; + } + + if (_compoundShapeURL != entity->getCompoundShapeURL()) { + return true; + } + return false; } @@ -87,10 +95,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi if (!newParticleProperties.valid()) { qCWarning(entitiesrenderer) << "Bad particle properties"; } - - if (resultWithReadLock([&]{ return _particleProperties != newParticleProperties; })) { + + if (resultWithReadLock([&] { return _particleProperties != newParticleProperties; })) { _timeUntilNextEmit = 0; - withWriteLock([&]{ + withWriteLock([&] { _particleProperties = newParticleProperties; if (!_prevEmitterShouldTrailInitialized) { _prevEmitterShouldTrailInitialized = true; @@ -101,13 +109,20 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi withWriteLock([&] { _pulseProperties = entity->getPulseProperties(); + _shapeType = entity->getShapeType(); + QString compoundShapeURL = entity->getCompoundShapeURL(); + if (_compoundShapeURL != compoundShapeURL) { + _compoundShapeURL = compoundShapeURL; + _hasComputedTriangles = false; + fetchGeometryResource(); + } }); _emitting = entity->getIsEmitting(); - bool textureEmpty = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); + bool textureEmpty = resultWithReadLock([&] { return _particleProperties.textures.isEmpty(); }); if (textureEmpty) { if (_networkTexture) { - withWriteLock([&] { + withWriteLock([&] { _networkTexture.reset(); }); } @@ -116,11 +131,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi entity->setVisuallyReady(true); }); } else { - bool textureNeedsUpdate = resultWithReadLock([&]{ + bool textureNeedsUpdate = resultWithReadLock([&] { return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures); }); if (textureNeedsUpdate) { - withWriteLock([&] { + withWriteLock([&] { _networkTexture = DependencyManager::get()->getTexture(_particleProperties.textures); }); } @@ -144,7 +159,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { // Fill in Uniforms structure ParticleUniforms particleUniforms; - withReadLock([&]{ + withReadLock([&] { particleUniforms.radius.start = _particleProperties.radius.range.start; particleUniforms.radius.middle = _particleProperties.radius.gradient.target; particleUniforms.radius.finish = _particleProperties.radius.range.finish; @@ -181,9 +196,32 @@ Item::Bound ParticleEffectEntityRenderer::getBound() { return _bound; } -static const size_t VERTEX_PER_PARTICLE = 4; +// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions +float importanceSample2DDimension(float startDim) { + float dimension = 1.0f; + if (startDim < 1.0f) { + float innerDimensionSquared = startDim * startDim; + float outerDimensionSquared = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2); + float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared); + dimension = std::sqrt(randDimensionSquared); + } + return dimension; +} -ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) { +float importanceSample3DDimension(float startDim) { + float dimension = 1.0f; + if (startDim < 1.0f) { + float innerDimensionCubed = startDim * startDim * startDim; + float outerDimensionCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3); + float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed); + dimension = std::cbrt(randDimensionCubed); + } + return dimension; +} + +ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo) { CpuParticle particle; const auto& accelerationSpread = particleProperties.emission.acceleration.spread; @@ -221,33 +259,130 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa float azimuth; if (azimuthFinish >= azimuthStart) { - azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat(); + azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat(); } else { azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } + // TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too if (emitDimensions == Vectors::ZERO) { // Point emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z; } else { - // Ellipsoid - float radiusScale = 1.0f; - if (emitRadiusStart < 1.0f) { - float randRadius = - emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart); - radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f); + glm::vec3 emitPosition; + switch (shapeType) { + case SHAPE_TYPE_BOX: { + glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions; + + int side = randIntInRange(0, 5); + int axis = side % 3; + float direction = side > 2 ? 1.0f : -1.0f; + + emitDirection[axis] = direction; + emitPosition[axis] = direction * dim[axis]; + axis = (axis + 1) % 3; + emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + axis = (axis + 1) % 3; + emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + break; + } + + case SHAPE_TYPE_CYLINDER_X: + case SHAPE_TYPE_CYLINDER_Y: + case SHAPE_TYPE_CYLINDER_Z: { + glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions; + int axis = shapeType - SHAPE_TYPE_CYLINDER_X; + + emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f); + emitDirection[axis] = 0.0f; + axis = (axis + 1) % 3; + emitPosition[axis] = radii[axis] * glm::cos(azimuth); + emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f; + axis = (axis + 1) % 3; + emitPosition[axis] = radii[axis] * glm::sin(azimuth); + emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f; + emitDirection = glm::normalize(emitDirection); + break; + } + + case SHAPE_TYPE_CIRCLE: { + glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z); + float x = radii.x * glm::cos(azimuth); + float z = radii.y * glm::sin(azimuth); + emitPosition = glm::vec3(x, 0.0f, z); + emitDirection = Vectors::UP; + break; + } + case SHAPE_TYPE_PLANE: { + glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z); + + int side = randIntInRange(0, 3); + int axis = side % 2; + float direction = side > 1 ? 1.0f : -1.0f; + + glm::vec2 pos; + pos[axis] = direction * dim[axis]; + axis = (axis + 1) % 2; + pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + + emitPosition = glm::vec3(pos.x, 0.0f, pos.y); + emitDirection = Vectors::UP; + break; + } + + case SHAPE_TYPE_COMPOUND: { + // if we get here we know that geometryResource is loaded + + size_t index = randFloat() * triangleInfo.totalSamples; + Triangle triangle; + for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) { + size_t numSamples = triangleInfo.samplesPerTriangle[i]; + if (index < numSamples) { + triangle = triangleInfo.triangles[i]; + break; + } + index -= numSamples; + } + + float edgeLength1 = glm::length(triangle.v1 - triangle.v0); + float edgeLength2 = glm::length(triangle.v2 - triangle.v1); + float edgeLength3 = glm::length(triangle.v0 - triangle.v2); + + float perimeter = edgeLength1 + edgeLength2 + edgeLength3; + float fraction1 = randFloatInRange(0.0f, 1.0f); + float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f); + float fraction2 = fraction1 - edgeLength1 / perimeter; + float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f); + float fraction3 = fraction2 - edgeLength2 / perimeter; + float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f); + + float dim = importanceSample2DDimension(emitRadiusStart); + triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform); + glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f; + glm::vec3 v0 = (dim * (triangle.v0 - center)) + center; + glm::vec3 v1 = (dim * (triangle.v1 - center)) + center; + glm::vec3 v2 = (dim * (triangle.v2 - center)) + center; + + emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1); + emitDirection = triangle.getNormal(); + break; + } + + case SHAPE_TYPE_SPHERE: + case SHAPE_TYPE_ELLIPSOID: + default: { + glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions; + float x = radii.x * glm::cos(elevation) * glm::cos(azimuth); + float y = radii.y * glm::cos(elevation) * glm::sin(azimuth); + float z = radii.z * glm::sin(elevation); + emitPosition = glm::vec3(x, y, z); + emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f, + radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, + radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f)); + break; + } } - glm::vec3 radii = radiusScale * 0.5f * emitDimensions; - float x = radii.x * glm::cos(elevation) * glm::cos(azimuth); - float y = radii.y * glm::cos(elevation) * glm::sin(azimuth); - float z = radii.z * glm::sin(elevation); - glm::vec3 emitPosition = glm::vec3(x, y, z); - emitDirection = glm::normalize(glm::vec3( - radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f, - radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, - radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f - )); particle.relativePosition += emitOrientation * emitPosition; } } @@ -267,20 +402,28 @@ void ParticleEffectEntityRenderer::stepSimulation() { const auto now = usecTimestampNow(); const auto interval = std::min(USECS_PER_SECOND / 60, now - _lastSimulated); _lastSimulated = now; - + particle::Properties particleProperties; - withReadLock([&]{ + ShapeType shapeType; + GeometryResource::Pointer geometryResource; + withReadLock([&] { particleProperties = _particleProperties; + shapeType = _shapeType; + geometryResource = _geometryResource; }); const auto& modelTransform = getModelTransform(); - if (_emitting && particleProperties.emitting()) { + if (_emitting && particleProperties.emitting() && + (shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) { uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; while (timeRemaining > _timeUntilNextEmit) { + if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) { + computeTriangles(geometryResource->getHFMModel()); + } // emit particle - _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties)); + _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo)); _timeUntilNextEmit = emitInterval; if (emitInterval < timeRemaining) { timeRemaining -= emitInterval; @@ -297,7 +440,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { } const float deltaTime = (float)interval / (float)USECS_PER_SECOND; - // update the particles + // update the particles for (auto& particle : _cpuParticles) { if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) { if (_prevEmitterShouldTrail) { @@ -313,7 +456,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { static GpuParticles gpuParticles; gpuParticles.clear(); gpuParticles.reserve(_cpuParticles.size()); // Reserve space - std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) { + std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) { glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation()); return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed)); }); @@ -356,5 +499,131 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle)); auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle); + static const size_t VERTEX_PER_PARTICLE = 4; batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE); } + +void ParticleEffectEntityRenderer::fetchGeometryResource() { + QUrl hullURL(_compoundShapeURL); + if (hullURL.isEmpty()) { + _geometryResource.reset(); + } else { + _geometryResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + } +} + +// FIXME: this is very similar to Model::calculateTriangleSets +void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) { + PROFILE_RANGE(render, __FUNCTION__); + + int numberOfMeshes = hfmModel.meshes.size(); + + _hasComputedTriangles = true; + _triangleInfo.triangles.clear(); + _triangleInfo.samplesPerTriangle.clear(); + + std::vector areas; + float minArea = FLT_MAX; + AABox bounds; + + for (int i = 0; i < numberOfMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + + const int numberOfParts = mesh.parts.size(); + for (int j = 0; j < numberOfParts; j++) { + const HFMMeshPart& part = mesh.parts.at(j); + + const int INDICES_PER_TRIANGLE = 3; + const int INDICES_PER_QUAD = 4; + const int TRIANGLES_PER_QUAD = 2; + + // tell our triangleSet how many triangles to expect. + int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; + int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; + int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; + _triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles); + areas.reserve(areas.size() + totalTriangles); + + auto meshTransform = hfmModel.offset * mesh.modelTransform; + + if (part.quadIndices.size() > 0) { + int vIndex = 0; + for (int q = 0; q < numberOfQuads; q++) { + int i0 = part.quadIndices[vIndex++]; + int i1 = part.quadIndices[vIndex++]; + int i2 = part.quadIndices[vIndex++]; + int i3 = part.quadIndices[vIndex++]; + + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + + Triangle tri1 = { v0, v1, v3 }; + Triangle tri2 = { v1, v2, v3 }; + _triangleInfo.triangles.push_back(tri1); + _triangleInfo.triangles.push_back(tri2); + + float area1 = tri1.getArea(); + areas.push_back(area1); + if (area1 > EPSILON) { + minArea = std::min(minArea, area1); + } + + float area2 = tri2.getArea(); + areas.push_back(area2); + if (area2 > EPSILON) { + minArea = std::min(minArea, area2); + } + + bounds += v0; + bounds += v1; + bounds += v2; + bounds += v3; + } + } + + if (part.triangleIndices.size() > 0) { + int vIndex = 0; + for (int t = 0; t < numberOfTris; t++) { + int i0 = part.triangleIndices[vIndex++]; + int i1 = part.triangleIndices[vIndex++]; + int i2 = part.triangleIndices[vIndex++]; + + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + + Triangle tri = { v0, v1, v2 }; + _triangleInfo.triangles.push_back(tri); + + float area = tri.getArea(); + areas.push_back(area); + if (area > EPSILON) { + minArea = std::min(minArea, area); + } + + bounds += v0; + bounds += v1; + bounds += v2; + } + } + } + } + + _triangleInfo.totalSamples = 0; + for (auto& area : areas) { + size_t numSamples = area / minArea; + _triangleInfo.samplesPerTriangle.push_back(numSamples); + _triangleInfo.totalSamples += numSamples; + } + + glm::vec3 scale = bounds.getScale(); + _triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter()); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 853d5cac29..d13c966e96 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -81,7 +81,18 @@ private: glm::vec2 spare; }; - static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); + void computeTriangles(const hfm::Model& hfmModel); + bool _hasComputedTriangles{ false }; + struct TriangleInfo { + std::vector triangles; + std::vector samplesPerTriangle; + size_t totalSamples; + glm::mat4 transform; + } _triangleInfo; + + static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo); void stepSimulation(); particle::Properties _particleProperties; @@ -90,11 +101,16 @@ private: CpuParticles _cpuParticles; bool _emitting { false }; uint64_t _timeUntilNextEmit { 0 }; - BufferPointer _particleBuffer{ std::make_shared() }; + BufferPointer _particleBuffer { std::make_shared() }; BufferView _uniformBuffer; quint64 _lastSimulated { 0 }; PulsePropertyGroup _pulseProperties; + ShapeType _shapeType; + QString _compoundShapeURL; + + void fetchGeometryResource(); + GeometryResource::Pointer _geometryResource; NetworkTexturePointer _networkTexture; ScenePointer _scene; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bd4c6e5c71..a532064b6c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1698,10 +1698,7 @@ AACube EntityItem::getQueryAACube(bool& success) const { } bool EntityItem::shouldPuffQueryAACube() const { - bool hasGrabs = _grabsLock.resultWithReadLock([&] { - return _grabs.count() > 0; - }); - return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs; + return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent(); } // TODO: get rid of all users of this function... @@ -1896,7 +1893,8 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) { void EntityItem::setUnscaledDimensions(const glm::vec3& value) { glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION)); - if (getUnscaledDimensions() != newDimensions) { + const float MIN_SCALE_CHANGE_SQUARED = 1.0e-6f; + if (glm::length2(getUnscaledDimensions() - newDimensions) > MIN_SCALE_CHANGE_SQUARED) { withWriteLock([&] { _unscaledDimensions = newDimensions; }); @@ -2086,7 +2084,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask } else { if (getDynamic()) { group = BULLET_COLLISION_GROUP_DYNAMIC; - } else if (isMovingRelativeToParent() || hasActions()) { + } else if (hasActions() || isMovingRelativeToParent()) { group = BULLET_COLLISION_GROUP_KINEMATIC; } else { group = BULLET_COLLISION_GROUP_STATIC; @@ -3009,6 +3007,26 @@ void EntityItem::setPrimitiveMode(PrimitiveMode value) { } } +bool EntityItem::getCauterized() const { + return resultWithReadLock([&] { + return _cauterized; + }); +} + +void EntityItem::setCauterized(bool value) { + bool changed = false; + withWriteLock([&] { + if (_cauterized != value) { + changed = true; + _cauterized = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::getIgnorePickIntersection() const { return resultWithReadLock([&] { return _ignorePickIntersection; @@ -3057,30 +3075,18 @@ bool EntityItem::getCollisionless() const { } uint16_t EntityItem::getCollisionMask() const { - uint16_t result; - withReadLock([&] { - result = _collisionMask; - }); - return result; + return _collisionMask; } bool EntityItem::getDynamic() const { if (SHAPE_TYPE_STATIC_MESH == getShapeType()) { return false; } - bool result; - withReadLock([&] { - result = _dynamic; - }); - return result; + return _dynamic; } bool EntityItem::getLocked() const { - bool result; - withReadLock([&] { - result = _locked; - }); - return result; + return _locked; } void EntityItem::setLocked(bool value) { @@ -3152,7 +3158,6 @@ uint32_t EntityItem::getDirtyFlags() const { return result; } - void EntityItem::markDirtyFlags(uint32_t mask) { withWriteLock([&] { mask &= Simulation::DIRTY_FLAGS; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 01ed949a0c..29a1a8d73c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -303,6 +303,9 @@ public: bool getCanCastShadow() const; void setCanCastShadow(bool value); + void setCauterized(bool value); + bool getCauterized() const; + inline bool isVisible() const { return getVisible(); } inline bool isInvisible() const { return !getVisible(); } @@ -448,7 +451,7 @@ public: bool clearActions(EntitySimulationPointer simulation); void setDynamicData(QByteArray dynamicData); const QByteArray getDynamicData() const; - bool hasActions() const { return !_objectActions.empty(); } + bool hasActions() const { return !_objectActions.empty() || !_grabActions.empty(); } QList getActionIDs() const { return _objectActions.keys(); } QVariantMap getActionArguments(const QUuid& actionID) const; void deserializeActions(); @@ -530,9 +533,6 @@ public: static QString _marketplacePublicKey; static void retrieveMarketplacePublicKey(); - void setCauterized(bool value) { _cauterized = value; } - bool getCauterized() const { return _cauterized; } - float getBoundingRadius() const { return _boundingRadius; } void setSpaceIndex(int32_t index); int32_t getSpaceIndex() const { return _spaceIndex; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3efedf02ec..44e317696c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); addShapeType(SHAPE_TYPE_STATIC_MESH); addShapeType(SHAPE_TYPE_ELLIPSOID); + addShapeType(SHAPE_TYPE_CIRCLE); } QHash stringToMaterialMappingModeLookup; @@ -975,7 +976,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * by setting the entityHostType parameter in {@link Entities.addEntity} to "avatar". * Material entities render as non-scalable spheres if they don't have their parent set. * @typedef {object} Entities.EntityProperties-Material - * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append ?name to the URL, the + * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append #name to the URL, the * material with that name in the {@link MaterialResource} will be applied to the entity.
* Alternatively, set the property value to "materialData" to use the materialData property * for the {@link MaterialResource} values. @@ -1114,23 +1115,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * default, particles emit along the entity's local z-axis, and azimuthStart and azimuthFinish * are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e., * the particles emit vertically. - * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted. - * @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted; - * range 0.01.0 for the ellipsoid center to the ellipsoid surface, respectively. - * Particles are emitted from the portion of the ellipsoid that lies between emitRadiusStart and the - * ellipsoid's surface. + * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with + * shapeType. + * @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted; + * range 0.01.0 for the center to the surface, respectively. + * Particles are emitted from the portion of the shape that lies between emitRadiusStart and the + * shape's surface. * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted - * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted - * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles start being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle. * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle.. * * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. @@ -1170,7 +1176,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * up in the world. If true, they will point towards the entity's up vector, based on its orientation. * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * - * @property {ShapeType} shapeType="none" - Currently not used. Read-only. + * @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled. + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is + * "compound". * * @example Create a ball of green smoke. * particles = Entities.addEntity({ @@ -1658,6 +1666,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Particles only if (_type == EntityTypes::ParticleEffect) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); @@ -3104,6 +3113,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::ParticleEffect) { APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); _staticPulse.setProperties(properties); @@ -3584,6 +3594,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::ParticleEffect) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ca914731b5..f8c45b792a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { if (entity->isWearable()) { - QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID())); + QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(EntityItemID, entity->getEntityItemID())); } } void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { if (entity->isWearable()) { - QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID())); + QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(EntityItemID, entity->getEntityItemID())); } } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8bf7c92b1f..b3dd78ae92 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "EntitySimulation.h" #include "VariantMapToScriptValue.h" @@ -286,27 +287,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); if (getIsServer()) { - QString certID(entity->getCertificateID()); - EntityItemID entityItemID = entity->getEntityItemID(); - EntityItemID existingEntityItemID; - - { - QWriteLocker locker(&_entityCertificateIDMapLock); - existingEntityItemID = _entityCertificateIDMap.value(certID); - if (!certID.isEmpty()) { - _entityCertificateIDMap.insert(certID, entityItemID); - qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID; - } - } - - // Delete an already-existing entity from the tree if it has the same - // CertificateID as the entity we're trying to add. - if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { - qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" - << existingEntityItemID << ". Deleting existing entity."; - deleteEntity(existingEntityItemID, true); - return; - } + addCertifiedEntityOnServer(entity); } // check to see if we need to simulate this entity.. @@ -764,13 +745,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) theEntity->die(); if (getIsServer()) { - { - QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); - QString certID = theEntity->getCertificateID(); - if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) { - _entityCertificateIDMap.remove(certID); - } - } + removeCertifiedEntityOnServer(theEntity); // set up the deleted entities ID QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock); @@ -1421,11 +1396,123 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) { return false; } +void EntityTree::addCertifiedEntityOnServer(EntityItemPointer entity) { + QString certID(entity->getCertificateID()); + EntityItemID existingEntityItemID; + if (!certID.isEmpty()) { + EntityItemID entityItemID = entity->getEntityItemID(); + QWriteLocker locker(&_entityCertificateIDMapLock); + QList& entityList = _entityCertificateIDMap[certID]; // inserts it if needed. + if (!entityList.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { + existingEntityItemID = entityList.first(); // we will only care about the first, if any, below. + entityList.removeOne(existingEntityItemID); + } + entityList << entityItemID; // adds to list within hash because entityList is a reference. + qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID << "total" << entityList.size() << "entities."; + } + // Delete an already-existing entity from the tree if it has the same + // CertificateID as the entity we're trying to add. + if (!existingEntityItemID.isNull()) { + qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" + << existingEntityItemID << ". Deleting existing entity."; + withWriteLock([&] { + deleteEntity(existingEntityItemID, true); + }); + } +} + +void EntityTree::removeCertifiedEntityOnServer(EntityItemPointer entity) { + QString certID = entity->getCertificateID(); + if (!certID.isEmpty()) { + QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); + QList& entityList = _entityCertificateIDMap[certID]; + entityList.removeOne(entity->getEntityItemID()); + if (entityList.isEmpty()) { + // hmmm, do we to make it be a hash instead of a list, so that this is faster if you stamp out 1000 of a domainUnlimited? + _entityCertificateIDMap.remove(certID); + } + } +} + +void EntityTree::startDynamicDomainVerificationOnServer(float minimumAgeToRemove) { + QReadLocker locker(&_entityCertificateIDMapLock); + QHashIterator> i(_entityCertificateIDMap); + qCDebug(entities) << _entityCertificateIDMap.size() << "certificates present."; + while (i.hasNext()) { + i.next(); + const auto& certificateID = i.key(); + const auto& entityIDs = i.value(); + if (entityIDs.isEmpty()) { + continue; + } + + // Examine each cert: + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); + QJsonObject request; + request["certificate_id"] = certificateID; + networkRequest.setUrl(requestURL); + + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, this, [this, entityIDs, networkReply, minimumAgeToRemove, certificateID] { + + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + bool failure = networkReply->error() != QNetworkReply::NoError; + auto failureReason = networkReply->error(); + networkReply->deleteLater(); + if (failure) { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << failureReason + << "; NOT deleting cert" << certificateID << "More info:" << jsonObject; + return; + } + QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); + if (jsonObject["domain_id"].toString() == thisDomainID) { + // Entity belongs here. Nothing to do. + return; + } + // Entity does not belong here: + QList retained; + for (int i = 0; i < entityIDs.size(); i++) { + EntityItemID entityID = entityIDs.at(i); + EntityItemPointer entity = findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID; + continue; + } + if (entity->getAge() <= minimumAgeToRemove) { + qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; + retained << entityID; + continue; + } + qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; + withWriteLock([&] { + deleteEntity(entityID, true); + }); + } + { + QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); + if (retained.isEmpty()) { + qCDebug(entities) << "Removed" << certificateID; + _entityCertificateIDMap.remove(certificateID); + } else { + qCDebug(entities) << "Retained" << retained.size() << "young entities for" << certificateID; + _entityCertificateIDMap[certificateID] = retained; + } + } + }); + } +} + void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) { QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this); - connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) { - QReadLocker locker(&_entityCertificateIDMapLock); - EntityItemID id = _entityCertificateIDMap.value(certID); + connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const EntityItemID& id) { if (entityItemID == id && _challengeOwnershipTimeoutTimer) { _challengeOwnershipTimeoutTimer->stop(); _challengeOwnershipTimeoutTimer->deleteLater(); @@ -1445,26 +1532,21 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->start(5000); } -QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) { +QByteArray EntityTree::computeNonce(const EntityItemID& entityID, const QString ownerKey) { QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-" QByteArray nonceBytes = nonce.toByteArray(); - QWriteLocker locker(&_certNonceMapLock); - _certNonceMap.insert(certID, QPair(nonce, ownerKey)); + QWriteLocker locker(&_entityNonceMapLock); + _entityNonceMap.insert(entityID, QPair(nonce, ownerKey)); return nonceBytes; } -bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id) { - { - QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); - id = _entityCertificateIDMap.value(certID); - } - +bool EntityTree::verifyNonce(const EntityItemID& entityID, const QString& nonce) { QString actualNonce, key; { - QWriteLocker locker(&_certNonceMapLock); - QPair sent = _certNonceMap.take(certID); + QWriteLocker locker(&_entityNonceMapLock); + QPair sent = _entityNonceMap.take(entityID); actualNonce = sent.first.toString(); key = sent.second; } @@ -1474,9 +1556,9 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); if (verificationSuccess) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; + qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce << + qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "failed. Actual nonce:" << actualNonce << "\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key; } @@ -1484,42 +1566,42 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity } void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int nodeToChallengeByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); message.readPrimitive(&nodeToChallengeByteArraySize); - QByteArray certID(message.read(certIDByteArraySize)); + QByteArray id(message.read(idByteArraySize)); QByteArray text(message.read(textByteArraySize)); QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize)); - sendChallengeOwnershipRequestPacket(certID, text, nodeToChallenge, sourceNode); + sendChallengeOwnershipRequestPacket(id, text, nodeToChallenge, sourceNode); } void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { auto nodeList = DependencyManager::get(); - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int challengingNodeUUIDByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); message.readPrimitive(&challengingNodeUUIDByteArraySize); - QByteArray certID(message.read(certIDByteArraySize)); + QByteArray id(message.read(idByteArraySize)); QByteArray text(message.read(textByteArraySize)); QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize)); auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDByteArraySize + text.length() + 2 * sizeof(int), + idByteArraySize + text.length() + 2 * sizeof(int), true); - challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipReplyPacket->writePrimitive(idByteArraySize); challengeOwnershipReplyPacket->writePrimitive(text.length()); - challengeOwnershipReplyPacket->write(certID); + challengeOwnershipReplyPacket->write(id); challengeOwnershipReplyPacket->write(text); nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode))); @@ -1529,7 +1611,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri // 1. Obtain a nonce auto nodeList = DependencyManager::get(); - QByteArray text = computeNonce(certID, ownerKey); + QByteArray text = computeNonce(entityItemID, ownerKey); if (text == "") { qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity..."; @@ -1539,14 +1621,14 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } else { qCDebug(entities) << "Challenging ownership of Cert ID" << certID; // 2. Send the nonce to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); + QByteArray idByteArray = entityItemID.toByteArray(); + int idByteArraySize = idByteArray.size(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + text.length() + 2 * sizeof(int), + idByteArraySize + text.length() + 2 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(text.length()); - challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(idByteArray); challengeOwnershipPacket->write(text); nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); @@ -1560,24 +1642,24 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } } -void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { +void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { auto nodeList = DependencyManager::get(); // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants // to make sure belongs to Avatar B. QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); - int certIDByteArraySize = certID.length(); + int idByteArraySize = id.length(); int TextByteArraySize = text.length(); int senderNodeUUIDSize = senderNodeUUID.length(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), + idByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(TextByteArraySize); challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); - challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(id); challengeOwnershipPacket->write(text); challengeOwnershipPacket->write(senderNodeUUID); @@ -1636,22 +1718,21 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); - QString certID(message.read(certIDByteArraySize)); + EntityItemID id(message.read(idByteArraySize)); QString text(message.read(textByteArraySize)); - emit killChallengeOwnershipTimeoutTimer(certID); + emit killChallengeOwnershipTimeoutTimer(id); - EntityItemID id; - if (!verifyNonce(certID, text, id)) { - if (!id.isNull()) { + if (!verifyNonce(id, text)) { + withWriteLock([&] { deleteEntity(id, true); - } + }); } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index e627a07d13..c80517c82b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -157,11 +157,6 @@ public: return _recentlyDeletedEntityItemIDs; } - QHash getEntityCertificateIDMap() const { - QReadLocker locker(&_entityCertificateIDMapLock); - return _entityCertificateIDMap; - } - void forgetEntitiesDeletedBefore(quint64 sinceTime); int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); @@ -252,8 +247,8 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; - QByteArray computeNonce(const QString& certID, const QString ownerKey); - bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id); + QByteArray computeNonce(const EntityItemID& entityID, const QString ownerKey); + bool verifyNonce(const EntityItemID& entityID, const QString& nonce); QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); } void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } @@ -279,6 +274,7 @@ public: void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, bool force, bool tellServer); + void startDynamicDomainVerificationOnServer(float minimumAgeToRemove); signals: void deletingEntity(const EntityItemID& entityID); @@ -290,7 +286,7 @@ signals: void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID); void clearingEntities(); - void killChallengeOwnershipTimeoutTimer(const QString& certID); + void killChallengeOwnershipTimeoutTimer(const EntityItemID& certID); protected: @@ -327,10 +323,10 @@ protected: QHash _entityMap; mutable QReadWriteLock _entityCertificateIDMapLock; - QHash _entityCertificateIDMap; + QHash> _entityCertificateIDMap; - mutable QReadWriteLock _certNonceMapLock; - QHash> _certNonceMap; + mutable QReadWriteLock _entityNonceMapLock; + QHash> _entityNonceMap; EntitySimulationPointer _simulation; @@ -377,8 +373,10 @@ protected: Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); private: + void addCertifiedEntityOnServer(EntityItemPointer entity); + void removeCertifiedEntityOnServer(EntityItemPointer entity); void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); - void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); + void sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); std::shared_ptr _myAvatar{ nullptr }; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index aab98adb52..60eaafc0dd 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -148,9 +148,13 @@ bool EntityTreeElement::checkFilterSettings(const EntityItemPointer& entity, Pic (!searchFilter.doesPickLocalEntities() && hostType == entity::HostType::LOCAL)) { return false; } - // We only check the collidable filters for non-local entities, because local entities are always collisionless - bool collidable = !entity->getCollisionless() && (entity->getShapeType() != SHAPE_TYPE_NONE); + // We only check the collidable filters for non-local entities, because local entities are always collisionless, + // but picks always include COLLIDABLE (see PickScriptingInterface::getPickFilter()), so if we were to respect + // the getCollisionless() property of Local entities then we would *never* intersect them in a pick. + // An unfortunate side effect of the following code is that Local entities are intersected even if the + // pick explicitly requested only COLLIDABLE entities (but, again, Local entities are always collisionless). if (hostType != entity::HostType::LOCAL) { + bool collidable = !entity->getCollisionless() && (entity->getShapeType() != SHAPE_TYPE_NONE); if ((collidable && !searchFilter.doesPickCollidable()) || (!collidable && !searchFilter.doesPickNonCollidable())) { return false; } diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 86ca6065bb..098183299f 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem { QVector getLinePoints() const; - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } - // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 08468617ba..d532fefe7e 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -175,7 +175,7 @@ protected: QString _textures; - ShapeType _shapeType = SHAPE_TYPE_NONE; + ShapeType _shapeType { SHAPE_TYPE_NONE }; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index b916ecc3de..12119f1466 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); withReadLock([&] { @@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); withWriteLock([&] { @@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); withWriteLock([&] { @@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); @@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); withReadLock([&] { @@ -718,11 +723,42 @@ void ParticleEffectEntityItem::debugDump() const { } void ParticleEffectEntityItem::setShapeType(ShapeType type) { + switch (type) { + case SHAPE_TYPE_NONE: + case SHAPE_TYPE_CAPSULE_X: + case SHAPE_TYPE_CAPSULE_Y: + case SHAPE_TYPE_CAPSULE_Z: + case SHAPE_TYPE_HULL: + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_SIMPLE_COMPOUND: + case SHAPE_TYPE_STATIC_MESH: + // these types are unsupported for ParticleEffectEntity + type = particle::DEFAULT_SHAPE_TYPE; + break; + default: + break; + } + withWriteLock([&] { - if (type != _shapeType) { - _shapeType = type; - _flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - } + _shapeType = type; + }); +} + +ShapeType ParticleEffectEntityItem::getShapeType() const { + return resultWithReadLock([&] { + return _shapeType; + }); +} + +void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) { + withWriteLock([&] { + _compoundShapeURL = compoundShapeURL; + }); +} + +QString ParticleEffectEntityItem::getCompoundShapeURL() const { + return resultWithReadLock([&] { + return _compoundShapeURL; }); } diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 0755d7868b..52f229201e 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -79,6 +79,7 @@ namespace particle { static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; static const bool DEFAULT_ROTATE_WITH_ENTITY = false; + static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID; template struct Range { @@ -255,7 +256,10 @@ public: float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; } void setShapeType(ShapeType type) override; - virtual ShapeType getShapeType() const override { return _shapeType; } + virtual ShapeType getShapeType() const override; + + QString getCompoundShapeURL() const; + virtual void setCompoundShapeURL(const QString& url); virtual void debugDump() const override; @@ -349,7 +353,8 @@ protected: PulsePropertyGroup _pulseProperties; bool _isEmitting { true }; - ShapeType _shapeType { SHAPE_TYPE_NONE }; + ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE }; + QString _compoundShapeURL { "" }; }; #endif // hifi_ParticleEffectEntityItem_h diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 363a7f39d1..fc590e06a4 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -112,7 +112,7 @@ protected: //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. - ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; + ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 98b18869fc..0771d9ad54 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -13,7 +13,6 @@ #include #include -#include #include @@ -353,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const { Extents meshExtents = hfmModel.getMeshExtents(); glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum; - glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint()); + glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint()); glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal); glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset); @@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { if (hullURL.isEmpty()) { _shapeResource.reset(); } else { - QUrlQuery queryArgs(hullURL); - queryArgs.addQueryItem("collision-hull", ""); - hullURL.setQuery(queryArgs); _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index df6ce50fd6..69e3227135 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -133,7 +133,7 @@ protected: KeyLightPropertyGroup _keyLightProperties; AmbientLightPropertyGroup _ambientLightProperties; - ShapeType _shapeType = DEFAULT_SHAPE_TYPE; + ShapeType _shapeType { DEFAULT_SHAPE_TYPE }; QString _compoundShapeURL; // The following 3 values are the defaults for zone creation diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 8da289806f..f8339ddd31 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -97,7 +97,7 @@ QString processID(const QString& id) { return id.mid(id.lastIndexOf(':') + 1); } -QString getName(const QVariantList& properties) { +QString getModelName(const QVariantList& properties) { QString name; if (properties.size() == 3) { name = properties.at(1).toString(); @@ -108,6 +108,17 @@ QString getName(const QVariantList& properties) { return name; } +QString getMaterialName(const QVariantList& properties) { + QString name; + if (properties.size() == 1 || properties.at(1).toString().isEmpty()) { + name = properties.at(0).toString(); + name = processID(name.left(name.indexOf(QChar('\0')))); + } else { + name = processID(properties.at(1).toString()); + } + return name; +} + QString getID(const QVariantList& properties, int index = 0) { return processID(properties.at(index).toString()); } @@ -300,8 +311,6 @@ QString getString(const QVariant& value) { return list.isEmpty() ? value.toString() : list.at(0).toString(); } -typedef std::vector ShapeVertices; - class AnimationCurve { public: QVector values; @@ -510,7 +519,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const blendshapes.append(extracted); } } else if (object.name == "Model") { - QString name = getName(object.properties); + QString name = getModelName(object.properties); QString id = getID(object.properties); modelIDsToNames.insert(id, name); @@ -829,7 +838,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } else if (object.name == "Material") { HFMMaterial material; MaterialParam materialParam; - material.name = (object.properties.at(1).toString()); + material.name = getMaterialName(object.properties); foreach (const FBXNode& subobject, object.children) { bool properties = false; @@ -1164,8 +1173,14 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const counter++; } } - _connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); - _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + if (_connectionParentMap.value(getID(connection.properties, 1)) == "0") { + // don't assign the new parent + qCDebug(modelformat) << "root node " << getID(connection.properties, 1) << " has discarded parent " << getID(connection.properties, 2); + _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else { + _connectionParentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); + _connectionChildMap.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } } } } @@ -1346,8 +1361,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } // NOTE: shapeVertices are in joint-frame - std::vector shapeVertices; - shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); + hfmModel.shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); hfmModel.bindExtents.reset(); hfmModel.meshExtents.reset(); @@ -1521,7 +1535,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const HFMJoint& joint = hfmModel.joints[jointIndex]; glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; - ShapeVertices& points = shapeVertices.at(jointIndex); + ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); for (int j = 0; j < cluster.indices.size(); j++) { int oldIndex = cluster.indices.at(j); @@ -1595,7 +1609,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const // transform cluster vertices to joint-frame and save for later glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; - ShapeVertices& points = shapeVertices.at(jointIndex); + ShapeVertices& points = hfmModel.shapeVertices.at(jointIndex); foreach (const glm::vec3& vertex, extracted.mesh.vertices) { const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); points.push_back(extractTranslation(vertexTransform)); @@ -1615,54 +1629,6 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const meshIDsToMeshIndices.insert(it.key(), meshIndex); } - const float INV_SQRT_3 = 0.57735026918f; - ShapeVertices cardinalDirections = { - Vectors::UNIT_X, - Vectors::UNIT_Y, - 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) - }; - - // now that all joints have been scanned compute a k-Dop bounding volume of mesh - for (int i = 0; i < hfmModel.joints.size(); ++i) { - HFMJoint& joint = hfmModel.joints[i]; - - // NOTE: points are in joint-frame - ShapeVertices& points = shapeVertices.at(i); - if (points.size() > 0) { - // compute average point - glm::vec3 avgPoint = glm::vec3(0.0f); - for (uint32_t j = 0; j < points.size(); ++j) { - 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) { - float maxDot = -FLT_MAX; - float minDot = FLT_MIN; - for (uint32_t k = 0; k < points.size(); ++k) { - float kDot = glm::dot(cardinalDirections[j], points[k] - avgPoint); - if (kDot > maxDot) { - maxDot = kDot; - } - if (kDot < minDot) { - minDot = kDot; - } - } - 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); - } - } - // attempt to map any meshes to a named model for (QHash::const_iterator m = meshIDsToMeshIndices.constBegin(); m != meshIDsToMeshIndices.constEnd(); m++) { diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index fade0fa5bc..2b13bf3078 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -32,6 +32,7 @@ static const QString JOINT_FIELD = "joint"; static const QString BLENDSHAPE_FIELD = "bs"; static const QString SCRIPT_FIELD = "script"; static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; +static const QString MATERIAL_MAPPING_FIELD = "materialMap"; class FSTReader { public: diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index b8d4e53b65..1699722215 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -711,19 +711,19 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); } else { - if (node.defined["rotation"] && node.rotation.size() == 4) { - //quat(x,y,z,w) to quat(w,x,y,z) - glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - tmat = glm::mat4_cast(rotquat) * tmat; - } - if (node.defined["scale"] && node.scale.size() == 3) { glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); glm::mat4 s = glm::mat4(1.0); s = glm::scale(s, scale); tmat = s * tmat; } - + + if (node.defined["rotation"] && node.rotation.size() == 4) { + //quat(x,y,z,w) to quat(w,x,y,z) + glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + tmat = glm::mat4_cast(rotquat) * tmat; + } + if (node.defined["translation"] && node.translation.size() == 3) { glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); glm::mat4 t = glm::mat4(1.0); @@ -734,15 +734,54 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { return tmat; } +void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { + for (auto &skin : _file.skins) { + GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; + GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView]; + GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer]; + int accBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0; + QVector matrices; + addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + accBoffset, + indicesAccessor.count, + matrices, + indicesAccessor.type, + indicesAccessor.componentType); + inverseBindMatrixValues.push_back(matrices.toStdVector()); + } +} + +void GLTFSerializer::getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result) { + int startingIndex = 0; + int finalIndex = (int)children.size(); + if (stride == -1) { + startingIndex = (int)children.size() - 1; + finalIndex = -1; + } + for (int index = startingIndex; index != finalIndex; index += stride) { + int c = children[index]; + result.push_back(c); + std::vector nested = _file.nodes[c].children.toStdVector(); + if (nested.size() != 0) { + std::sort(nested.begin(), nested.end()); + for (int r : nested) { + if (result.end() == std::find(result.begin(), result.end(), r)) { + getNodeQueueByDepthFirstChildren(nested, stride, result); + } + } + } + } +} + + bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { + int numNodes = _file.nodes.size(); //Build dependencies - QVector> nodeDependencies(_file.nodes.size()); + QVector> nodeDependencies(numNodes); int nodecount = 0; - bool hasChildren = false; foreach(auto &node, _file.nodes) { //nodes_transforms.push_back(getModelTransform(node)); - hasChildren |= !node.children.isEmpty(); foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } @@ -764,26 +803,98 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { nodecount++; } - - HFMJoint joint; - joint.isSkeletonJoint = true; - joint.bindTransformFoundInCluster = false; - joint.distanceToParent = 0; - joint.parentIndex = -1; - hfmModel.joints.resize(_file.nodes.size()); - hfmModel.jointIndices["x"] = _file.nodes.size(); - int jointInd = 0; - for (auto& node : _file.nodes) { - int size = node.transforms.size(); - if (hasChildren) { size--; } - joint.preTransform = glm::mat4(1); - for (int i = 0; i < size; i++) { - joint.preTransform = node.transforms[i] * joint.preTransform; - } - joint.name = node.name; - hfmModel.joints[jointInd] = joint; - jointInd++; + + + // initialize order in which nodes will be parsed + std::vector nodeQueue; + nodeQueue.reserve(numNodes); + int rootNode = 0; + int finalNode = numNodes; + if (!_file.scenes[_file.scene].nodes.contains(0)) { + rootNode = numNodes - 1; + finalNode = -1; } + bool rootAtStartOfList = rootNode < finalNode; + int nodeListStride = 1; + if (!rootAtStartOfList) { nodeListStride = -1; } + + QVector initialSceneNodes = _file.scenes[_file.scene].nodes; + std::sort(initialSceneNodes.begin(), initialSceneNodes.end()); + int sceneRootNode = 0; + int sceneFinalNode = initialSceneNodes.size(); + if (!rootAtStartOfList) { + sceneRootNode = initialSceneNodes.size() - 1; + sceneFinalNode = -1; + } + for (int index = sceneRootNode; index != sceneFinalNode; index += nodeListStride) { + int i = initialSceneNodes[index]; + nodeQueue.push_back(i); + std::vector children = _file.nodes[i].children.toStdVector(); + std::sort(children.begin(), children.end()); + getNodeQueueByDepthFirstChildren(children, nodeListStride, nodeQueue); + } + + // Build joints + HFMJoint joint; + joint.distanceToParent = 0; + hfmModel.jointIndices["x"] = numNodes; + hfmModel.hasSkeletonJoints = false; + + for (int nodeIndex : nodeQueue) { + auto& node = _file.nodes[nodeIndex]; + + joint.parentIndex = -1; + if (!_file.scenes[_file.scene].nodes.contains(nodeIndex)) { + joint.parentIndex = std::distance(nodeQueue.begin(), std::find(nodeQueue.begin(), nodeQueue.end(), nodeDependencies[nodeIndex][0])); + } + joint.transform = node.transforms.first(); + joint.translation = extractTranslation(joint.transform); + joint.rotation = glmExtractRotation(joint.transform); + glm::vec3 scale = extractScale(joint.transform); + joint.postTransform = glm::scale(glm::mat4(), scale); + + joint.name = node.name; + joint.isSkeletonJoint = false; + hfmModel.joints.push_back(joint); + } + + + // Build skeleton + std::vector jointInverseBindTransforms; + jointInverseBindTransforms.resize(numNodes); + if (!_file.skins.isEmpty()) { + std::vector> inverseBindValues; + getSkinInverseBindMatrices(inverseBindValues); + + int jointIndex = finalNode; + while (jointIndex != rootNode) { + rootAtStartOfList ? jointIndex-- : jointIndex++; + int jOffset = nodeQueue[jointIndex]; + auto joint = hfmModel.joints[jointIndex]; + + hfmModel.hasSkeletonJoints = true; + for (int s = 0; s < _file.skins.size(); s++) { + auto skin = _file.skins[s]; + joint.isSkeletonJoint = skin.joints.contains(jOffset); + + if (joint.isSkeletonJoint) { + std::vector value = inverseBindValues[s]; + int matrixCount = 16 * skin.joints.indexOf(jOffset); + jointInverseBindTransforms[jointIndex] = + glm::mat4(value[matrixCount], value[matrixCount + 1], value[matrixCount + 2], value[matrixCount + 3], + value[matrixCount + 4], value[matrixCount + 5], value[matrixCount + 6], value[matrixCount + 7], + value[matrixCount + 8], value[matrixCount + 9], value[matrixCount + 10], value[matrixCount + 11], + value[matrixCount + 12], value[matrixCount + 13], value[matrixCount + 14], value[matrixCount + 15]); + } else { + jointInverseBindTransforms[jointIndex] = glm::mat4(); + } + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex])); + hfmModel.bindExtents.addPoint(bindTranslation); + } + hfmModel.joints[jointIndex] = joint; + } + } + //Build materials QVector materialIDs; @@ -803,23 +914,39 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } - - nodecount = 0; // Build meshes - foreach(auto &node, _file.nodes) { + nodecount = 0; + for (int nodeIndex = rootNode; nodeIndex != finalNode; nodeIndex += nodeListStride) { + auto& node = _file.nodes[nodeIndex]; if (node.defined["mesh"]) { qCDebug(modelformat) << "node_transforms" << node.transforms; foreach(auto &primitive, _file.meshes[node.mesh].primitives) { hfmModel.meshes.append(HFMMesh()); HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; - HFMCluster cluster; - cluster.jointIndex = nodecount; - cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mesh.clusters.append(cluster); + if (!hfmModel.hasSkeletonJoints) { + HFMCluster cluster; + cluster.jointIndex = nodecount; + cluster.inverseBindMatrix = glm::mat4(); + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } else { + for (int j = rootNode; j != finalNode; j += nodeListStride) { + HFMCluster cluster; + cluster.jointIndex = j; + cluster.inverseBindMatrix = jointInverseBindTransforms[j]; + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } + } + HFMCluster root; + root.jointIndex = rootNode; + if (root.jointIndex == -1) { + root.jointIndex = 0; + } + root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; + root.inverseBindTransform = Transform(root.inverseBindMatrix); + mesh.clusters.append(root); HFMMeshPart part = HFMMeshPart(); @@ -848,6 +975,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } QList keys = primitive.attributes.values.keys(); + QVector clusterJoints; + QVector clusterWeights; foreach(auto &key, keys) { int accessorIdx = primitive.attributes.values[key]; @@ -949,8 +1078,69 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { for (int n = 0; n < texcoords.size(); n = n + 2) { mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); } + } else if (key == "JOINTS_0") { + QVector joints; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + joints, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < joints.size(); n++) { + clusterJoints.push_back(joints[n]); + } + } else if (key == "WEIGHTS_0") { + QVector weights; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + weights, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < weights.size(); n++) { + clusterWeights.push_back(weights[n]); + } + } + } + + // adapted from FBXSerializer.cpp + if (hfmModel.hasSkeletonJoints) { + int numClusterIndices = clusterJoints.size(); + const int WEIGHTS_PER_VERTEX = 4; + const float ALMOST_HALF = 0.499f; + int numVertices = mesh.vertices.size(); + mesh.clusterIndices.fill(mesh.clusters.size() - 1, numClusterIndices); + mesh.clusterWeights.fill(0, numClusterIndices); + + for (int c = 0; c < clusterJoints.size(); c++) { + mesh.clusterIndices[c] = _file.skins[node.skin].joints[clusterJoints[c]]; } + // normalize and compress to 16-bits + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += clusterWeights[k]; + } + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); + } + } else { + mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + } } if (primitive.defined["material"]) { @@ -959,8 +1149,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { mesh.parts.push_back(part); // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0) { - for (int i = 0; i < part.triangleIndices.size(); i++) mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); i++) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } } mesh.meshExtents.reset(); foreach(const glm::vec3& vertex, mesh.vertices) { diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index 05dc526f79..d9c477bd99 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -712,6 +712,8 @@ private: hifi::ByteArray _glbBinary; glm::mat4 getModelTransform(const GLTFNode& node); + void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); + void getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result); bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url); bool parseGLTF(const hifi::ByteArray& data); diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index c2e9c08463..416f343a47 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -891,12 +891,14 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V if (!objMaterial.used) { continue; } - hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, - objMaterial.specularColor, - objMaterial.emissiveColor, - objMaterial.shininess, - objMaterial.opacity); - HFMMaterial& hfmMaterial = hfmModel.materials[materialID]; + + HFMMaterial& hfmMaterial = hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, + objMaterial.specularColor, + objMaterial.emissiveColor, + objMaterial.shininess, + objMaterial.opacity); + + hfmMaterial.name = materialID; hfmMaterial.materialID = materialID; hfmMaterial._material = std::make_shared(); graphics::MaterialPointer modelMaterial = hfmMaterial._material; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index fdd06ffa64..caee4ceb2a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -15,7 +15,7 @@ #include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" #include "graphics/Material.h" -#include "image/Image.h" +#include "image/TextureProcessing.h" // #define SCRIPTABLE_MESH_DEBUG 1 diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index f0fc97c5c7..236445bfda 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -154,3 +154,57 @@ QString HFMModel::getModelNameOfMesh(int meshIndex) const { } return QString(); } + +void HFMModel::computeKdops() { + const float INV_SQRT_3 = 0.57735026918f; + ShapeVertices cardinalDirections = { + Vectors::UNIT_X, + Vectors::UNIT_Y, + 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) + }; + if (joints.size() != (int)shapeVertices.size()) { + return; + } + // now that all joints have been scanned compute a k-Dop bounding volume of mesh + for (int i = 0; i < joints.size(); ++i) { + HFMJoint& joint = joints[i]; + + // NOTE: points are in joint-frame + ShapeVertices& points = shapeVertices.at(i); + glm::quat rotOffset = jointRotationOffsets.contains(i) ? glm::inverse(jointRotationOffsets[i]) : quat(); + if (points.size() > 0) { + // compute average point + glm::vec3 avgPoint = glm::vec3(0.0f); + for (uint32_t j = 0; j < points.size(); ++j) { + points[j] = rotOffset * points[j]; + 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) { + float maxDot = -FLT_MAX; + float minDot = FLT_MIN; + for (uint32_t k = 0; k < points.size(); ++k) { + float kDot = glm::dot(cardinalDirections[j], points[k] - avgPoint); + if (kDot > maxDot) { + maxDot = kDot; + } + if (kDot < minDot) { + minDot = kDot; + } + } + 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); + } + } +} diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 22b089328f..8c60169289 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -53,6 +53,8 @@ using ColorType = glm::vec3; const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; + +using ShapeVertices = std::vector; // The version of the Draco mesh binary data itself. See also: FBX_DRACO_MESH_VERSION in FBX.h static const int DRACO_MESH_VERSION = 2; @@ -251,11 +253,6 @@ public: bool wasCompressed { false }; }; -/**jsdoc - * @typedef {object} FBXAnimationFrame - * @property {Quat[]} rotations - * @property {Vec3[]} translations - */ /// A single animation frame. class AnimationFrame { public: @@ -332,10 +329,12 @@ public: /// given a meshIndex this will return the name of the model that mesh belongs to if known QString getModelNameOfMesh(int meshIndex) const; + void computeKdops(); QList blendshapeChannelNames; QMap jointRotationOffsets; + std::vector shapeVertices; FlowData flowData; }; diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 4db39f2152..0c733ae789 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -3,6 +3,7 @@ setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() target_etc2comp() +target_openexr() if (UNIX AND NOT APPLE) set(THREADS_PREFER_PTHREAD_FLAG ON) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2488b15fcd..df5ed15867 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -1,1467 +1,78 @@ -// -// Image.cpp -// image/src/image -// -// Created by Clement Brisset on 4/5/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - #include "Image.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "TGAReader.h" - #include "ImageLogging.h" +#include "TextureProcessing.h" -using namespace gpu; - -#define CPU_MIPMAPS 1 #include -#undef _CRT_SECURE_NO_WARNINGS -#include -#include +using namespace image; -static const glm::uvec2 SPARSE_PAGE_SIZE(128); -static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); -static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); -bool DEV_DECIMATE_TEXTURES = false; -std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; -std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; +Image Image::getScaled(glm::uvec2 dstSize, AspectRatioMode ratioMode, TransformationMode transformMode) const { + if ((Image::Format)_data.format() == Image::Format_PACKED_FLOAT) { + // Start by converting to full float + glm::vec4* floatPixels = new glm::vec4[getWidth()*getHeight()]; + auto unpackFunc = getHDRUnpackingFunction(); + auto floatDataIt = floatPixels; + for (glm::uint32 lineNb = 0; lineNb < getHeight(); lineNb++) { + const glm::uint32* srcPixelIt = reinterpret_cast(getScanLine((int)lineNb)); + const glm::uint32* srcPixelEnd = srcPixelIt + getWidth(); -// we use a ref here to work around static order initialization -// possibly causing the element not to be constructed yet -static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10; - -uint rectifyDimension(const uint& dimension) { - if (dimension == 0) { - return 0; - } - if (dimension < SPARSE_PAGE_SIZE.x) { - uint newSize = SPARSE_PAGE_SIZE.x; - while (dimension <= newSize / 2) { - newSize /= 2; + while (srcPixelIt < srcPixelEnd) { + *floatDataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++floatDataIt; + } } - return newSize; + + // Perform filtered resize with NVTT + static_assert(sizeof(glm::vec4) == 4 * sizeof(float), "Assuming glm::vec4 holds 4 floats"); + nvtt::Surface surface; + surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, floatPixels); + delete[] floatPixels; + + nvtt::ResizeFilter filter = nvtt::ResizeFilter_Kaiser; + if (transformMode == Qt::TransformationMode::FastTransformation) { + filter = nvtt::ResizeFilter_Box; + } + surface.resize(dstSize.x, dstSize.y, 1, filter); + + // And convert back to original format + QImage resizedImage((int)dstSize.x, (int)dstSize.y, (QImage::Format)Image::Format_PACKED_FLOAT); + + auto packFunc = getHDRPackingFunction(); + auto srcRedIt = reinterpret_cast(surface.channel(0)); + auto srcGreenIt = reinterpret_cast(surface.channel(1)); + auto srcBlueIt = reinterpret_cast(surface.channel(2)); + for (glm::uint32 lineNb = 0; lineNb < dstSize.y; lineNb++) { + glm::uint32* dstPixelIt = reinterpret_cast(resizedImage.scanLine((int)lineNb)); + glm::uint32* dstPixelEnd = dstPixelIt + dstSize.x; + + while (dstPixelIt < dstPixelEnd) { + *dstPixelIt = packFunc(glm::vec3(*srcRedIt, *srcGreenIt, *srcBlueIt)); + ++srcRedIt; + ++srcGreenIt; + ++srcBlueIt; + ++dstPixelIt; + } + } + return resizedImage; } else { - uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1); - return pages * SPARSE_PAGE_SIZE.x; + return _data.scaled(fromGlm(dstSize), ratioMode, transformMode); } } -glm::uvec2 rectifySize(const glm::uvec2& size) { - return { rectifyDimension(size.x), rectifyDimension(size.y) }; +Image Image::getConvertedToFormat(Format newFormat) const { + assert(getFormat() != Format_PACKED_FLOAT); + return _data.convertToFormat((QImage::Format)newFormat); } - -namespace image { - -const QStringList getSupportedFormats() { - auto formats = QImageReader::supportedImageFormats(); - QStringList stringFormats; - std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), - [](QByteArray& format) -> QString { return format; }); - return stringFormats; +void Image::invertPixels() { + _data.invertPixels(QImage::InvertRgba); } - -// On GLES, we don't use HDR skyboxes -QImage::Format hdrFormatForTarget(BackendTarget target) { - if (target == BackendTarget::GLES32) { - return QImage::Format_RGB32; - } - return QImage::Format_RGB30; +Image Image::getSubImage(QRect rect) const { + return _data.copy(rect); } -TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { - switch (type) { - case ALBEDO_TEXTURE: - return image::TextureUsage::createAlbedoTextureFromImage; - case EMISSIVE_TEXTURE: - return image::TextureUsage::createEmissiveTextureFromImage; - case LIGHTMAP_TEXTURE: - return image::TextureUsage::createLightmapTextureFromImage; - case CUBE_TEXTURE: - if (options.value("generateIrradiance", true).toBool()) { - return image::TextureUsage::createCubeTextureFromImage; - } else { - return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance; - } - case BUMP_TEXTURE: - return image::TextureUsage::createNormalTextureFromBumpImage; - case NORMAL_TEXTURE: - return image::TextureUsage::createNormalTextureFromNormalImage; - case ROUGHNESS_TEXTURE: - return image::TextureUsage::createRoughnessTextureFromImage; - case GLOSS_TEXTURE: - return image::TextureUsage::createRoughnessTextureFromGlossImage; - case SPECULAR_TEXTURE: - return image::TextureUsage::createMetallicTextureFromImage; - case STRICT_TEXTURE: - return image::TextureUsage::createStrict2DTextureFromImage; - - case DEFAULT_TEXTURE: - default: - return image::TextureUsage::create2DTextureFromImage; - } +Image Image::getMirrored(bool horizontal, bool vertical) const { + return _data.mirrored(horizontal, vertical); } - -gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -static float denormalize(float value, const float minValue) { - return value < minValue ? 0.0f : value; -} - -uint32 packR11G11B10F(const glm::vec3& color) { - // Denormalize else unpacking gives high and incorrect values - // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value - static const auto minValue = 6.10e-5f; - static const auto maxValue = 6.50e4f; - glm::vec3 ucolor; - ucolor.r = denormalize(color.r, minValue); - ucolor.g = denormalize(color.g, minValue); - ucolor.b = denormalize(color.b, minValue); - ucolor.r = std::min(ucolor.r, maxValue); - ucolor.g = std::min(ucolor.g, maxValue); - ucolor.b = std::min(ucolor.b, maxValue); - return glm::packF2x11_1x10(ucolor); -} - -QImage processRawImageData(QIODevice& content, const std::string& filename) { - // Help the QImage loader by extracting the image file format from the url filename ext. - // Some tga are not created properly without it. - auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); - if (!content.isReadable()) { - content.open(QIODevice::ReadOnly); - } else { - content.reset(); - } - - if (filenameExtension == "tga") { - QImage image = image::readTGA(content); - if (!image.isNull()) { - return image; - } - content.reset(); - } - - QImageReader imageReader(&content, filenameExtension.c_str()); - - if (imageReader.canRead()) { - return imageReader.read(); - } else { - // Extension could be incorrect, try to detect the format from the content - QImageReader newImageReader; - newImageReader.setDecideFormatFromContent(true); - content.reset(); - newImageReader.setDevice(&content); - - if (newImageReader.canRead()) { - return newImageReader.read(); - } - } - - return QImage(); -} - -void mapToRedChannel(QImage& image, ColorChannel sourceChannel) { - // Change format of image so we know exactly how to process it - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - for (int i = 0; i < image.height(); i++) { - QRgb* pixel = reinterpret_cast(image.scanLine(i)); - // Past end pointer - QRgb* lineEnd = pixel + image.width(); - - // Transfer channel data from source to target - for (; pixel < lineEnd; pixel++) { - int colorValue; - switch (sourceChannel) { - case ColorChannel::RED: - colorValue = qRed(*pixel); - break; - case ColorChannel::GREEN: - colorValue = qGreen(*pixel); - break; - case ColorChannel::BLUE: - colorValue = qBlue(*pixel); - break; - case ColorChannel::ALPHA: - colorValue = qAlpha(*pixel); - break; - default: - colorValue = qRed(*pixel); - break; - } - - // Dump the color in the red channel, ignore the rest - *pixel = qRgba(colorValue, 0, 0, 255); - } - } -} - -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, ColorChannel sourceChannel, - int maxNumPixels, TextureUsage::Type textureType, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - - QImage image = processRawImageData(*content.get(), filename); - // Texture content can take up a lot of memory. Here we release our ownership of that content - // in case it can be released. - content.reset(); - - int imageWidth = image.width(); - int imageHeight = image.height(); - - // Validate that the image loaded - if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) { - QString reason(image.format() == QImage::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)"); - qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason); - return nullptr; - } - - // Validate the image is less than _maxNumPixels, and downscale if necessary - if (imageWidth * imageHeight > maxNumPixels) { - float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight)); - int originalWidth = imageWidth; - int originalHeight = imageHeight; - imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); - imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - qCDebug(imagelogging).nospace() << "Downscaled " << " (" << - QSize(originalWidth, originalHeight) << " to " << - QSize(imageWidth, imageHeight) << ")"; - } - - // Re-map to image with single red channel texture if requested - if (sourceChannel != ColorChannel::NONE) { - mapToRedChannel(image, sourceChannel); - } - - auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, compress, target, abortProcessing); - - return texture; -} - -QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) { - PROFILE_RANGE(resource_parse, "processSourceImage"); - - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - const glm::uvec2 srcImageSize = toGlm(localCopy.size()); - glm::uvec2 targetSize = srcImageSize; - - const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; - while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { - targetSize /= 2; - } - if (targetSize != srcImageSize) { - ++DECIMATED_TEXTURE_COUNT; - } - - if (!cubemap) { - auto rectifiedSize = rectifySize(targetSize); - if (rectifiedSize != targetSize) { - ++RECTIFIED_TEXTURE_COUNT; - targetSize = rectifiedSize; - } - } - - if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) { - targetSize /= 2; - } - - if (targetSize != srcImageSize) { - PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); - qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; - return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - return localCopy; -} - -#if defined(NVTT_API) -struct OutputHandler : public nvtt::OutputHandler { - OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} - - virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { - _size = size; - _miplevel = miplevel; - - _data = static_cast(malloc(size)); - _current = _data; - } - - virtual bool writeData(const void* data, int size) override { - assert(_current + size <= _data + _size); - memcpy(_current, data, size); - _current += size; - return true; - } - - virtual void endImage() override { - if (_face >= 0) { - _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); - } else { - _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); - } - free(_data); - _data = nullptr; - } - - gpu::Byte* _data{ nullptr }; - gpu::Byte* _current{ nullptr }; - gpu::Texture* _texture{ nullptr }; - int _miplevel = 0; - int _size = 0; - int _face = -1; -}; - -struct PackedFloatOutputHandler : public OutputHandler { - PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { - if (format == gpu::Element::COLOR_RGB9E5) { - _packFunc = glm::packF3x9_E1x5; - } else if (format == gpu::Element::COLOR_R11G11B10) { - _packFunc = packR11G11B10F; - } else { - qCWarning(imagelogging) << "Unknown handler format"; - Q_UNREACHABLE(); - } - } - - virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { - // Divide by 3 because we will compress from 3*floats to 1 uint32 - OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); - } - virtual bool writeData(const void* data, int size) override { - // Expecting to write multiple of floats - if (_packFunc) { - assert((size % sizeof(float)) == 0); - auto floatCount = size / sizeof(float); - const float* floatBegin = (const float*)data; - const float* floatEnd = floatBegin + floatCount; - - while (floatBegin < floatEnd) { - _pixel[_coordIndex] = *floatBegin; - floatBegin++; - _coordIndex++; - if (_coordIndex == 3) { - uint32 packedRGB = _packFunc(_pixel); - _coordIndex = 0; - OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); - } - } - return true; - } - return false; - } - - std::function _packFunc; - glm::vec3 _pixel; - int _coordIndex{ 0 }; -}; - -struct MyErrorHandler : public nvtt::ErrorHandler { - virtual void error(nvtt::Error e) override { - qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); - } -}; - -class SequentialTaskDispatcher : public nvtt::TaskDispatcher { -public: - SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; - - const std::atomic& _abortProcessing; - - virtual void dispatch(nvtt::Task* task, void* context, int count) override { - for (int i = 0; i < count; i++) { - if (!_abortProcessing.load()) { - task(context, i); - } else { - break; - } - } - } -}; - -void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - assert(localCopy.format() == hdrFormatForTarget(target)); - - const int width = localCopy.width(), height = localCopy.height(); - std::vector data; - std::vector::iterator dataIt; - auto mipFormat = texture->getStoredMipFormat(); - std::function unpackFunc; - - nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); - - // TODO: gles: generate ETC mips instead? - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) { - compressionOptions.setFormat(nvtt::Format_BC6); - } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPixelFormat(32, 32, 32, 0); - } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPixelFormat(32, 32, 32, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { - unpackFunc = glm::unpackF3x9_E1x5; - } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { - unpackFunc = glm::unpackF2x11_1x10; - } else { - qCWarning(imagelogging) << "Unknown HDR encoding format in QImage"; - Q_UNREACHABLE(); - return; - } - - data.resize(width * height); - dataIt = data.begin(); - for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = reinterpret_cast(localCopy.constScanLine(lineNb)); - const uint32* srcPixelEnd = srcPixelIt + width; - - while (srcPixelIt < srcPixelEnd) { - *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); - ++srcPixelIt; - ++dataIt; - } - } - assert(dataIt == data.end()); - - // We're done with the localCopy, free up the memory to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - std::unique_ptr outputHandler; - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - nvtt::Context context; - int mipLevel = 0; - - if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { - // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats - outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); - } else { - outputHandler.reset(new OutputHandler(texture, face)); - } - - outputOptions.setOutputHandler(outputHandler.get()); - - nvtt::Surface surface; - surface.setImage(inputFormat, width, height, 1, &(*data.begin())); - surface.setAlphaMode(alphaMode); - surface.setWrapMode(wrapMode); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - context.setTaskDispatcher(&dispatcher); - - context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); - while (surface.canMakeNextMipmap() && !abortProcessing.load()) { - surface.buildNextMipmap(nvtt::MipmapFilter_Box); - context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); - } -} - -void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != hdrFormatForTarget(target)) { - localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); - } - - const int width = localCopy.width(), height = localCopy.height(); - auto mipFormat = texture->getStoredMipFormat(); - - if (target != BackendTarget::GLES32) { - const void* data = static_cast(localCopy.constBits()); - nvtt::TextureType textureType = nvtt::TextureType_2D; - nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::RoundMode roundMode = nvtt::RoundMode_None; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - - float inputGamma = 2.2f; - float outputGamma = 2.2f; - - nvtt::InputOptions inputOptions; - inputOptions.setTextureLayout(textureType, width, height); - - inputOptions.setMipmapData(data, width, height); - // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap - data = nullptr; - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - inputOptions.setFormat(inputFormat); - inputOptions.setGamma(inputGamma, outputGamma); - inputOptions.setAlphaMode(alphaMode); - inputOptions.setWrapMode(wrapMode); - inputOptions.setRoundMode(roundMode); - - inputOptions.setMipmapGeneration(true); - inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); - - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { - compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { - compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { - compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC7); - } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_R_8) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 0, 0, 0); - } else if (mipFormat == gpu::Element::VEC2NU8_XY) { - inputOptions.setNormalMap(true); - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 8, 0, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - OutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - compressor.setTaskDispatcher(&dispatcher); - compressor.process(inputOptions, compressionOptions, outputOptions); - } else { - int numMips = 1 + (int)log2(std::max(width, height)); - Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; - Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { - etcFormat = Etc::Image::Format::RGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { - etcFormat = Etc::Image::Format::SRGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::RGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::SRGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { - etcFormat = Etc::Image::Format::RGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { - etcFormat = Etc::Image::Format::SRGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { - etcFormat = Etc::Image::Format::R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { - etcFormat = Etc::Image::Format::RG11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_RG11; - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; - const float effort = 1.0f; - const int numEncodeThreads = 4; - int encodingTime; - const float MAX_COLOR = 255.0f; - - std::vector floatData; - floatData.resize(width * height); - for (int y = 0; y < height; y++) { - QRgb *line = (QRgb *)localCopy.scanLine(y); - for (int x = 0; x < width; x++) { - QRgb &pixel = line[x]; - floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; - } - } - - // free up the memory afterward to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - Etc::EncodeMipmaps( - (float *)floatData.data(), width, height, - etcFormat, errorMetric, effort, - numEncodeThreads, numEncodeThreads, - numMips, Etc::FILTER_WRAP_NONE, - mipMaps, &encodingTime - ); - - for (int i = 0; i < numMips; i++) { - if (mipMaps[i].paucEncodingBits.get()) { - if (face >= 0) { - texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } else { - texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } - } - } - - delete[] mipMaps; - } -} - -#endif - -void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { -#if CPU_MIPMAPS - PROFILE_RANGE(resource_parse, "generateMips"); - - if (target == BackendTarget::GLES32) { - generateLDRMips(texture, std::move(image), target, abortProcessing, face); - } else { - if (image.format() == hdrFormatForTarget(target)) { - generateHDRMips(texture, std::move(image), target, abortProcessing, face); - } else { - generateLDRMips(texture, std::move(image), target, abortProcessing, face); - } - } -#else - texture->setAutoGenerateMips(true); -#endif -} - -void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) { - PROFILE_RANGE(resource_parse, "processTextureAlpha"); - validAlpha = false; - alphaAsMask = true; - const uint8 OPAQUE_ALPHA = 255; - const uint8 TRANSPARENT_ALPHA = 0; - - // Figure out if we can use a mask for alpha or not - int numOpaques = 0; - int numTranslucents = 0; - const int NUM_PIXELS = srcImage.width() * srcImage.height(); - const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS)); - const QRgb* data = reinterpret_cast(srcImage.constBits()); - for (int i = 0; i < NUM_PIXELS; ++i) { - auto alpha = qAlpha(data[i]); - if (alpha == OPAQUE_ALPHA) { - numOpaques++; - } else if (alpha != TRANSPARENT_ALPHA) { - if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) { - alphaAsMask = false; - break; - } - } - } - validAlpha = (numOpaques != NUM_PIXELS); -} - -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - bool validAlpha = image.hasAlphaChannel(); - bool alphaAsMask = false; - - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - if (validAlpha) { - processTextureAlpha(image, validAlpha, alphaAsMask); - } - - gpu::TexturePointer theTexture = nullptr; - - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - // GLES does not support GL_BGRA - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - formatMip = formatGPU; - } else { - if (validAlpha) { - // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures - // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; - } - formatMip = formatGPU; - } - } else { - if (target == BackendTarget::GLES32) { - } else { - formatGPU = gpu::Element::COLOR_SRGBA_32; - formatMip = gpu::Element::COLOR_SBGRA_32; - } - } - - if (isStrict) { - theTexture = gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - } else { - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - } - theTexture->setSource(srcImageName); - auto usage = gpu::Texture::Usage::Builder().withColor(); - if (validAlpha) { - usage.withAlpha(); - if (alphaAsMask) { - usage.withAlphaMask(); - } - } - theTexture->setUsage(usage.build()); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -int clampPixelCoordinate(int coordinate, int maxCoordinate) { - return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate)); -} - -const int RGBA_MAX = 255; - -// transform -1 - 1 to 0 - 255 (from sobel value to rgb) -double mapComponent(double sobelValue) { - const double factor = RGBA_MAX / 2.0; - return (sobelValue + 1.0) * factor; -} - -QImage processBumpMap(QImage&& image) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - if (localCopy.format() != QImage::Format_Grayscale8) { - localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8); - } - - // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps - // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image - const double pStrength = 2.0; - int width = localCopy.width(); - int height = localCopy.height(); - - QImage result(width, height, QImage::Format_ARGB32); - - for (int i = 0; i < width; i++) { - const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); - const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1); - - for (int j = 0; j < height; j++) { - const int jNextClamped = clampPixelCoordinate(j + 1, height - 1); - const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); - - // surrounding pixels - const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped); - const QRgb top = localCopy.pixel(iPrevClamped, j); - const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped); - const QRgb right = localCopy.pixel(i, jNextClamped); - const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped); - const QRgb bottom = localCopy.pixel(iNextClamped, j); - const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped); - const QRgb left = localCopy.pixel(i, jPrevClamped); - - // take their gray intensities - // since it's a grayscale image, the value of each component RGB is the same - const double tl = qRed(topLeft); - const double t = qRed(top); - const double tr = qRed(topRight); - const double r = qRed(right); - const double br = qRed(bottomRight); - const double b = qRed(bottom); - const double bl = qRed(bottomLeft); - const double l = qRed(left); - - // apply the sobel filter - const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl); - const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr); - const double dZ = RGBA_MAX / pStrength; - - glm::vec3 v(dX, dY, dZ); - glm::normalize(v); - - // convert to rgb from the value obtained computing the filter - QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0); - result.setPixel(i, j, qRgbValue); - } - } - - return result; -} -gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool isBumpMap, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - if (isBumpMap) { - image = processBumpMap(std::move(image)); - } - - // Make sure the normal map source image is ARGB32 - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - gpu::TexturePointer theTexture = nullptr; - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; - } - } else { - formatGPU = gpu::Element::VEC2NU8_XY; - } - formatMip = formatGPU; - - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool isInvertedPixels, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - if (isInvertedPixels) { - // Gloss turned into Rough - image.invertPixels(QImage::InvertRgba); - } - - gpu::TexturePointer theTexture = nullptr; - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; - } - } else { - formatGPU = gpu::Element::COLOR_R_8; - } - formatMip = formatGPU; - - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -class CubeLayout { -public: - - enum SourceProjection { - FLAT = 0, - EQUIRECTANGULAR, - }; - int _type = FLAT; - int _widthRatio = 1; - int _heightRatio = 1; - - class Face { - public: - int _x = 0; - int _y = 0; - bool _horizontalMirror = false; - bool _verticalMirror = false; - - Face() {} - Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {} - }; - - Face _faceXPos; - Face _faceXNeg; - Face _faceYPos; - Face _faceYNeg; - Face _faceZPos; - Face _faceZNeg; - - CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) : - _type(FLAT), - _widthRatio(wr), - _heightRatio(hr), - _faceXPos(fXP), - _faceXNeg(fXN), - _faceYPos(fYP), - _faceYNeg(fYN), - _faceZPos(fZP), - _faceZNeg(fZN) {} - - CubeLayout(int wr, int hr) : - _type(EQUIRECTANGULAR), - _widthRatio(wr), - _heightRatio(hr) {} - - - static const CubeLayout CUBEMAP_LAYOUTS[]; - static const int NUM_CUBEMAP_LAYOUTS; - - static int findLayout(int width, int height) { - // Find the layout of the cubemap in the 2D image - int foundLayout = -1; - for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) { - if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) { - foundLayout = i; - break; - } - } - return foundLayout; - } - - static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { - QImage image(faceWidth, faceWidth, source.format()); - - glm::vec2 dstInvSize(1.0f / faceWidth); - - struct CubeToXYZ { - gpu::Texture::CubeFace _face; - CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {} - - glm::vec3 xyzFrom(const glm::vec2& uv) { - auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f)); - - switch (_face) { - case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z: - return glm::vec3(-faceDir.x, faceDir.y, faceDir.z); - case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z: - return glm::vec3(faceDir.x, faceDir.y, -faceDir.z); - case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X: - return glm::vec3(faceDir.z, faceDir.y, faceDir.x); - case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X: - return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x); - case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y: - return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y); - case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y: - default: - return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y); - } - } - }; - CubeToXYZ cubeToXYZ(face); - - struct RectToXYZ { - RectToXYZ() {} - - glm::vec2 uvFrom(const glm::vec3& xyz) { - auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z)); - auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y)); - - const float LON_TO_RECT_U = 1.0f / (glm::pi()); - const float LAT_TO_RECT_V = 2.0f / glm::pi(); - return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f); - } - }; - RectToXYZ rectToXYZ; - - int srcFaceHeight = source.height(); - int srcFaceWidth = source.width(); - - glm::vec2 dstCoord; - glm::ivec2 srcPixel; - for (int y = 0; y < faceWidth; ++y) { - QRgb* destScanLineBegin = reinterpret_cast( image.scanLine(y) ); - QRgb* destPixelIterator = destScanLineBegin; - - dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom - for (int x = 0; x < faceWidth; ++x) { - dstCoord.x = (x + 0.5f) * dstInvSize.x; - - auto xyzDir = cubeToXYZ.xyzFrom(dstCoord); - auto srcCoord = rectToXYZ.uvFrom(xyzDir); - - srcPixel.x = floor(srcCoord.x * srcFaceWidth); - // Flip the vertical axis to QImage going top to bottom - srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight); - - if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) { - // We can't directly use the pixel() method because that launches a pixel color conversion to output - // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which - // are not convertible by Qt. The same goes with the setPixel method, by the way. - const QRgb* sourcePixelIterator = reinterpret_cast(source.scanLine(srcPixel.y)); - sourcePixelIterator += srcPixel.x; - *destPixelIterator = *sourcePixelIterator; - - // Keep for debug, this is showing the dir as a color - // glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256); - // unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16); - // *destPixelIterator = val; - } - ++destPixelIterator; - } - } - return image; - } -}; - -const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { - - // Here is the expected layout for the faces in an image with the 2/1 aspect ratio: - // THis is detected as an Equirectangular projection - // WIDTH - // <---------------------------> - // ^ +------+------+------+------+ - // H | | | | | - // E | | | | | - // I | | | | | - // G +------+------+------+------+ - // H | | | | | - // T | | | | | - // | | | | | | - // v +------+------+------+------+ - // - // FaceWidth = width = height / 6 - { 2, 1 }, - - // Here is the expected layout for the faces in an image with the 1/6 aspect ratio: - // - // WIDTH - // <------> - // ^ +------+ - // | | | - // | | +X | - // | | | - // H +------+ - // E | | - // I | -X | - // G | | - // H +------+ - // T | | - // | | +Y | - // | | | - // | +------+ - // | | | - // | | -Y | - // | | | - // H +------+ - // E | | - // I | +Z | - // G | | - // H +------+ - // T | | - // | | -Z | - // | | | - // V +------+ - // - // FaceWidth = width = height / 6 - { 1, 6, - { 0, 0, true, false }, - { 0, 1, true, false }, - { 0, 2, false, true }, - { 0, 3, false, true }, - { 0, 4, true, false }, - { 0, 5, true, false } - }, - - // Here is the expected layout for the faces in an image with the 3/4 aspect ratio: - // - // <-----------WIDTH-----------> - // ^ +------+------+------+------+ - // | | | | | | - // | | | +Y | | | - // | | | | | | - // H +------+------+------+------+ - // E | | | | | - // I | -X | -Z | +X | +Z | - // G | | | | | - // H +------+------+------+------+ - // T | | | | | - // | | | -Y | | | - // | | | | | | - // V +------+------+------+------+ - // - // FaceWidth = width / 4 = height / 3 - { 4, 3, - { 2, 1, true, false }, - { 0, 1, true, false }, - { 1, 0, false, true }, - { 1, 2, false, true }, - { 3, 1, true, false }, - { 1, 1, true, false } - }, - - // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: - // - // <-------WIDTH--------> - // ^ +------+------+------+ - // | | | | | - // | | | +Y | | - // | | | | | - // H +------+------+------+ - // E | | | | - // I | -X | -Z | +X | - // G | | | | - // H +------+------+------+ - // T | | | | - // | | | -Y | | - // | | | | | - // | +------+------+------+ - // | | | | | - // | | | +Z! | | <+Z is upside down! - // | | | | | - // V +------+------+------+ - // - // FaceWidth = width / 3 = height / 4 - { 3, 4, - { 2, 1, true, false }, - { 0, 1, true, false }, - { 1, 0, false, true }, - { 1, 2, false, true }, - { 1, 3, false, true }, - { 1, 1, true, false } - } -}; -const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); - -//#define DEBUG_COLOR_PACKING - -QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format, BackendTarget target) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - QImage hdrImage(localCopy.width(), localCopy.height(), hdrFormatForTarget(target)); - std::function packFunc; -#ifdef DEBUG_COLOR_PACKING - std::function unpackFunc; -#endif - - switch (format.getSemantic()) { - case gpu::R11G11B10: - packFunc = packR11G11B10F; -#ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF2x11_1x10; -#endif - break; - case gpu::RGB9E5: - packFunc = glm::packF3x9_E1x5; -#ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF3x9_E1x5; -#endif - break; - default: - qCWarning(imagelogging) << "Unsupported HDR format"; - Q_UNREACHABLE(); - return localCopy; - } - - localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); - for (auto y = 0; y < localCopy.height(); y++) { - const QRgb* srcLineIt = reinterpret_cast( localCopy.constScanLine(y) ); - const QRgb* srcLineEnd = srcLineIt + localCopy.width(); - uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); - glm::vec3 color; - - while (srcLineIt < srcLineEnd) { - color.r = qRed(*srcLineIt); - color.g = qGreen(*srcLineIt); - color.b = qBlue(*srcLineIt); - // Normalize and apply gamma - color /= 255.0f; - color.r = powf(color.r, 2.2f); - color.g = powf(color.g, 2.2f); - color.b = powf(color.b, 2.2f); - *hdrLineIt = packFunc(color); -#ifdef DEBUG_COLOR_PACKING - glm::vec3 ucolor = unpackFunc(*hdrLineIt); - assert(glm::distance(color, ucolor) <= 5e-2); -#endif - ++srcLineIt; - ++hdrLineIt; - } - } - return hdrImage; -} - -gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool generateIrradiance, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); - - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - int originalWidth = localCopy.width(); - int originalHeight = localCopy.height(); - if ((originalWidth <= 0) && (originalHeight <= 0)) { - return nullptr; - } - - gpu::TexturePointer theTexture = nullptr; - - QImage image = processSourceImage(std::move(localCopy), true, target); - - if (image.format() != hdrFormatForTarget(target)) { - if (target == BackendTarget::GLES32) { - image = image.convertToFormat(QImage::Format_RGB32); - } else { - image = convertToHDRFormat(std::move(image), HDR_FORMAT, target); - } - } - - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; - } - } else { - formatGPU = HDR_FORMAT; - } - - formatMip = formatGPU; - - // Find the layout of the cubemap in the 2D image - // Use the original image size since processSourceImage may have altered the size / aspect ratio - int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); - - if (foundLayout < 0) { - qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); - return nullptr; - } - - std::vector faces; - - // If found, go extract the faces as separate images - auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; - if (layout._type == CubeLayout::FLAT) { - int faceWidth = image.width() / layout._widthRatio; - - faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); - } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { - // THe face width is estimated from the input image - const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; - const int EQUIRECT_MAX_FACE_WIDTH = 2048; - int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); - for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { - QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); - faces.push_back(std::move(faceImage)); - } - } - - // free up the memory afterward to avoid bloating the heap - image = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - // If the 6 faces have been created go on and define the true Texture - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - - // Generate irradiance while we are at it - if (generateIrradiance) { - PROFILE_RANGE(resource_parse, "generateIrradiance"); - gpu::Element irradianceFormat; - // TODO: we could locally compress the irradiance texture on Android, but we don't need to - if (target == BackendTarget::GLES32) { - irradianceFormat = gpu::Element::COLOR_SRGBA_32; - } else { - irradianceFormat = HDR_FORMAT; - } - - auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(irradianceFormat); - for (uint8 face = 0; face < faces.size(); ++face) { - irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); - } - - irradianceTexture->generateIrradiance(target); - - auto irradiance = irradianceTexture->getIrradiance(); - theTexture->overrideIrradiance(irradiance); - } - - for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); - } - } - - return theTexture; -} - -} // namespace image diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 40c31eeeff..bfecf4f2a1 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -1,9 +1,10 @@ +#pragma once // // Image.h -// image/src/image +// image/src/Image // -// Created by Clement Brisset on 4/5/2017. -// Copyright 2017 High Fidelity, Inc. +// Created by Olivier Prat on 29/3/2019. +// Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,80 +13,85 @@ #ifndef hifi_image_Image_h #define hifi_image_Image_h -#include - -#include +#include #include "ColorChannel.h" -class QByteArray; -class QImage; +#include +#include +#include namespace image { -namespace TextureUsage { + class Image { + public: -enum Type { - DEFAULT_TEXTURE, - STRICT_TEXTURE, - ALBEDO_TEXTURE, - NORMAL_TEXTURE, - BUMP_TEXTURE, - SPECULAR_TEXTURE, - METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey - ROUGHNESS_TEXTURE, - GLOSS_TEXTURE, - EMISSIVE_TEXTURE, - CUBE_TEXTURE, - OCCLUSION_TEXTURE, - SCATTERING_TEXTURE = OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE, - UNUSED_TEXTURE -}; + enum Format { + Format_Invalid = QImage::Format_Invalid, + Format_Mono = QImage::Format_Mono, + Format_MonoLSB = QImage::Format_MonoLSB, + Format_Indexed8 = QImage::Format_Indexed8, + Format_RGB32 = QImage::Format_RGB32, + Format_ARGB32 = QImage::Format_ARGB32, + Format_ARGB32_Premultiplied = QImage::Format_ARGB32_Premultiplied, + Format_RGB16 = QImage::Format_RGB16, + Format_ARGB8565_Premultiplied = QImage::Format_ARGB8565_Premultiplied, + Format_RGB666 = QImage::Format_RGB666, + Format_ARGB6666_Premultiplied = QImage::Format_ARGB6666_Premultiplied, + Format_RGB555 = QImage::Format_RGB555, + Format_ARGB8555_Premultiplied = QImage::Format_ARGB8555_Premultiplied, + Format_RGB888 = QImage::Format_RGB888, + Format_RGB444 = QImage::Format_RGB444, + Format_ARGB4444_Premultiplied = QImage::Format_ARGB4444_Premultiplied, + Format_RGBX8888 = QImage::Format_RGBX8888, + Format_RGBA8888 = QImage::Format_RGBA8888, + Format_RGBA8888_Premultiplied = QImage::Format_RGBA8888_Premultiplied, + Format_Grayscale8 = QImage::Format_Grayscale8, + Format_R11G11B10F = QImage::Format_RGB30, + Format_PACKED_FLOAT = Format_R11G11B10F + }; -using TextureLoader = std::function&)>; -TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); + using AspectRatioMode = Qt::AspectRatioMode; + using TransformationMode = Qt::TransformationMode; -gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); + Image() {} + Image(int width, int height, Format format) : _data(width, height, (QImage::Format)format) {} + Image(const QImage& data) : _data(data) {} + void operator=(const QImage& image) { + _data = image; + } -} // namespace TextureUsage + bool isNull() const { return _data.isNull(); } -const QStringList getSupportedFormats(); + Format getFormat() const { return (Format)_data.format(); } + bool hasAlphaChannel() const { return _data.hasAlphaChannel(); } -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, ColorChannel sourceChannel, - int maxNumPixels, TextureUsage::Type textureType, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); + glm::uint32 getWidth() const { return (glm::uint32)_data.width(); } + glm::uint32 getHeight() const { return (glm::uint32)_data.height(); } + glm::uvec2 getSize() const { return toGlm(_data.size()); } + size_t getByteCount() const { return _data.byteCount(); } + + QRgb getPixel(int x, int y) const { return _data.pixel(x, y); } + void setPixel(int x, int y, QRgb value) { + _data.setPixel(x, y, value); + } + + glm::uint8* editScanLine(int y) { return _data.scanLine(y); } + const glm::uint8* getScanLine(int y) const { return _data.scanLine(y); } + const glm::uint8* getBits() const { return _data.constBits(); } + + Image getScaled(glm::uvec2 newSize, AspectRatioMode ratioMode, TransformationMode transformationMode = Qt::SmoothTransformation) const; + Image getConvertedToFormat(Format newFormat) const; + Image getSubImage(QRect rect) const; + Image getMirrored(bool horizontal, bool vertical) const; + + // Inplace transformations + void invertPixels(); + + private: + + QImage _data; + }; } // namespace image diff --git a/libraries/image/src/image/OpenEXRReader.cpp b/libraries/image/src/image/OpenEXRReader.cpp new file mode 100644 index 0000000000..66e304e3fa --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.cpp @@ -0,0 +1,99 @@ +// +// OpenEXRReader.cpp +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OpenEXRReader.h" + +#include "TextureProcessing.h" +#include "ImageLogging.h" + +#include +#include + +#if !defined(Q_OS_ANDROID) + +#include +#include +#include +#include + +class QIODeviceImfStream : public Imf::IStream { +public: + + QIODeviceImfStream(QIODevice& device, const std::string& filename) : + Imf::IStream(filename.c_str()), _device(device) { + } + + bool read(char c[/*n*/], int n) override { + if (_device.read(c, n) <= 0) { + qWarning(imagelogging) << "OpenEXR - in file " << fileName() << " : " << _device.errorString(); + return false; + } + return true; + } + + Imf::Int64 tellg() override { + return _device.pos(); + } + + void seekg(Imf::Int64 pos) override { + _device.seek(pos); + } + + void clear() override { + // Not much to do + } + +private: + + QIODevice& _device; +}; + +#endif + +image::Image image::readOpenEXR(QIODevice& content, const std::string& filename) { +#if !defined(Q_OS_ANDROID) + QIODeviceImfStream device(content, filename); + + if (Imf::isOpenExrFile(device)) { + Imf::RgbaInputFile file(device); + Imath::Box2i viewport = file.dataWindow(); + Imf::Array2D pixels; + int width = viewport.max.x - viewport.min.x + 1; + int height = viewport.max.y - viewport.min.y + 1; + + pixels.resizeErase(height, width); + + file.setFrameBuffer(&pixels[0][0] - viewport.min.x - viewport.min.y * width, 1, width); + file.readPixels(viewport.min.y, viewport.max.y); + + Image image{ width, height, Image::Format_PACKED_FLOAT }; + auto packHDRPixel = getHDRPackingFunction(); + + for (int y = 0; y < height; y++) { + const auto srcScanline = pixels[y]; + gpu::uint32* dstScanline = (gpu::uint32*) image.editScanLine(y); + + for (int x = 0; x < width; x++) { + const auto& srcPixel = srcScanline[x]; + auto& dstPixel = dstScanline[x]; + glm::vec3 floatPixel{ srcPixel.r, srcPixel.g, srcPixel.b }; + + dstPixel = packHDRPixel(floatPixel); + } + } + return image; + } else { + qWarning(imagelogging) << "OpenEXR - File " << filename.c_str() << " doesn't have the proper format"; + } +#endif + + return QImage(); +} diff --git a/libraries/image/src/image/OpenEXRReader.h b/libraries/image/src/image/OpenEXRReader.h new file mode 100644 index 0000000000..232ba370e0 --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.h @@ -0,0 +1,24 @@ +// +// OpenEXRReader.h +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_image_OpenEXRReader_h +#define hifi_image_OpenEXRReader_h + +#include "Image.h" + +namespace image { + + // TODO Move this into a plugin that QImageReader can use + Image readOpenEXR(QIODevice& contents, const std::string& filename); + +} + +#endif // hifi_image_OpenEXRReader_h diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index 897d565eba..7948806f26 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -16,7 +16,7 @@ #include #include -QImage image::readTGA(QIODevice& content) { +image::Image image::readTGA(QIODevice& content) { enum class TGAImageType : uint8_t { NoImageData = 0, UncompressedColorMapped = 1, diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h index 8019be7f0b..fc7bc1a101 100644 --- a/libraries/image/src/image/TGAReader.h +++ b/libraries/image/src/image/TGAReader.h @@ -12,12 +12,11 @@ #ifndef hifi_image_TGAReader_h #define hifi_image_TGAReader_h -#include +#include "Image.h" namespace image { -// TODO Move this into a plugin that QImageReader can use -QImage readTGA(QIODevice& contents); + Image readTGA(QIODevice& contents); } diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp new file mode 100644 index 0000000000..037229ace5 --- /dev/null +++ b/libraries/image/src/image/TextureProcessing.cpp @@ -0,0 +1,1525 @@ +// +// TextureProcessing.cpp +// image/src/TextureProcessing +// +// Created by Clement Brisset on 4/5/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TextureProcessing.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "TGAReader.h" +#if !defined(Q_OS_ANDROID) +#include "OpenEXRReader.h" +#endif +#include "ImageLogging.h" + +using namespace gpu; + +#define CPU_MIPMAPS 1 +#include + +#undef _CRT_SECURE_NO_WARNINGS +#include +#include + +static const glm::uvec2 SPARSE_PAGE_SIZE(128); +static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); +static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); +bool DEV_DECIMATE_TEXTURES = false; +std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; +std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; + +// we use a ref here to work around static order initialization +// possibly causing the element not to be constructed yet +static const auto& GPU_CUBEMAP_DEFAULT_FORMAT = gpu::Element::COLOR_SRGBA_32; +static const auto& GPU_CUBEMAP_HDR_FORMAT = gpu::Element::COLOR_R11G11B10; + +namespace image { + +uint rectifyDimension(const uint& dimension) { + if (dimension == 0) { + return 0; + } + if (dimension < SPARSE_PAGE_SIZE.x) { + uint newSize = SPARSE_PAGE_SIZE.x; + while (dimension <= newSize / 2) { + newSize /= 2; + } + return newSize; + } else { + uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1); + return pages * SPARSE_PAGE_SIZE.x; + } +} + +glm::uvec2 rectifySize(const glm::uvec2& size) { + return { rectifyDimension(size.x), rectifyDimension(size.y) }; +} + +const QStringList getSupportedFormats() { + auto formats = QImageReader::supportedImageFormats(); + QStringList stringFormats; + std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), + [](QByteArray& format) -> QString { return format; }); + return stringFormats; +} + + +// On GLES, we don't use HDR skyboxes +bool isHDRTextureFormatEnabledForTarget(BackendTarget target) { + return target != BackendTarget::GLES32; +} + +gpu::Element getHDRTextureFormatForTarget(BackendTarget target, bool compressed) { + if (compressed) { + if (target == BackendTarget::GLES32) { + return gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } else { + return gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + } + } else { + if (!isHDRTextureFormatEnabledForTarget(target)) { + return GPU_CUBEMAP_DEFAULT_FORMAT; + } else { + return GPU_CUBEMAP_HDR_FORMAT; + } + } +} + +TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { + switch (type) { + case ALBEDO_TEXTURE: + return image::TextureUsage::createAlbedoTextureFromImage; + case EMISSIVE_TEXTURE: + return image::TextureUsage::createEmissiveTextureFromImage; + case LIGHTMAP_TEXTURE: + return image::TextureUsage::createLightmapTextureFromImage; + case CUBE_TEXTURE: + if (options.value("generateIrradiance", true).toBool()) { + return image::TextureUsage::createCubeTextureFromImage; + } else { + return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance; + } + case BUMP_TEXTURE: + return image::TextureUsage::createNormalTextureFromBumpImage; + case NORMAL_TEXTURE: + return image::TextureUsage::createNormalTextureFromNormalImage; + case ROUGHNESS_TEXTURE: + return image::TextureUsage::createRoughnessTextureFromImage; + case GLOSS_TEXTURE: + return image::TextureUsage::createRoughnessTextureFromGlossImage; + case SPECULAR_TEXTURE: + return image::TextureUsage::createMetallicTextureFromImage; + case STRICT_TEXTURE: + return image::TextureUsage::createStrict2DTextureFromImage; + + case DEFAULT_TEXTURE: + default: + return image::TextureUsage::create2DTextureFromImage; + } +} + +gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::create2DTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createCubeTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.0f : value; +} + +static uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + static const auto maxValue = 6.50e4f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + ucolor.r = std::min(ucolor.r, maxValue); + ucolor.g = std::min(ucolor.g, maxValue); + ucolor.b = std::min(ucolor.b, maxValue); + return glm::packF2x11_1x10(ucolor); +} + +static std::function getHDRPackingFunction(const gpu::Element& format) { + if (format == gpu::Element::COLOR_RGB9E5) { + return glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + return packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + return nullptr; + } +} + +std::function getHDRPackingFunction() { + return getHDRPackingFunction(GPU_CUBEMAP_HDR_FORMAT); +} + +std::function getHDRUnpackingFunction() { + if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { + return glm::unpackF3x9_E1x5; + } else if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { + return glm::unpackF2x11_1x10; + } else { + qCWarning(imagelogging) << "Unknown HDR encoding format in Image"; + Q_UNREACHABLE(); + return nullptr; + } +} + +Image processRawImageData(QIODevice& content, const std::string& filename) { + // Help the Image loader by extracting the image file format from the url filename ext. + // Some tga are not created properly without it. + auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + // Remove possible query part of the filename if it comes from an URL + filenameExtension = filenameExtension.substr(0, filenameExtension.find_first_of('?')); + if (!content.isReadable()) { + content.open(QIODevice::ReadOnly); + } else { + content.reset(); + } + + if (filenameExtension == "tga") { + Image image = image::readTGA(content); + if (!image.isNull()) { + return image; + } + content.reset(); + } +#if !defined(Q_OS_ANDROID) + else if (filenameExtension == "exr") { + Image image = image::readOpenEXR(content, filename); + if (!image.isNull()) { + return image; + } + } +#endif + + QImageReader imageReader(&content, filenameExtension.c_str()); + + if (imageReader.canRead()) { + return Image(imageReader.read()); + } else { + // Extension could be incorrect, try to detect the format from the content + QImageReader newImageReader; + newImageReader.setDecideFormatFromContent(true); + content.reset(); + newImageReader.setDevice(&content); + + if (newImageReader.canRead()) { + return Image(newImageReader.read()); + } + } + + return Image(); +} + +void mapToRedChannel(Image& image, ColorChannel sourceChannel) { + // Change format of image so we know exactly how to process it + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + for (glm::uint32 i = 0; i < image.getHeight(); i++) { + QRgb* pixel = reinterpret_cast(image.editScanLine(i)); + // Past end pointer + QRgb* lineEnd = pixel + image.getWidth(); + + // Transfer channel data from source to target + for (; pixel < lineEnd; pixel++) { + int colorValue; + switch (sourceChannel) { + case ColorChannel::RED: + colorValue = qRed(*pixel); + break; + case ColorChannel::GREEN: + colorValue = qGreen(*pixel); + break; + case ColorChannel::BLUE: + colorValue = qBlue(*pixel); + break; + case ColorChannel::ALPHA: + colorValue = qAlpha(*pixel); + break; + default: + colorValue = qRed(*pixel); + break; + } + + // Dump the color in the red channel, ignore the rest + *pixel = qRgba(colorValue, 0, 0, 255); + } + } +} + +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, ColorChannel sourceChannel, + int maxNumPixels, TextureUsage::Type textureType, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + + Image image = processRawImageData(*content.get(), filename); + // Texture content can take up a lot of memory. Here we release our ownership of that content + // in case it can be released. + content.reset(); + + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + // Validate that the image loaded + if (imageWidth == 0 || imageHeight == 0 || image.getFormat() == Image::Format_Invalid) { + QString reason(image.getFormat() == Image::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)"); + qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason); + return nullptr; + } + + // Validate the image is less than _maxNumPixels, and downscale if necessary + if (imageWidth * imageHeight > maxNumPixels) { + float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight)); + int originalWidth = imageWidth; + int originalHeight = imageHeight; + imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); + imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); + image = image.getScaled(glm::uvec2(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + qCDebug(imagelogging).nospace() << "Downscaled " << " (" << + QSize(originalWidth, originalHeight) << " to " << + QSize(imageWidth, imageHeight) << ")"; + } + + // Re-map to image with single red channel texture if requested + if (sourceChannel != ColorChannel::NONE) { + mapToRedChannel(image, sourceChannel); + } + + auto loader = TextureUsage::getTextureLoaderForType(textureType); + auto texture = loader(std::move(image), filename, compress, target, abortProcessing); + + return texture; +} + +Image processSourceImage(Image&& srcImage, bool cubemap, BackendTarget target) { + PROFILE_RANGE(resource_parse, "processSourceImage"); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + const glm::uvec2 srcImageSize = localCopy.getSize(); + glm::uvec2 targetSize = srcImageSize; + + const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; + while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { + targetSize /= 2; + } + if (targetSize != srcImageSize) { + ++DECIMATED_TEXTURE_COUNT; + } + + if (!cubemap) { + auto rectifiedSize = rectifySize(targetSize); + if (rectifiedSize != targetSize) { + ++RECTIFIED_TEXTURE_COUNT; + targetSize = rectifiedSize; + } + } + + if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) { + targetSize /= 2; + } + + if (targetSize != srcImageSize) { + PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); + qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; + return localCopy.getScaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + + return localCopy; +} + +#if defined(NVTT_API) +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + _size = size; + _miplevel = miplevel; + + _data = static_cast(malloc(size)); + _current = _data; + } + + virtual bool writeData(const void* data, int size) override { + assert(_current + size <= _data + _size); + memcpy(_current, data, size); + _current += size; + return true; + } + + virtual void endImage() override { + if (_face >= 0) { + _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); + } else { + _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); + } + free(_data); + _data = nullptr; + } + + gpu::Byte* _data{ nullptr }; + gpu::Byte* _current{ nullptr }; + gpu::Texture* _texture{ nullptr }; + int _miplevel = 0; + int _size = 0; + int _face = -1; +}; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + _packFunc = getHDRPackingFunction(format); + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + +struct MyErrorHandler : public nvtt::ErrorHandler { + virtual void error(nvtt::Error e) override { + qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); + } +}; + +class SequentialTaskDispatcher : public nvtt::TaskDispatcher { +public: + SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; + + const std::atomic& _abortProcessing; + + virtual void dispatch(nvtt::Task* task, void* context, int count) override { + for (int i = 0; i < count; i++) { + if (!_abortProcessing.load()) { + task(context, i); + } else { + break; + } + } + } +}; + +void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + assert(localCopy.getFormat() == Image::Format_PACKED_FLOAT); + + const int width = localCopy.getWidth(), height = localCopy.getHeight(); + std::vector data; + std::vector::iterator dataIt; + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc = getHDRUnpackingFunction(); + + nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + // TODO: gles: generate ETC mips instead? + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) { + compressionOptions.setFormat(nvtt::Format_BC6); + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + data.resize(width * height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = reinterpret_cast(localCopy.getScanLine(lineNb)); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + + // We're done with the localCopy, free up the memory to avoid bloating the heap + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + std::unique_ptr outputHandler; + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + nvtt::Context context; + int mipLevel = 0; + + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats + outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); + } else { + outputHandler.reset(new OutputHandler(texture, face)); + } + + outputOptions.setOutputHandler(outputHandler.get()); + + nvtt::Surface surface; + surface.setImage(inputFormat, width, height, 1, &(*data.begin())); + surface.setAlphaMode(alphaMode); + surface.setWrapMode(wrapMode); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + context.setTaskDispatcher(&dispatcher); + + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + while (surface.canMakeNextMipmap() && !abortProcessing.load()) { + surface.buildNextMipmap(nvtt::MipmapFilter_Box); + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + } +} + +void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + assert(localCopy.getFormat() != Image::Format_PACKED_FLOAT); + if (localCopy.getFormat() != Image::Format_ARGB32) { + localCopy = localCopy.getConvertedToFormat(Image::Format_ARGB32); + } + + const int width = localCopy.getWidth(), height = localCopy.getHeight(); + auto mipFormat = texture->getStoredMipFormat(); + + if (target != BackendTarget::GLES32) { + const void* data = static_cast(localCopy.getBits()); + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + float inputGamma = 2.2f; + float outputGamma = 2.2f; + + nvtt::InputOptions inputOptions; + inputOptions.setTextureLayout(textureType, width, height); + + inputOptions.setMipmapData(data, width, height); + // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap + data = nullptr; + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + inputOptions.setFormat(inputFormat); + inputOptions.setGamma(inputGamma, outputGamma); + inputOptions.setAlphaMode(alphaMode); + inputOptions.setWrapMode(wrapMode); + inputOptions.setRoundMode(roundMode); + + inputOptions.setMipmapGeneration(true); + inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { + compressionOptions.setFormat(nvtt::Format_BC1); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC1a); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC3); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { + compressionOptions.setFormat(nvtt::Format_BC4); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { + compressionOptions.setFormat(nvtt::Format_BC5); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC7); + } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_R_8) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 0, 0, 0); + } else if (mipFormat == gpu::Element::VEC2NU8_XY) { + inputOptions.setNormalMap(true); + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 8, 0, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + OutputHandler outputHandler(texture, face); + outputOptions.setOutputHandler(&outputHandler); + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + compressor.setTaskDispatcher(&dispatcher); + compressor.process(inputOptions, compressionOptions, outputOptions); + } else { + int numMips = 1 + (int)log2(std::max(width, height)); + Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; + Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { + etcFormat = Etc::Image::Format::RGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { + etcFormat = Etc::Image::Format::SRGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::RGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::SRGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { + etcFormat = Etc::Image::Format::RGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { + etcFormat = Etc::Image::Format::SRGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { + etcFormat = Etc::Image::Format::R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { + etcFormat = Etc::Image::Format::RG11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_RG11; + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; + const float effort = 1.0f; + const int numEncodeThreads = 4; + int encodingTime; + const float MAX_COLOR = 255.0f; + + std::vector floatData; + floatData.resize(width * height); + for (int y = 0; y < height; y++) { + QRgb *line = (QRgb *)localCopy.editScanLine(y); + for (int x = 0; x < width; x++) { + QRgb &pixel = line[x]; + floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; + } + } + + // free up the memory afterward to avoid bloating the heap + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + Etc::EncodeMipmaps( + (float *)floatData.data(), width, height, + etcFormat, errorMetric, effort, + numEncodeThreads, numEncodeThreads, + numMips, Etc::FILTER_WRAP_NONE, + mipMaps, &encodingTime + ); + + for (int i = 0; i < numMips; i++) { + if (mipMaps[i].paucEncodingBits.get()) { + if (face >= 0) { + texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } else { + texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } + } + } + + delete[] mipMaps; + } +} + +#endif + +void generateMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { +#if CPU_MIPMAPS + PROFILE_RANGE(resource_parse, "generateMips"); + + if (target == BackendTarget::GLES32) { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + if (image.getFormat() == Image::Format_PACKED_FLOAT) { + generateHDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } + } +#else + texture->setAutoGenerateMips(true); +#endif +} + +void processTextureAlpha(const Image& srcImage, bool& validAlpha, bool& alphaAsMask) { + PROFILE_RANGE(resource_parse, "processTextureAlpha"); + validAlpha = false; + alphaAsMask = true; + const uint8 OPAQUE_ALPHA = 255; + const uint8 TRANSPARENT_ALPHA = 0; + + // Figure out if we can use a mask for alpha or not + int numOpaques = 0; + int numTranslucents = 0; + const int NUM_PIXELS = srcImage.getWidth() * srcImage.getHeight(); + const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS)); + const QRgb* data = reinterpret_cast(srcImage.getBits()); + for (int i = 0; i < NUM_PIXELS; ++i) { + auto alpha = qAlpha(data[i]); + if (alpha == OPAQUE_ALPHA) { + numOpaques++; + } else if (alpha != TRANSPARENT_ALPHA) { + if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) { + alphaAsMask = false; + break; + } + } + } + validAlpha = (numOpaques != NUM_PIXELS); +} + +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + bool validAlpha = image.hasAlphaChannel(); + bool alphaAsMask = false; + + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + if (validAlpha) { + processTextureAlpha(image, validAlpha, alphaAsMask); + } + + gpu::TexturePointer theTexture = nullptr; + + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + // GLES does not support GL_BGRA + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; + } else { + if (validAlpha) { + // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures + // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + } + formatMip = formatGPU; + } + } else { + if (target == BackendTarget::GLES32) { + } else { + formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = gpu::Element::COLOR_SBGRA_32; + } + } + + if (isStrict) { + theTexture = gpu::Texture::createStrict(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + } else { + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + } + theTexture->setSource(srcImageName); + auto usage = gpu::Texture::Usage::Builder().withColor(); + if (validAlpha) { + usage.withAlpha(); + if (alphaAsMask) { + usage.withAlphaMask(); + } + } + theTexture->setUsage(usage.build()); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +int clampPixelCoordinate(int coordinate, int maxCoordinate) { + return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate)); +} + +const int RGBA_MAX = 255; + +// transform -1 - 1 to 0 - 255 (from sobel value to rgb) +double mapComponent(double sobelValue) { + const double factor = RGBA_MAX / 2.0; + return (sobelValue + 1.0) * factor; +} + +Image processBumpMap(Image&& image) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + if (localCopy.getFormat() != Image::Format_Grayscale8) { + localCopy = localCopy.getConvertedToFormat(Image::Format_Grayscale8); + } + + // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps + // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image + const double pStrength = 2.0; + int width = localCopy.getWidth(); + int height = localCopy.getHeight(); + + Image result(width, height, Image::Format_ARGB32); + + for (int i = 0; i < width; i++) { + const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); + const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1); + + for (int j = 0; j < height; j++) { + const int jNextClamped = clampPixelCoordinate(j + 1, height - 1); + const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); + + // surrounding pixels + const QRgb topLeft = localCopy.getPixel(iPrevClamped, jPrevClamped); + const QRgb top = localCopy.getPixel(iPrevClamped, j); + const QRgb topRight = localCopy.getPixel(iPrevClamped, jNextClamped); + const QRgb right = localCopy.getPixel(i, jNextClamped); + const QRgb bottomRight = localCopy.getPixel(iNextClamped, jNextClamped); + const QRgb bottom = localCopy.getPixel(iNextClamped, j); + const QRgb bottomLeft = localCopy.getPixel(iNextClamped, jPrevClamped); + const QRgb left = localCopy.getPixel(i, jPrevClamped); + + // take their gray intensities + // since it's a grayscale image, the value of each component RGB is the same + const double tl = qRed(topLeft); + const double t = qRed(top); + const double tr = qRed(topRight); + const double r = qRed(right); + const double br = qRed(bottomRight); + const double b = qRed(bottom); + const double bl = qRed(bottomLeft); + const double l = qRed(left); + + // apply the sobel filter + const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl); + const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr); + const double dZ = RGBA_MAX / pStrength; + + glm::vec3 v(dX, dY, dZ); + glm::normalize(v); + + // convert to rgb from the value obtained computing the filter + QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0); + result.setPixel(i, j, qRgbValue); + } + } + + return result; +} +gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool isBumpMap, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + if (isBumpMap) { + image = processBumpMap(std::move(image)); + } + + // Make sure the normal map source image is ARGB32 + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + gpu::TexturePointer theTexture = nullptr; + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; + } + } else { + formatGPU = gpu::Element::VEC2NU8_XY; + } + formatMip = formatGPU; + + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool isInvertedPixels, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + if (isInvertedPixels) { + // Gloss turned into Rough + image.invertPixels(); + } + + gpu::TexturePointer theTexture = nullptr; + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; + } + } else { + formatGPU = gpu::Element::COLOR_R_8; + } + formatMip = formatGPU; + + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +class CubeLayout { +public: + + enum SourceProjection { + FLAT = 0, + EQUIRECTANGULAR, + }; + int _type = FLAT; + int _widthRatio = 1; + int _heightRatio = 1; + + class Face { + public: + int _x = 0; + int _y = 0; + bool _horizontalMirror = false; + bool _verticalMirror = false; + + Face() {} + Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {} + }; + + Face _faceXPos; + Face _faceXNeg; + Face _faceYPos; + Face _faceYNeg; + Face _faceZPos; + Face _faceZNeg; + + CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) : + _type(FLAT), + _widthRatio(wr), + _heightRatio(hr), + _faceXPos(fXP), + _faceXNeg(fXN), + _faceYPos(fYP), + _faceYNeg(fYN), + _faceZPos(fZP), + _faceZNeg(fZN) {} + + CubeLayout(int wr, int hr) : + _type(EQUIRECTANGULAR), + _widthRatio(wr), + _heightRatio(hr) {} + + + static const CubeLayout CUBEMAP_LAYOUTS[]; + static const int NUM_CUBEMAP_LAYOUTS; + + static int findLayout(int width, int height) { + // Find the layout of the cubemap in the 2D image + int foundLayout = -1; + for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) { + if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) { + foundLayout = i; + break; + } + } + return foundLayout; + } + + static Image extractEquirectangularFace(const Image& source, gpu::Texture::CubeFace face, int faceWidth) { + Image image(faceWidth, faceWidth, source.getFormat()); + + glm::vec2 dstInvSize(1.0f / faceWidth); + + struct CubeToXYZ { + gpu::Texture::CubeFace _face; + CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {} + + glm::vec3 xyzFrom(const glm::vec2& uv) { + auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f)); + + switch (_face) { + case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z: + return glm::vec3(-faceDir.x, faceDir.y, faceDir.z); + case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z: + return glm::vec3(faceDir.x, faceDir.y, -faceDir.z); + case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X: + return glm::vec3(faceDir.z, faceDir.y, faceDir.x); + case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X: + return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x); + case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y: + return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y); + case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y: + default: + return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y); + } + } + }; + CubeToXYZ cubeToXYZ(face); + + struct RectToXYZ { + RectToXYZ() {} + + glm::vec2 uvFrom(const glm::vec3& xyz) { + auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z)); + auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y)); + + const float LON_TO_RECT_U = 1.0f / (glm::pi()); + const float LAT_TO_RECT_V = 2.0f / glm::pi(); + return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f); + } + }; + RectToXYZ rectToXYZ; + + int srcFaceHeight = source.getHeight(); + int srcFaceWidth = source.getWidth(); + + glm::vec2 dstCoord; + glm::ivec2 srcPixel; + for (int y = 0; y < faceWidth; ++y) { + QRgb* destScanLineBegin = reinterpret_cast( image.editScanLine(y) ); + QRgb* destPixelIterator = destScanLineBegin; + + dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom + for (int x = 0; x < faceWidth; ++x) { + dstCoord.x = (x + 0.5f) * dstInvSize.x; + + auto xyzDir = cubeToXYZ.xyzFrom(dstCoord); + auto srcCoord = rectToXYZ.uvFrom(xyzDir); + + srcPixel.x = floor(srcCoord.x * srcFaceWidth); + // Flip the vertical axis to Image going top to bottom + srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight); + + if (((uint32)srcPixel.x < (uint32)source.getWidth()) && ((uint32)srcPixel.y < (uint32)source.getHeight())) { + // We can't directly use the pixel() method because that launches a pixel color conversion to output + // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which + // are not convertible by Qt. The same goes with the setPixel method, by the way. + const QRgb* sourcePixelIterator = reinterpret_cast(source.getScanLine(srcPixel.y)); + sourcePixelIterator += srcPixel.x; + *destPixelIterator = *sourcePixelIterator; + + // Keep for debug, this is showing the dir as a color + // glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256); + // unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16); + // *destPixelIterator = val; + } + ++destPixelIterator; + } + } + return image; + } +}; + +const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { + + // Here is the expected layout for the faces in an image with the 2/1 aspect ratio: + // THis is detected as an Equirectangular projection + // WIDTH + // <---------------------------> + // ^ +------+------+------+------+ + // H | | | | | + // E | | | | | + // I | | | | | + // G +------+------+------+------+ + // H | | | | | + // T | | | | | + // | | | | | | + // v +------+------+------+------+ + // + // FaceWidth = width = height / 6 + { 2, 1 }, + + // Here is the expected layout for the faces in an image with the 1/6 aspect ratio: + // + // WIDTH + // <------> + // ^ +------+ + // | | | + // | | +X | + // | | | + // H +------+ + // E | | + // I | -X | + // G | | + // H +------+ + // T | | + // | | +Y | + // | | | + // | +------+ + // | | | + // | | -Y | + // | | | + // H +------+ + // E | | + // I | +Z | + // G | | + // H +------+ + // T | | + // | | -Z | + // | | | + // V +------+ + // + // FaceWidth = width = height / 6 + { 1, 6, + { 0, 0, true, false }, + { 0, 1, true, false }, + { 0, 2, false, true }, + { 0, 3, false, true }, + { 0, 4, true, false }, + { 0, 5, true, false } + }, + + // Here is the expected layout for the faces in an image with the 3/4 aspect ratio: + // + // <-----------WIDTH-----------> + // ^ +------+------+------+------+ + // | | | | | | + // | | | +Y | | | + // | | | | | | + // H +------+------+------+------+ + // E | | | | | + // I | -X | -Z | +X | +Z | + // G | | | | | + // H +------+------+------+------+ + // T | | | | | + // | | | -Y | | | + // | | | | | | + // V +------+------+------+------+ + // + // FaceWidth = width / 4 = height / 3 + { 4, 3, + { 2, 1, true, false }, + { 0, 1, true, false }, + { 1, 0, false, true }, + { 1, 2, false, true }, + { 3, 1, true, false }, + { 1, 1, true, false } + }, + + // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: + // + // <-------WIDTH--------> + // ^ +------+------+------+ + // | | | | | + // | | | +Y | | + // | | | | | + // H +------+------+------+ + // E | | | | + // I | -X | -Z | +X | + // G | | | | + // H +------+------+------+ + // T | | | | + // | | | -Y | | + // | | | | | + // | +------+------+------+ + // | | | | | + // | | | +Z! | | <+Z is upside down! + // | | | | | + // V +------+------+------+ + // + // FaceWidth = width / 3 = height / 4 + { 3, 4, + { 2, 1, true, false }, + { 0, 1, true, false }, + { 1, 0, false, true }, + { 1, 2, false, true }, + { 1, 3, false, true }, + { 1, 1, true, false } + } +}; +const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); + +//#define DEBUG_COLOR_PACKING +Image convertToLDRFormat(Image&& srcImage, Image::Format format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + Image ldrImage(localCopy.getWidth(), localCopy.getHeight(), format); + auto unpackFunc = getHDRUnpackingFunction(); + + for (glm::uint32 y = 0; y < localCopy.getHeight(); y++) { + const QRgb* srcLineIt = reinterpret_cast(localCopy.getScanLine(y)); + const QRgb* srcLineEnd = srcLineIt + localCopy.getWidth(); + uint32* ldrLineIt = reinterpret_cast(ldrImage.editScanLine(y)); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color = unpackFunc(*srcLineIt); + // Apply reverse gamma and clamp + color.r = std::pow(color.r, 1.0f / 2.2f); + color.g = std::pow(color.g, 1.0f / 2.2f); + color.b = std::pow(color.b, 1.0f / 2.2f); + color.r = std::min(1.0f, color.r) * 255.0f; + color.g = std::min(1.0f, color.g) * 255.0f; + color.b = std::min(1.0f, color.b) * 255.0f; + *ldrLineIt = qRgb((int)color.r, (int)color.g, (int)color.b); + + ++srcLineIt; + ++ldrLineIt; + } + } + return ldrImage; +} + +Image convertToHDRFormat(Image&& srcImage, gpu::Element format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + Image hdrImage(localCopy.getWidth(), localCopy.getHeight(), Image::Format_PACKED_FLOAT); + std::function packFunc; +#ifdef DEBUG_COLOR_PACKING + std::function unpackFunc; +#endif + + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF2x11_1x10; +#endif + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF3x9_E1x5; +#endif + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return localCopy; + } + + localCopy = localCopy.getConvertedToFormat(Image::Format_ARGB32); + for (glm::uint32 y = 0; y < localCopy.getHeight(); y++) { + const QRgb* srcLineIt = reinterpret_cast( localCopy.getScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + localCopy.getWidth(); + uint32* hdrLineIt = reinterpret_cast( hdrImage.editScanLine(y) ); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color.r = qRed(*srcLineIt); + color.g = qGreen(*srcLineIt); + color.b = qBlue(*srcLineIt); + // Normalize and apply gamma + color /= 255.0f; + color.r = std::pow(color.r, 2.2f); + color.g = std::pow(color.g, 2.2f); + color.b = std::pow(color.b, 2.2f); + *hdrLineIt = packFunc(color); +#ifdef DEBUG_COLOR_PACKING + glm::vec3 ucolor = unpackFunc(*hdrLineIt); + assert(glm::distance(color, ucolor) <= 5e-2); +#endif + ++srcLineIt; + ++hdrLineIt; + } + } + return hdrImage; +} + +gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool generateIrradiance, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + int originalWidth = localCopy.getWidth(); + int originalHeight = localCopy.getHeight(); + if ((originalWidth <= 0) && (originalHeight <= 0)) { + return nullptr; + } + + gpu::TexturePointer theTexture = nullptr; + + Image image = processSourceImage(std::move(localCopy), true, target); + auto hasTargetHDRFormat = isHDRTextureFormatEnabledForTarget(target); + if (hasTargetHDRFormat && image.getFormat() != Image::Format_PACKED_FLOAT) { + // If the target format is HDR but the image isn't, we need to convert the + // image to HDR. + image = convertToHDRFormat(std::move(image), GPU_CUBEMAP_HDR_FORMAT); + } else if (!hasTargetHDRFormat && image.getFormat() == Image::Format_PACKED_FLOAT) { + // If the target format isn't HDR (such as on GLES) but the image is, we need to + // convert the image to LDR + image = convertToLDRFormat(std::move(image), Image::Format_ARGB32); + } + + gpu::Element formatGPU = getHDRTextureFormatForTarget(target, compress); + gpu::Element formatMip = formatGPU; + + // Find the layout of the cubemap in the 2D image + // Use the original image size since processSourceImage may have altered the size / aspect ratio + int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); + + if (foundLayout < 0) { + qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); + return nullptr; + } + + std::vector faces; + + // If found, go extract the faces as separate images + auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; + if (layout._type == CubeLayout::FLAT) { + int faceWidth = image.getWidth() / layout._widthRatio; + + faces.push_back(image.getSubImage(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { + // THe face width is estimated from the input image + const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; + const int EQUIRECT_MAX_FACE_WIDTH = 2048; + int faceWidth = std::min(image.getWidth() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); + for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { + Image faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); + faces.push_back(std::move(faceImage)); + } + } + + // free up the memory afterward to avoid bloating the heap + image = Image(); // Image doesn't have a clear function, so override it with an empty one. + + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].getWidth(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + + // Generate irradiance while we are at it + if (generateIrradiance) { + PROFILE_RANGE(resource_parse, "generateIrradiance"); + gpu::Element irradianceFormat; + // TODO: we could locally compress the irradiance texture on Android, but we don't need to + if (target == BackendTarget::GLES32) { + irradianceFormat = gpu::Element::COLOR_SRGBA_32; + } else { + irradianceFormat = GPU_CUBEMAP_HDR_FORMAT; + } + + auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].getWidth(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + irradianceTexture->setSource(srcImageName); + irradianceTexture->setStoredMipFormat(irradianceFormat); + for (uint8 face = 0; face < faces.size(); ++face) { + irradianceTexture->assignStoredMipFace(0, face, faces[face].getByteCount(), faces[face].getBits()); + } + + irradianceTexture->generateIrradiance(target); + + auto irradiance = irradianceTexture->getIrradiance(); + theTexture->overrideIrradiance(irradiance); + } + + for (uint8 face = 0; face < faces.size(); ++face) { + generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); + } + } + + return theTexture; +} + +} // namespace image diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h new file mode 100644 index 0000000000..72e2400721 --- /dev/null +++ b/libraries/image/src/image/TextureProcessing.h @@ -0,0 +1,118 @@ +// +// TextureProcessing.h +// image/src/TextureProcessing +// +// Created by Clement Brisset on 4/5/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_image_TextureProcessing_h +#define hifi_image_TextureProcessing_h + +#include + +#include + +#include "Image.h" + +namespace image { + + std::function getHDRPackingFunction(); + std::function getHDRUnpackingFunction(); + +namespace TextureUsage { + +/**jsdoc + *

Describes the type of texture.

+ *

See also: {@link Material} and + * {@link https://docs.highfidelity.com/create/3d-models/pbr-materials-guide.html|PBR Materials Guide}.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0DefaultBasic color.
1StrictBasic color. Quality never downgraded.
2AlbedoColor for PBR.
3NormalNormal map.
4BumpBump map.
5Specular or metallicMetallic or not.
6RoughnessRough or matte.
7GlossGloss or shine.
8EmissiveThe amount of light reflected.
9CubeCubic image for sky boxes.
10Occlusion or scatteringHow objects or human skin interact with light.
11LightmapLight map.
12UnusedTexture is not currently used.
+ * @typedef {number} TextureCache.TextureType + */ +enum Type { + DEFAULT_TEXTURE, + STRICT_TEXTURE, + ALBEDO_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + SCATTERING_TEXTURE = OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + UNUSED_TEXTURE +}; + +using TextureLoader = std::function&)>; +TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); + +gpu::TexturePointer create2DTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createStrict2DTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createAlbedoTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createEmissiveTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createNormalTextureFromNormalImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createNormalTextureFromBumpImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createRoughnessTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createRoughnessTextureFromGlossImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createMetallicTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createCubeTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createLightmapTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); +gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); + +} // namespace TextureUsage + +const QStringList getSupportedFormats(); + +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, ColorChannel sourceChannel, + int maxNumPixels, TextureUsage::Type textureType, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); + +} // namespace image + +#endif // hifi_image_TextureProcessing_h diff --git a/libraries/material-networking/src/material-networking/MaterialCache.cpp b/libraries/material-networking/src/material-networking/MaterialCache.cpp index 6561fc697e..9eef89d5c9 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.cpp +++ b/libraries/material-networking/src/material-networking/MaterialCache.cpp @@ -559,8 +559,7 @@ void NetworkMaterial::setLightmapMap(const QUrl& url) { } NetworkMaterial::NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl) : - graphics::Material(*material._material), - _textures(MapChannel::NUM_MAP_CHANNELS) + graphics::Material(*material._material) { _name = material.name.toStdString(); if (!material.albedoTexture.filename.isEmpty()) { @@ -709,7 +708,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { bool NetworkMaterial::isMissingTexture() { for (auto& networkTexture : _textures) { - auto& texture = networkTexture.texture; + auto& texture = networkTexture.second.texture; if (!texture) { continue; } diff --git a/libraries/material-networking/src/material-networking/MaterialCache.h b/libraries/material-networking/src/material-networking/MaterialCache.h index d327aedb22..4894054de4 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.h +++ b/libraries/material-networking/src/material-networking/MaterialCache.h @@ -36,15 +36,21 @@ public: bool isMissingTexture(); void checkResetOpacityMap(); -protected: - friend class Geometry; - class Texture { public: QString name; NetworkTexturePointer texture; }; - using Textures = std::vector; + struct MapChannelHash { + std::size_t operator()(MapChannel mapChannel) const { + return static_cast(mapChannel); + } + }; + using Textures = std::unordered_map; + Textures getTextures() { return _textures; } + +protected: + friend class Geometry; Textures _textures; diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 43f467266a..6af59930fa 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include diff --git a/libraries/material-networking/src/material-networking/TextureCache.h b/libraries/material-networking/src/material-networking/TextureCache.h index dcab527e4a..f8ddd77412 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.h +++ b/libraries/material-networking/src/material-networking/TextureCache.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include diff --git a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h index 1cc4f4f948..58d2784855 100644 --- a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h +++ b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h @@ -25,7 +25,8 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * API to manage texture cache resources. + * The TextureCache API manages texture cache resources. + * * @namespace TextureCache * * @hifi-interface @@ -47,11 +48,14 @@ public: TextureCacheScriptingInterface(); /**jsdoc + * Prefetches a texture resource of specific type. * @function TextureCache.prefetch - * @param {string} url - * @param {number} type - * @param {number} [maxNumPixels=67108864] - * @returns {ResourceObject} + * @variation 0 + * @param {string} url - The URL of the texture to prefetch. + * @param {TextureCache.TextureType} type - The type of the texture. + * @param {number} [maxNumPixels=67108864] - The maximum number of pixels to use for the image. If the texture has more + * than this number it is downscaled. + * @returns {ResourceObject} A resource object. */ Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); @@ -59,6 +63,7 @@ signals: /**jsdoc * @function TextureCache.spectatorCameraFramebufferReset * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void spectatorCameraFramebufferReset(); }; diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 536255a841..70269c6401 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -113,6 +113,7 @@ namespace baker { hfmModelOut->jointRotationOffsets = input.get3(); hfmModelOut->jointIndices = input.get4(); hfmModelOut->flowData = input.get5(); + hfmModelOut->computeKdops(); output = hfmModelOut; } }; diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp index acb2bdc1c5..17b62d0915 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp @@ -10,6 +10,62 @@ #include "ModelBakerLogging.h" +#include + +void processMaterialMapping(MaterialMapping& materialMapping, const QJsonObject& materialMap, const hifi::URL& url) { + auto mappingKeys = materialMap.keys(); + for (auto mapping : mappingKeys) { + auto mappingJSON = materialMap[mapping]; + if (mappingJSON.isObject()) { + auto mappingValue = mappingJSON.toObject(); + + // Old subsurface scattering mapping + { + auto scatteringIter = mappingValue.find("scattering"); + auto scatteringMapIter = mappingValue.find("scatteringMap"); + if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) { + std::shared_ptr material = std::make_shared(); + + if (scatteringIter != mappingValue.end()) { + float scattering = (float)scatteringIter.value().toDouble(); + material->setScattering(scattering); + } + + if (scatteringMapIter != mappingValue.end()) { + QString scatteringMap = scatteringMapIter.value().toString(); + material->setScatteringMap(scatteringMap); + } + + material->setDefaultFallthrough(true); + + NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), + [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + materialResource->moveToThread(qApp->thread()); + materialResource->parsedMaterials.names.push_back("scattering"); + materialResource->parsedMaterials.networkMaterials["scattering"] = material; + + materialMapping.push_back(std::pair("mat::" + mapping.toStdString(), materialResource)); + continue; + } + } + + // Material JSON description + { + NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), + [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + materialResource->moveToThread(qApp->thread()); + materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url); + materialMapping.push_back(std::pair(mapping.toStdString(), materialResource)); + } + + } else if (mappingJSON.isString()) { + auto mappingValue = mappingJSON.toString(); + materialMapping.push_back(std::pair(mapping.toStdString(), + MaterialCache::instance().getMaterial(url.resolved(mappingValue)))); + } + } +} + void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& mapping = input.get0(); const auto& url = input.get1(); @@ -18,56 +74,18 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con auto mappingIter = mapping.find("materialMap"); if (mappingIter != mapping.end()) { QByteArray materialMapValue = mappingIter.value().toByteArray(); - QJsonObject materialMap = QJsonDocument::fromJson(materialMapValue).object(); - if (materialMap.isEmpty()) { + QJsonDocument materialMapJSON = QJsonDocument::fromJson(materialMapValue); + if (materialMapJSON.isEmpty()) { qCDebug(model_baker) << "Material Map found but did not produce valid JSON:" << materialMapValue; + } else if (materialMapJSON.isObject()) { + QJsonObject materialMap = materialMapJSON.object(); + processMaterialMapping(materialMapping, materialMap, url); } else { - auto mappingKeys = materialMap.keys(); - for (auto mapping : mappingKeys) { - auto mappingJSON = materialMap[mapping]; - if (mappingJSON.isObject()) { - auto mappingValue = mappingJSON.toObject(); - - // Old subsurface scattering mapping - { - auto scatteringIter = mappingValue.find("scattering"); - auto scatteringMapIter = mappingValue.find("scatteringMap"); - if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) { - std::shared_ptr material = std::make_shared(); - - if (scatteringIter != mappingValue.end()) { - float scattering = (float)scatteringIter.value().toDouble(); - material->setScattering(scattering); - } - - if (scatteringMapIter != mappingValue.end()) { - QString scatteringMap = scatteringMapIter.value().toString(); - material->setScatteringMap(scatteringMap); - } - - material->setDefaultFallthrough(true); - - NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - materialResource->moveToThread(qApp->thread()); - materialResource->parsedMaterials.names.push_back("scattering"); - materialResource->parsedMaterials.networkMaterials["scattering"] = material; - - materialMapping.push_back(std::pair("mat::" + mapping.toStdString(), materialResource)); - continue; - } - } - - // Material JSON description - { - NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - materialResource->moveToThread(qApp->thread()); - materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url); - materialMapping.push_back(std::pair(mapping.toStdString(), materialResource)); - } - - } else if (mappingJSON.isString()) { - auto mappingValue = mappingJSON.toString(); - materialMapping.push_back(std::pair(mapping.toStdString(), MaterialCache::instance().getMaterial(url.resolved(mappingValue)))); + QJsonArray materialMapArray = materialMapJSON.array(); + for (auto materialMapIter : materialMapArray) { + if (materialMapIter.isObject()) { + QJsonObject materialMap = materialMapIter.toObject(); + processMaterialMapping(materialMapping, materialMap, url); } } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 4cf7609ee9..23b365dd03 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -437,8 +437,8 @@ const QVariantMap Geometry::getTextures() const { QVariantMap textures; for (const auto& material : _materials) { for (const auto& texture : material->_textures) { - if (texture.texture) { - textures[texture.name] = texture.texture->getURL(); + if (texture.second.texture) { + textures[texture.second.name] = texture.second.texture->getURL(); } } } @@ -467,7 +467,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) { for (auto& material : _materials) { // Check if any material textures actually changed if (std::any_of(material->_textures.cbegin(), material->_textures.cend(), - [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.texture && textureMap.contains(it.name); })) { + [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.second.texture && textureMap.contains(it.second.name); })) { // FIXME: The Model currently caches the materials (waste of space!) // so they must be copied in the Geometry copy-ctor diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h index 16532fafc3..cea2a6cd40 100644 --- a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h @@ -25,7 +25,8 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * API to manage model cache resources. + * The ModelCache API manages model cache resources. + * * @namespace ModelCache * * @hifi-interface diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 2096213273..e1f3098658 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -91,6 +91,9 @@ private: class ScriptableResource : public QObject { /**jsdoc + * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link ModelCache.prefetch}, + * {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}. + * * @class ResourceObject * * @hifi-interface @@ -99,8 +102,8 @@ class ScriptableResource : public QObject { * @hifi-server-entity * @hifi-assignment-client * - * @property {string} url - URL of this resource. - * @property {Resource.State} state - Current loading state. + * @property {string} url - URL of the resource. Read-only. + * @property {Resource.State} state - Current loading state. Read-only. */ Q_OBJECT Q_PROPERTY(QUrl url READ getURL) @@ -109,12 +112,13 @@ class ScriptableResource : public QObject { public: /**jsdoc + * The loading state of a resource. * @typedef {object} Resource.State * @property {number} QUEUED - The resource is queued up, waiting to be loaded. * @property {number} LOADING - The resource is downloading. - * @property {number} LOADED - The resource has finished downloaded by is not complete. + * @property {number} LOADED - The resource has finished downloading but is not complete. * @property {number} FINISHED - The resource has completely finished loading and is ready. - * @property {number} FAILED - Downloading the resource has failed. + * @property {number} FAILED - The resource has failed to download. */ enum State { QUEUED, @@ -129,7 +133,7 @@ public: virtual ~ScriptableResource() = default; /**jsdoc - * Release this resource. + * Releases the resource. * @function ResourceObject#release */ Q_INVOKABLE void release(); @@ -144,16 +148,16 @@ public: signals: /**jsdoc - * Triggered when download progress for this resource has changed. + * Triggered when the resource's download progress changes. * @function ResourceObject#progressChanged - * @param {number} bytesReceived - Byytes downloaded so far. + * @param {number} bytesReceived - Bytes downloaded so far. * @param {number} bytesTotal - Total number of bytes in the resource. * @returns {Signal} */ void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); /**jsdoc - * Triggered when resource loading state has changed. + * Triggered when the resource's loading state changes. * @function ResourceObject#stateChanged * @param {Resource.State} state - New state. * @returns {Signal} @@ -317,30 +321,63 @@ public: ScriptableResourceCache(QSharedPointer resourceCache); /**jsdoc - * Get the list of all resource URLs. + * Gets the URLs of all resources in the cache. * @function ResourceCache.getResourceList - * @returns {string[]} + * @returns {string[]} The URLs of all resources in the cache. + * @example Report cached resources. + * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * + * var cachedResources = AnimationCache.getResourceList(); + * print("Cached resources: " + JSON.stringify(cachedResources)); */ Q_INVOKABLE QVariantList getResourceList(); /**jsdoc * @function ResourceCache.updateTotalSize - * @param {number} deltaSize + * @param {number} deltaSize - Delta size. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void updateTotalSize(const qint64& deltaSize); /**jsdoc * Prefetches a resource. * @function ResourceCache.prefetch - * @param {string} url - URL of the resource to prefetch. - * @returns {ResourceObject} + * @param {string} url - The URL of the resource to prefetch. + * @returns {ResourceObject} A resource object. + * @example Prefetch a resource and wait until it has loaded. + * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * // TextureCache has its own version of this function. + * + * var resourceURL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx"; + * var resourceObject = AnimationCache.prefetch(resourceURL); + * + * function checkIfResourceLoaded(state) { + * if (state === Resource.State.FINISHED) { + * print("Resource loaded and ready."); + * } else if (state === Resource.State.FAILED) { + * print("Resource not loaded."); + * } + * } + * + * // Resource may have already been loaded. + * print("Resource state: " + resourceObject.state); + * checkIfResourceLoaded(resourceObject.state); + * + * // Resource may still be loading. + * resourceObject.stateChanged.connect(function (state) { + * print("Resource state changed to: " + state); + * checkIfResourceLoaded(state); + * }); */ Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } + + // FIXME: This function variation shouldn't be in the API. Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); signals: /**jsdoc + * Triggered when the cache content has changed. * @function ResourceCache.dirty * @returns {Signal} */ diff --git a/libraries/networking/src/ResourceScriptingInterface.h b/libraries/networking/src/ResourceScriptingInterface.h index 5f81537e99..73e2d8e29c 100644 --- a/libraries/networking/src/ResourceScriptingInterface.h +++ b/libraries/networking/src/ResourceScriptingInterface.h @@ -18,6 +18,8 @@ #include /**jsdoc + * The Resources API enables the default location for different resource types to be overridden. + * * @namespace Resources * * @hifi-interface @@ -32,15 +34,17 @@ class ResourceScriptingInterface : public QObject, public Dependency { public: /**jsdoc + * Overrides a path prefix with an alternative path. * @function Resources.overrideUrlPrefix - * @param {string} prefix - * @param {string} replacement + * @param {string} prefix - The path prefix to override, e.g., "atp:/". + * @param {string} replacement - The replacement path for the prefix. */ Q_INVOKABLE void overrideUrlPrefix(const QString& prefix, const QString& replacement); /**jsdoc + * Restores the default path for a specified prefix. * @function Resources.restoreUrlPrefix - * @param {string} prefix + * @param {string} prefix - The prefix of the resource to restore the path for. */ Q_INVOKABLE void restoreUrlPrefix(const QString& prefix) { overrideUrlPrefix(prefix, ""); diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index c63170de75..2f47ef5e00 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -71,12 +71,12 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b doLogAction("makeUserConnection", payload); } -void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) { - doLogAction(newValue ? "bubbleOn" : "bubbleOff"); +void UserActivityLoggerScriptingInterface::privacyShieldToggled(bool newValue) { + doLogAction(newValue ? "privacyShieldOn" : "privacyShieldOff"); } -void UserActivityLoggerScriptingInterface::bubbleActivated() { - doLogAction("bubbleActivated"); +void UserActivityLoggerScriptingInterface::privacyShieldActivated() { + doLogAction("privacyShieldActivated"); } void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index 71d411056d..1cda1235e9 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -30,8 +30,8 @@ public: Q_INVOKABLE void palAction(QString action, QString target); Q_INVOKABLE void palOpened(float secondsOpen); Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); - Q_INVOKABLE void bubbleToggled(bool newValue); - Q_INVOKABLE void bubbleActivated(); + Q_INVOKABLE void privacyShieldToggled(bool newValue); + Q_INVOKABLE void privacyShieldActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem); Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 7f20f881da..b21c200ef2 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: - return static_cast(AvatarMixerPacketVersion::SendMaxTranslationDimension); + return static_cast(AvatarMixerPacketVersion::FBXJointOrderChange); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::SendMaxTranslationDimension); + return static_cast(AvatarMixerPacketVersion::FBXJointOrderChange); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -86,7 +86,8 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::MicrophoneAudioNoEcho: case PacketType::MicrophoneAudioWithEcho: case PacketType::AudioStreamStats: - return static_cast(AudioVersion::HighDynamicRangeVolume); + case PacketType::StopInjector: + return static_cast(AudioVersion::StopInjectors); case PacketType::DomainSettings: return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height case PacketType::Ping: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 413ff14b17..1dafc561f6 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -134,6 +134,7 @@ public: BulkAvatarTraits, AudioSoloRequest, BulkAvatarTraitsAck, + StopInjector, NUM_PACKET_TYPE }; @@ -266,6 +267,8 @@ enum class EntityVersion : PacketVersion { ModelScale, ReOrderParentIDProperties, CertificateTypeProperty, + DisableWebMedia, + ParticleShapeType, // Add new versions above here NUM_PACKET_TYPE, @@ -327,7 +330,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { CollisionFlag, AvatarTraitsAck, FasterAvatarEntities, - SendMaxTranslationDimension + SendMaxTranslationDimension, + FBXJointOrderChange }; enum class DomainConnectRequestVersion : PacketVersion { @@ -366,6 +370,7 @@ enum class AudioVersion : PacketVersion { SpaceBubbleChanges, HasPersonalMute, HighDynamicRangeVolume, + StopInjectors }; enum class MessageDataVersion : PacketVersion { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 2f03eda286..e82201adfd 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -97,7 +97,7 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { _errorString = "Duplicate Id entries"; return false; } - + gotId = true; if (nextToken() != '"') { _errorString = "Invalid Id value"; return false; @@ -107,14 +107,21 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { _errorString = "Invalid Id string"; return false; } - QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) ); - if (idValue.isNull()) { - _errorString = "Id value invalid UUID string: " + idString; - return false; - } - parsedEntities["Id"] = idValue; - gotId = true; + // some older archives may have a null string id, so + // return success without setting parsedEntities, + // which will result in a new uuid for the restored + // archive. (not parsing and using isNull as parsing + // results in null if there is a corrupt string) + + if (idString != "{00000000-0000-0000-0000-000000000000}") { + QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) ); + if (idValue.isNull()) { + _errorString = "Id value invalid UUID string: " + idString; + return false; + } + parsedEntities["Id"] = idValue; + } } else if (key == "Version") { if (gotVersion) { _errorString = "Duplicate Version entries"; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 32ee72ea1c..20ba3cde60 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -82,11 +82,11 @@ void OctreePersistThread::start() { } if (data.readOctreeDataInfoFromData(_cachedJSONData)) { - qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; + qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.dataVersion << ")"; packet->writePrimitive(true); auto id = data.id.toRfc4122(); packet->write(id); - packet->writePrimitive(data.version); + packet->writePrimitive(data.dataVersion); } else { _cachedJSONData.clear(); qCWarning(octree) << "No octree data found"; @@ -144,8 +144,8 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointersetOctreeVersionInfo(data.id, data.version); + qDebug() << "Setting entity version info to: " << data.id << data.dataVersion; + _tree->setOctreeVersionInfo(data.id, data.dataVersion); } bool persistentFileRead; diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index 12a9b12adc..9809d02866 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -245,7 +245,7 @@ void OculusMobileDisplayPlugin::updatePresentPose() { }); } -void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compsiteFramebuffer) { +void OculusMobileDisplayPlugin::internalPresent() { VrHandler::pollTask(); if (!vrActive()) { @@ -253,12 +253,8 @@ void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& c return; } - GLuint sourceTexture = 0; - glm::uvec2 sourceSize; - if (compsiteFramebuffer) { - sourceTexture = getGLBackend()->getTextureID(compsiteFramebuffer->getRenderBuffer(0)); - sourceSize = { compsiteFramebuffer->getWidth(), compsiteFramebuffer->getHeight() }; - } + auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() }; VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking); _presentRate.increment(); } diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h index b5f7aa57b0..a98989655e 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h @@ -54,8 +54,8 @@ protected: void uncustomizeContext() override; void updatePresentPose() override; - void internalPresent(const gpu::FramebufferPointer&) override; - void hmdPresent(const gpu::FramebufferPointer&) override { throw std::runtime_error("Unused"); } + void internalPresent() override; + void hmdPresent() override { throw std::runtime_error("Unused"); } bool isHmdMounted() const override; bool alwaysPresent() const override { return true; } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index a5f1a0598f..247550a9d0 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -436,7 +436,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const float z = scale.z; float radius = 0.5f * sqrtf(0.5f * (x * x + z * z)); float halfHeight = 0.5f * scale.y - radius; - float MIN_HALF_HEIGHT = 0.1f; + float MIN_HALF_HEIGHT = 0.0f; if (halfHeight < MIN_HALF_HEIGHT) { halfHeight = MIN_HALF_HEIGHT; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4d210c96c5..6abe5c3899 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -217,9 +217,8 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } return MOTION_TYPE_DYNAMIC; } - if (_entity->isMovingRelativeToParent() || - _entity->hasActions() || - _entity->hasGrabs() || + if (_entity->hasActions() || + _entity->isMovingRelativeToParent() || _entity->hasAncestorOfType(NestableType::Avatar)) { return MOTION_TYPE_KINEMATIC; } diff --git a/libraries/physics/src/MultiSphereShape.cpp b/libraries/physics/src/MultiSphereShape.cpp index 549c8042d5..5a5a9d8dab 100644 --- a/libraries/physics/src/MultiSphereShape.cpp +++ b/libraries/physics/src/MultiSphereShape.cpp @@ -17,6 +17,14 @@ void SphereRegion::translate(const glm::vec3& translation) { line.second += translation; } } + +void SphereRegion::scale(float scale) { + for (auto &line : _lines) { + line.first *= scale; + line.second *= scale; + } +} + void SphereRegion::dump(std::vector>& outLines) { for (auto &line : _lines) { outLines.push_back(line); @@ -127,7 +135,7 @@ bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& na _jointIndex = jointIndex; _name = name; _mode = getExtractionModeByName(_name); - if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4 || kdop.size() > 200) { + if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4) { return false; } std::vector points; @@ -151,7 +159,9 @@ bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& na _midPoint /= (int)points.size(); glm::vec3 dimensions = max - min; - + if (glm::length(dimensions) == 0.0f) { + return false; + } for (size_t i = 0; i < points.size(); i++) { glm::vec3 relPoint = points[i] - _midPoint; relPoints.push_back(relPoint); @@ -343,6 +353,7 @@ void MultiSphereShape::connectSpheres(int index1, int index2, bool onlyEdges) { } void MultiSphereShape::calculateDebugLines() { + std::vector radiuses; if (_spheres.size() == 1) { auto sphere = _spheres[0]; calculateSphereLines(_debugLines, sphere._position, sphere._radius); @@ -351,41 +362,25 @@ void MultiSphereShape::calculateDebugLines() { } else if (_spheres.size() == 4) { std::vector axes; axes.resize(8); + const float AXIS_DOT_THRESHOLD = 0.3f; for (size_t i = 0; i < CORNER_SIGNS.size(); i++) { - for (size_t j = 0; j < 4; j++) { + for (size_t j = 0; j < _spheres.size(); j++) { auto axis = _spheres[j]._position - _midPoint; - glm::vec3 sign = { axis.x != 0.0f ? glm::abs(axis.x) / axis.x : 0.0f, - axis.x != 0.0f ? glm::abs(axis.y) / axis.y : 0.0f , - axis.z != 0.0f ? glm::abs(axis.z) / axis.z : 0.0f }; - bool add = false; - if (sign.x == 0.0f) { - if (sign.y == CORNER_SIGNS[i].y && sign.z == CORNER_SIGNS[i].z) { - add = true; - } - } else if (sign.y == 0.0f) { - if (sign.x == CORNER_SIGNS[i].x && sign.z == CORNER_SIGNS[i].z) { - add = true; - } - } else if (sign.z == 0.0f) { - if (sign.x == CORNER_SIGNS[i].x && sign.y == CORNER_SIGNS[i].y) { - add = true; - } - } else if (sign == CORNER_SIGNS[i]) { - add = true; - } - if (add) { + if (glm::length(axes[i]) == 0.0f && glm::length(axis) > 0.0f && glm::dot(CORNER_SIGNS[i], glm::normalize(axis)) > AXIS_DOT_THRESHOLD) { + radiuses.push_back(_spheres[j]._radius); axes[i] = axis; break; } - } + } } - calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint); + calculateChamferBox(_debugLines, radiuses, axes, _midPoint); } else if (_spheres.size() == 8) { std::vector axes; for (size_t i = 0; i < _spheres.size(); i++) { + radiuses.push_back(_spheres[i]._radius); axes.push_back(_spheres[i]._position - _midPoint); } - calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint); + calculateChamferBox(_debugLines, radiuses, axes, _midPoint); } } @@ -398,9 +393,9 @@ void MultiSphereShape::connectEdges(std::vector> } } -void MultiSphereShape::calculateChamferBox(std::vector>& outLines, const float& radius, const std::vector& axes, const glm::vec3& translation) { +void MultiSphereShape::calculateChamferBox(std::vector>& outLines, const std::vector& radiuses, const std::vector& axes, const glm::vec3& translation) { std::vector> sphereLines; - calculateSphereLines(sphereLines, glm::vec3(0.0f), radius); + calculateSphereLines(sphereLines, glm::vec3(0.0f), radiuses[0]); std::vector regions = { SphereRegion({ 1.0f, 1.0f, 1.0f }), @@ -417,6 +412,7 @@ void MultiSphereShape::calculateChamferBox(std::vector>& outLines); void extractEdges(bool reverseY = false); void translate(const glm::vec3& translation); + void scale(float scale); void dump(std::vector>& outLines); const glm::vec3& getDirection() const { return _direction; } const std::vector& getEdgesX() const { return _edgesX; } @@ -94,7 +95,7 @@ private: void calculateSphereLines(std::vector>& outLines, const glm::vec3& center, const float& radius, const int& subdivisions = DEFAULT_SPHERE_SUBDIVISIONS, const glm::vec3& direction = Vectors::UNIT_Y, const float& percentage = 1.0f, std::vector* edge = nullptr); - void calculateChamferBox(std::vector>& outLines, const float& radius, const std::vector& axes, const glm::vec3& translation); + void calculateChamferBox(std::vector>& outLines, const std::vector& radiuses, const std::vector& axes, const glm::vec3& translation); void connectEdges(std::vector>& outLines, const std::vector& edge1, const std::vector& edge2, bool reverse = false); void connectSpheres(int index1, int index2, bool onlyEdges = false); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 71db87557c..47503e8f85 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -2,12 +2,6 @@ #include - -const DisplayPlugin::HUDOperator DisplayPlugin::DEFAULT_HUD_OPERATOR{ std::function() }; - -DisplayPlugin::DisplayPlugin() : _hudOperator{ DEFAULT_HUD_OPERATOR } { -} - int64_t DisplayPlugin::getPaintDelayUsecs() const { std::lock_guard lock(_paintDelayMutex); return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0; @@ -41,8 +35,8 @@ void DisplayPlugin::waitForPresent() { } } -std::function DisplayPlugin::getHUDOperator() { - HUDOperator hudOperator; +std::function DisplayPlugin::getHUDOperator() { + std::function hudOperator; { QMutexLocker locker(&_presentMutex); hudOperator = _hudOperator; @@ -54,5 +48,3 @@ glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const { static const glm::mat4 xform; return xform; } - - diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9194fde3ac..aa52e57c3f 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -121,8 +121,6 @@ class DisplayPlugin : public Plugin, public HmdDisplay { Q_OBJECT using Parent = Plugin; public: - DisplayPlugin(); - virtual int getRequiredThreadCount() const { return 0; } virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } @@ -216,8 +214,7 @@ public: void waitForPresent(); float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec - using HUDOperator = std::function; - virtual HUDOperator getHUDOperator() final; + std::function getHUDOperator(); static const QString& MENU_PATH(); @@ -234,8 +231,7 @@ protected: gpu::ContextPointer _gpuContext; - static const HUDOperator DEFAULT_HUD_OPERATOR; - HUDOperator _hudOperator; + std::function _hudOperator { std::function() }; MovingAverage _movingAveragePresent; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index b1104b8aad..2634784f3a 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -416,7 +416,7 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMo void ModelMeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("ModelMeshPartPayload::render"); - if (!args) { + if (!args || (args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && _cauterized)) { return; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index deae91dda9..641fc81487 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -107,6 +107,7 @@ public: void render(RenderArgs* args) override; void setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning); + void setCauterized(bool cauterized) { _cauterized = cauterized; } // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch) override; @@ -138,6 +139,8 @@ private: gpu::BufferPointer _meshBlendshapeBuffer; int _meshNumVertices; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; + bool _cauterized { false }; + }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9c7df54cda..ff0fa4ac3b 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -224,6 +224,7 @@ void Model::updateRenderItems() { PrimitiveMode primitiveMode = self->getPrimitiveMode(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); + bool cauterized = self->isCauterized(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -237,7 +238,7 @@ void Model::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { @@ -261,6 +262,7 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + data.setCauterized(cauterized); data.updateKey(renderItemKeyGlobalFlags); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); @@ -935,6 +937,23 @@ bool Model::isGroupCulled() const { return _renderItemKeyGlobalFlags.isSubMetaCulled(); } +void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) { + if (Model::isCauterized() != cauterized) { + _cauterized = cauterized; + if (!scene) { + _needsFixupInScene = true; + return; + } + render::Transaction transaction; + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, [cauterized](ModelMeshPartPayload& data) { + data.setCauterized(cauterized); + }); + } + scene->enqueueTransaction(transaction); + } +} + const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { return _renderItemKeyGlobalFlags; } @@ -1302,6 +1321,8 @@ void Model::scaleToFit() { // size is our "target size in world space" // we need to set our model scale so that the extents of the mesh, fit in a box that size... glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; + const glm::vec3 MIN_MESH_DIMENSIONS { 1.0e-6f }; // one micrometer + meshDimensions = glm::max(meshDimensions, MIN_MESH_DIMENSIONS); glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions; setScaleInternal(rescaleDimensions); _scaledToFit = true; @@ -1557,18 +1578,40 @@ void Model::applyMaterialMapping() { continue; } - auto materialLoaded = [this, networkMaterialResource, shapeIDs, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() { + // This needs to be precomputed before the lambda, since the lambdas could be called out of order + std::unordered_map priorityMapPerResource; + for (auto shapeID : shapeIDs) { + priorityMapPerResource[shapeID] = ++_priorityMap[shapeID]; + } + + auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() { if (networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) { return; } render::Transaction transaction; - auto networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]]; + std::shared_ptr networkMaterial; + { + QString url = networkMaterialResource->getURL().toString(); + bool foundMaterialName = false; + if (url.contains("#")) { + auto split = url.split("#"); + std::string materialName = split.last().toStdString(); + auto networkMaterialIter = networkMaterialResource->parsedMaterials.networkMaterials.find(materialName); + if (networkMaterialIter != networkMaterialResource->parsedMaterials.networkMaterials.end()) { + networkMaterial = networkMaterialIter->second; + foundMaterialName = true; + } + } + if (!foundMaterialName) { + networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]]; + } + } for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); - graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, ++_priorityMap[shapeID]); + graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID)); _materialMapping[shapeID].push_back(material); transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d1d4a7d3d1..1431b5e3f9 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -126,6 +126,9 @@ public: void setHifiRenderLayer(render::hifi::Layer layer, const render::ScenePointer& scene = nullptr); + bool isCauterized() const { return _cauterized; } + void setCauterized(bool value, const render::ScenePointer& scene); + // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. const render::ItemKey getRenderItemKeyGlobalFlags() const; @@ -397,7 +400,7 @@ protected: glm::vec3 _translation; // this is the translation in world coordinates to the model's registration point glm::quat _rotation; - glm::vec3 _scale; + glm::vec3 _scale { 1.0f }; glm::vec3 _overrideTranslation; glm::quat _overrideRotation; @@ -502,6 +505,7 @@ protected: // For this to work, a Meta RI must exists and knows about the RIs of this Model. // render::ItemKey _renderItemKeyGlobalFlags; + bool _cauterized { false }; bool shouldInvalidatePayloadShapeKey(int meshIndex); diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index e021465ff3..b1a62625b2 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -126,8 +126,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra if (inputs) { batch.setFramebuffer(inputs); } - if (renderContext->args->_hudOperator && renderContext->args->_blitFramebuffer) { - renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_blitFramebuffer, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); } }); #endif diff --git a/libraries/render-utils/src/RenderHifi.h b/libraries/render-utils/src/RenderHifi.h index c489a0ad1d..8d3fe7e4d1 100644 --- a/libraries/render-utils/src/RenderHifi.h +++ b/libraries/render-utils/src/RenderHifi.h @@ -23,9 +23,9 @@ namespace render { // Tag is the alias names of render::ItemKey::Tag combinations used in the Hifi Render Engine enum Tag : uint8_t { - TAG_NONE = render::ItemKey::TAG_BITS_NONE, // No Tags at all - TAG_MAIN_VIEW = render::ItemKey::TAG_BITS_0, // Main view - TAG_SECONDARY_VIEW = render::ItemKey::TAG_BITS_1, // Secondary View + TAG_NONE = render::ItemKey::TAG_BITS_NONE, // No Tags at all + TAG_MAIN_VIEW = render::ItemKey::TAG_BITS_0, // Main view + TAG_SECONDARY_VIEW = render::ItemKey::TAG_BITS_1, // Secondary View TAG_ALL_VIEWS = TAG_MAIN_VIEW | TAG_SECONDARY_VIEW, // All views }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 5456453d8a..a261deefb8 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -58,10 +58,6 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende initZPassPipelines(*shapePlumber, state, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); } - // FIXME: calling this here before the zones/lights are drawn during the deferred/forward passes means we're actually using the frames from the previous draw - // Fetch the current frame stacks from all the stages - // Starting with the Light Frame genreated in previous tasks - const auto setupOutput = task.addJob("ShadowSetup", input); const auto queryResolution = setupOutput.getN(1); const auto shadowFrame = setupOutput.getN(3); @@ -99,7 +95,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto cascadeSetupOutput = task.addJob(jobName, shadowFrame, i, tagBits, tagMask); + const auto cascadeSetupOutput = task.addJob(jobName, shadowFrame, i, shadowCasterReceiverFilter); const auto shadowFilter = cascadeSetupOutput.getN(0); auto antiFrustum = render::Varying(ViewFrustumPointer()); cascadeFrustums[i] = cascadeSetupOutput.getN(1); @@ -452,8 +448,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = shadowFrame->_objects[0]; if (globalShadow && _cascadeIndex < globalShadow->getCascadeCount()) { - // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + output.edit0() = _filter; // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -551,7 +546,6 @@ void CullShadowBounds::run(const render::RenderContextPointer& renderContext, co assert(lightStage); const auto globalLightDir = currentKeyLight->getDirection(); auto castersFilter = render::ItemFilter::Builder(filter).withShadowCaster().build(); - const auto& receiversFilter = filter; for (auto& inItems : inShapes) { auto key = inItems.first; @@ -570,7 +564,7 @@ void CullShadowBounds::run(const render::RenderContextPointer& renderContext, co if (castersFilter.test(shapeKey)) { outItems->second.emplace_back(item); outBounds += item.bound; - } else if (receiversFilter.test(shapeKey)) { + } else { // Receivers are not rendered but they still increase the bounds of the shadow scene // although only in the direction of the light direction so as to have a correct far // distance without decreasing the near distance. @@ -585,7 +579,7 @@ void CullShadowBounds::run(const render::RenderContextPointer& renderContext, co if (castersFilter.test(shapeKey)) { outItems->second.emplace_back(item); outBounds += item.bound; - } else if (receiversFilter.test(shapeKey)) { + } else { // Receivers are not rendered but they still increase the bounds of the shadow scene // although only in the direction of the light direction so as to have a correct far // distance without decreasing the near distance. diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7e7d59763e..4dc6f3073f 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -134,15 +134,13 @@ public: using Outputs = render::VaryingSet3; using JobModel = render::Job::ModelIO; - RenderShadowCascadeSetup(unsigned int cascadeIndex, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : - _cascadeIndex(cascadeIndex), _tagBits(tagBits), _tagMask(tagMask) {} + RenderShadowCascadeSetup(unsigned int cascadeIndex, render::ItemFilter filter) : _cascadeIndex(cascadeIndex), _filter(filter) {} void run(const render::RenderContextPointer& renderContext, const Inputs& input, Outputs& output); private: unsigned int _cascadeIndex; - uint8_t _tagBits { 0x00 }; - uint8_t _tagMask { 0x00 }; + render::ItemFilter _filter; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 8b2fff68c6..78e9909222 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -61,7 +61,7 @@ namespace render { class Args { public: - enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE, SECONDARY_CAMERA_RENDER_MODE }; + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, MIRROR_RENDER_MODE, SECONDARY_CAMERA_RENDER_MODE }; enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD }; enum DebugFlags { RENDER_DEBUG_NONE = 0, @@ -131,7 +131,7 @@ namespace render { render::ScenePointer _scene; int8_t _cameraMode { -1 }; - std::function _hudOperator; + std::function _hudOperator; gpu::TexturePointer _hudTexture; }; diff --git a/libraries/render/src/render/RenderFetchCullSortTask.h b/libraries/render/src/render/RenderFetchCullSortTask.h index 1b1e4a5d8f..0b475614a1 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.h +++ b/libraries/render/src/render/RenderFetchCullSortTask.h @@ -39,7 +39,7 @@ public: RenderFetchCullSortTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask); }; #endif // hifi_RenderFetchCullSortTask_h diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 1850261c99..16d1d49d9f 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -47,6 +47,10 @@ void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) { _queriedTransitions.emplace_back(id, func); } +void Transaction::transitionFinishedOperator(ItemID id, TransitionFinishedFunc func) { + _transitionFinishedOperators.emplace_back(id, func); +} + void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { _updatedItems.emplace_back(id, functor); } @@ -75,6 +79,7 @@ void Transaction::reserve(const std::vector& transactionContainer) size_t addedTransitionsCount = 0; size_t queriedTransitionsCount = 0; size_t reAppliedTransitionsCount = 0; + size_t transitionFinishedOperatorsCount = 0; size_t highlightResetsCount = 0; size_t highlightRemovesCount = 0; size_t highlightQueriesCount = 0; @@ -85,6 +90,7 @@ void Transaction::reserve(const std::vector& transactionContainer) updatedItemsCount += transaction._updatedItems.size(); resetSelectionsCount += transaction._resetSelections.size(); addedTransitionsCount += transaction._addedTransitions.size(); + transitionFinishedOperatorsCount += transaction._transitionFinishedOperators.size(); queriedTransitionsCount += transaction._queriedTransitions.size(); reAppliedTransitionsCount += transaction._reAppliedTransitions.size(); highlightResetsCount += transaction._highlightResets.size(); @@ -99,6 +105,7 @@ void Transaction::reserve(const std::vector& transactionContainer) _addedTransitions.reserve(addedTransitionsCount); _queriedTransitions.reserve(queriedTransitionsCount); _reAppliedTransitions.reserve(reAppliedTransitionsCount); + _transitionFinishedOperators.reserve(transitionFinishedOperatorsCount); _highlightResets.reserve(highlightResetsCount); _highlightRemoves.reserve(highlightRemovesCount); _highlightQueries.reserve(highlightQueriesCount); @@ -142,6 +149,7 @@ void Transaction::merge(Transaction&& transaction) { moveElements(_resetSelections, transaction._resetSelections); moveElements(_addedTransitions, transaction._addedTransitions); moveElements(_queriedTransitions, transaction._queriedTransitions); + moveElements(_transitionFinishedOperators, transaction._transitionFinishedOperators); moveElements(_reAppliedTransitions, transaction._reAppliedTransitions); moveElements(_highlightResets, transaction._highlightResets); moveElements(_highlightRemoves, transaction._highlightRemoves); @@ -156,6 +164,7 @@ void Transaction::merge(const Transaction& transaction) { copyElements(_addedTransitions, transaction._addedTransitions); copyElements(_queriedTransitions, transaction._queriedTransitions); copyElements(_reAppliedTransitions, transaction._reAppliedTransitions); + copyElements(_transitionFinishedOperators, transaction._transitionFinishedOperators); copyElements(_highlightResets, transaction._highlightResets); copyElements(_highlightRemoves, transaction._highlightRemoves); copyElements(_highlightQueries, transaction._highlightQueries); @@ -168,6 +177,7 @@ void Transaction::clear() { _resetSelections.clear(); _addedTransitions.clear(); _queriedTransitions.clear(); + _transitionFinishedOperators.clear(); _reAppliedTransitions.clear(); _highlightResets.clear(); _highlightRemoves.clear(); @@ -271,6 +281,7 @@ void Scene::processTransactionFrame(const Transaction& transaction) { transitionItems(transaction._addedTransitions); reApplyTransitions(transaction._reAppliedTransitions); queryTransitionItems(transaction._queriedTransitions); + resetTransitionFinishedOperator(transaction._transitionFinishedOperators); // Update the numItemsAtomic counter AFTER the pending changes went through _numAllocatedItems.exchange(maxID); @@ -394,7 +405,7 @@ void Scene::transitionItems(const Transaction::TransitionAdds& transactions) { // Only remove if: // transitioning to something other than none or we're transitioning to none from ELEMENT_LEAVE_DOMAIN or USER_LEAVE_DOMAIN const auto& oldTransitionType = transitionStage->getTransition(transitionId).eventType; - if (transitionType != Transition::NONE || !(oldTransitionType == Transition::ELEMENT_LEAVE_DOMAIN || oldTransitionType == Transition::USER_LEAVE_DOMAIN)) { + if (transitionType != oldTransitionType) { resetItemTransition(itemId); } } @@ -440,6 +451,23 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti } } +void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& operators) { + for (auto& finishedOperator : operators) { + auto itemId = std::get<0>(finishedOperator); + const auto& item = _items[itemId]; + auto func = std::get<1>(finishedOperator); + + if (item.exist() && func != nullptr) { + TransitionStage::Index transitionId = item.getTransitionId(); + if (!TransitionStage::isIndexInvalid(transitionId)) { + _transitionFinishedOperatorMap[transitionId].emplace_back(func); + } else if (func) { + func(); + } + } + } +} + void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { auto outlineStage = getStage(HighlightStage::getName()); if (outlineStage) { @@ -526,9 +554,18 @@ void Scene::setItemTransition(ItemID itemId, Index transitionId) { void Scene::resetItemTransition(ItemID itemId) { auto& item = _items[itemId]; - if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { + TransitionStage::Index transitionId = item.getTransitionId(); + if (!render::TransitionStage::isIndexInvalid(transitionId)) { auto transitionStage = getStage(TransitionStage::getName()); - transitionStage->removeTransition(item.getTransitionId()); + + auto finishedOperators = _transitionFinishedOperatorMap[transitionId]; + for (auto finishedOperator : finishedOperators) { + if (finishedOperator) { + finishedOperator(); + } + } + _transitionFinishedOperatorMap.erase(transitionId); + transitionStage->removeTransition(transitionId); setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); } } @@ -587,4 +624,4 @@ void Scene::resetStage(const Stage::Name& name, const StagePointer& stage) { } else { (*found).second = stage; } -} \ No newline at end of file +} diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index f00c74775d..08fbf33bcd 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -32,12 +32,15 @@ class Scene; // These changes must be expressed through the corresponding command from the Transaction // THe Transaction is then queued on the Scene so all the pending transactions can be consolidated and processed at the time // of updating the scene before it s rendered. -// +// + + class Transaction { friend class Scene; public: typedef std::function TransitionQueryFunc; + typedef std::function TransitionFinishedFunc; typedef std::function SelectionHighlightQueryFunc; Transaction() {} @@ -52,6 +55,7 @@ public: void removeTransitionFromItem(ItemID id); void reApplyTransitionToItem(ItemID id); void queryTransitionOnItem(ItemID id, TransitionQueryFunc func); + void transitionFinishedOperator(ItemID id, TransitionFinishedFunc func); template void updateItem(ItemID id, std::function func) { updateItem(id, std::make_shared>(func)); @@ -84,6 +88,7 @@ protected: using Update = std::tuple; using TransitionAdd = std::tuple; using TransitionQuery = std::tuple; + using TransitionFinishedOperator = std::tuple; using TransitionReApply = ItemID; using SelectionReset = Selection; using HighlightReset = std::tuple; @@ -95,6 +100,7 @@ protected: using Updates = std::vector; using TransitionAdds = std::vector; using TransitionQueries = std::vector; + using TransitionFinishedOperators = std::vector; using TransitionReApplies = std::vector; using SelectionResets = std::vector; using HighlightResets = std::vector; @@ -107,6 +113,7 @@ protected: TransitionAdds _addedTransitions; TransitionQueries _queriedTransitions; TransitionReApplies _reAppliedTransitions; + TransitionFinishedOperators _transitionFinishedOperators; SelectionResets _resetSelections; HighlightResets _highlightResets; HighlightRemoves _highlightRemoves; @@ -208,6 +215,7 @@ protected: ItemIDSet _masterNonspatialSet; void resetItems(const Transaction::Resets& transactions); + void resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& transactions); void removeItems(const Transaction::Removes& transactions); void updateItems(const Transaction::Updates& transactions); void transitionItems(const Transaction::TransitionAdds& transactions); @@ -223,6 +231,8 @@ protected: mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method SelectionMap _selections; + std::unordered_map> _transitionFinishedOperatorMap; + void resetSelections(const Transaction::SelectionResets& transactions); // More actions coming to selections soon: // void removeFromSelection(const Selection& selection); diff --git a/libraries/render/src/render/Transition.h b/libraries/render/src/render/Transition.h index 30bda8aa2a..eca41e9d6c 100644 --- a/libraries/render/src/render/Transition.h +++ b/libraries/render/src/render/Transition.h @@ -50,4 +50,4 @@ namespace render { typedef std::vector TransitionTypes; } -#endif // hifi_render_Transition_h \ No newline at end of file +#endif // hifi_render_Transition_h diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index a6801dcdcb..c97ea10329 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -116,15 +116,25 @@ protected: * @param {AudioInjector.AudioInjectorOptions} [injectorOptions={}] - Audio injector configuration. * @returns {AudioInjector} The audio injector that plays the audio file. * @example Play a sound. - * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); - * var injector; - * var injectorOptions = { - * position: MyAvatar.position - * }; + * var sound = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav"); * - * Script.setTimeout(function () { // Give the sound time to load. - * injector = Audio.playSound(sound, injectorOptions); - * }, 1000); + * function playSound() { + * var injectorOptions = { + * position: MyAvatar.position + * }; + * var injector = Audio.playSound(sound, injectorOptions); + * } + * + * function onSoundReady() { + * sound.ready.disconnect(onSoundReady); + * playSound(); + * } + * + * if (sound.downloaded) { + * playSound(); + * } else { + * sound.ready.connect(onSoundReady); + * } */ Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a4fd2540d4..7abb63ca1c 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -163,7 +163,7 @@ ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, const QString& scriptContents, const QString& fileNameString) { ScriptEngine* engine = new ScriptEngine(context, scriptContents, fileNameString); - ScriptEnginePointer engineSP = ScriptEnginePointer(engine); + ScriptEnginePointer engineSP = ScriptEnginePointer(engine, &QObject::deleteLater); auto scriptEngines = DependencyManager::get(); scriptEngines->addScriptEngine(qSharedPointerCast(engineSP)); engine->setScriptEngines(scriptEngines); @@ -267,7 +267,7 @@ void ScriptEngine::disconnectNonEssentialSignals() { QThread* workerThread; // Ensure the thread should be running, and does exist if (_isRunning && _isThreaded && (workerThread = thread())) { - connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); + connect(this, &QObject::destroyed, workerThread, &QThread::quit); connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); } } @@ -381,7 +381,7 @@ void ScriptEngine::runInThread() { // the script engine, make sure to add code to "reconnect" them to the // disconnectNonEssentialSignals() method connect(workerThread, &QThread::started, this, &ScriptEngine::run); - connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); + connect(this, &QObject::destroyed, workerThread, &QThread::quit); connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); @@ -579,6 +579,8 @@ static void scriptableResourceFromScriptValue(const QScriptValue& value, Scripta } /**jsdoc + * The Resource API provides values that define the possible loading states of a resource. + * * @namespace Resource * * @hifi-interface @@ -587,7 +589,7 @@ static void scriptableResourceFromScriptValue(const QScriptValue& value, Scripta * @hifi-server-entity * @hifi-assignment-client * - * @property {Resource.State} State + * @property {Resource.State} State - The possible loading states of a resource. Read-only. */ static QScriptValue createScriptableResourcePrototype(ScriptEnginePointer engine) { auto prototype = engine->newObject(); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index d55a63b960..5166cb7a0b 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -69,7 +69,23 @@ const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.915461599 const float DEFAULT_AVATAR_MAX_WALKING_SPEED = 2.6f; // meters / second const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second -const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; +const float DEFAULT_AVATAR_MAX_SPRINT_SPEED = 3.4f; // meters / second +const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; // meters / second + +const float ANALOG_AVATAR_MAX_WALKING_SPEED = 6.0f; // meters / second +const float ANALOG_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second +const float ANALOG_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float ANALOG_AVATAR_MAX_SPRINT_SPEED = 8.0f; // meters / second +const float ANALOG_AVATAR_GEAR_1 = 0.2f; // meters / second +const float ANALOG_AVATAR_GEAR_2 = 0.4f; // meters / second +const float ANALOG_AVATAR_GEAR_3 = 0.6f; // meters / second +const float ANALOG_AVATAR_GEAR_4 = 0.8f; // meters / second +const float ANALOG_AVATAR_GEAR_5 = 1.0f; // meters / second + +const float ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED = 10.0f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.42f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_SPRINT_SPEED = 20.0f; // meters / second const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world) const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor) @@ -86,6 +102,6 @@ static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meter static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_RADIUS = 0.5f * MIN_AVATAR_HEIGHT; static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; -static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f; +static const float AVATAR_SPRINT_SPEED_SCALAR = 2.0f; #endif // hifi_AvatarConstants_h diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 3f2f7cd7fb..b6fca03403 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const { return glm::normalize(glm::cross(edge1, edge2)); } +float Triangle::getArea() const { + glm::vec3 edge1 = v1 - v0; + glm::vec3 edge2 = v2 - v0; + return 0.5f * glm::length(glm::cross(edge1, edge2)); +} + Triangle Triangle::operator*(const glm::mat4& transform) const { return { glm::vec3(transform * glm::vec4(v0, 1.0f)), diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index ce25a4f559..764eeb1500 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -132,6 +132,7 @@ public: glm::vec3 v1; glm::vec3 v2; glm::vec3 getNormal() const; + float getArea() const; Triangle operator*(const glm::mat4& transform) const; }; diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index 272538e26d..018636ad5a 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -7,6 +7,7 @@ // #include "Profile.h" +#include Q_LOGGING_CATEGORY(trace_app, "trace.app") Q_LOGGING_CATEGORY(trace_app_detail, "trace.app.detail") @@ -41,14 +42,22 @@ static bool tracingEnabled() { return DependencyManager::isSet() && DependencyManager::get()->isEnabled(); } -Duration::Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : _name(name), _category(category) { +DurationBase::DurationBase(const QLoggingCategory& category, const QString& name) : _name(name), _category(category) { +} + +Duration::Duration(const QLoggingCategory& category, + const QString& name, + uint32_t argbColor, + uint64_t payload, + const QVariantMap& baseArgs) : + DurationBase(category, name) { if (tracingEnabled() && category.isDebugEnabled()) { QVariantMap args = baseArgs; args["nv_payload"] = QVariant::fromValue(payload); tracing::traceEvent(_category, _name, tracing::DurationBegin, "", args); #if defined(NSIGHT_TRACING) - nvtxEventAttributes_t eventAttrib { 0 }; + nvtxEventAttributes_t eventAttrib{ 0 }; eventAttrib.version = NVTX_VERSION; eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE; eventAttrib.colorType = NVTX_COLOR_ARGB; @@ -98,3 +107,17 @@ void Duration::endRange(const QLoggingCategory& category, uint64_t rangeId) { #endif } +ConditionalDuration::ConditionalDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime) : + DurationBase(category, name), _startTime(tracing::Tracer::now()), _minTime(minTime * USECS_PER_MSEC) { +} + +ConditionalDuration::~ConditionalDuration() { + if (tracingEnabled() && _category.isDebugEnabled()) { + auto endTime = tracing::Tracer::now(); + auto duration = endTime - _startTime; + if (duration >= _minTime) { + tracing::traceEvent(_category, _startTime, _name, tracing::DurationBegin); + tracing::traceEvent(_category, endTime, _name, tracing::DurationEnd); + } + } +} diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index dc2ed6e754..e7084b4f79 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -37,17 +37,31 @@ Q_DECLARE_LOGGING_CATEGORY(trace_startup) Q_DECLARE_LOGGING_CATEGORY(trace_workload) Q_DECLARE_LOGGING_CATEGORY(trace_baker) -class Duration { +class DurationBase { + +protected: + DurationBase(const QLoggingCategory& category, const QString& name); + const QString _name; + const QLoggingCategory& _category; +}; + +class Duration : public DurationBase { public: Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor = 0xff0000ff, uint64_t payload = 0, const QVariantMap& args = QVariantMap()); ~Duration(); static uint64_t beginRange(const QLoggingCategory& category, const char* name, uint32_t argbColor); static void endRange(const QLoggingCategory& category, uint64_t rangeId); +}; + +class ConditionalDuration : public DurationBase { +public: + ConditionalDuration(const QLoggingCategory& category, const QString& name, uint32_t minTime); + ~ConditionalDuration(); private: - QString _name; - const QLoggingCategory& _category; + const int64_t _startTime; + const int64_t _minTime; }; @@ -95,6 +109,7 @@ inline void metadata(const QString& metadataType, const QVariantMap& args) { } #define PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name); +#define PROFILE_RANGE_IF_LONGER(category, name, ms) ConditionalDuration profileRangeThis(trace_##category(), name, ms); #define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); #define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor) #define PROFILE_RANGE_END(category, rangeId) Duration::endRange(trace_##category(), rangeId) diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 4d97f43e6a..866698adeb 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -68,19 +68,28 @@ const QUuid SpatiallyNestable::getParentID() const { void SpatiallyNestable::setParentID(const QUuid& parentID) { bumpAncestorChainRenderableVersion(); + bool success = false; + auto parent = getParentPointer(success); + bool parentChanged = false; _idLock.withWriteLock([&] { if (_parentID != parentID) { + parentChanged = true; _parentID = parentID; _parentKnowsMe = false; } }); + if (parentChanged && success && parent) { + parent->recalculateChildCauterization(); + } + if (!_parentKnowsMe) { - bool success = false; - auto parent = getParentPointer(success); + success = false; + parent = getParentPointer(success); if (success && parent) { bumpAncestorChainRenderableVersion(); parent->updateQueryAACube(); + parent->recalculateChildCauterization(); } } } @@ -175,8 +184,9 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const { void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; - auto parent = _parent.lock(); - if (parent) { + bool success = false; + auto parent = getParentPointer(success); + if (success && parent) { parent->recalculateChildCauterization(); } } diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index 3f6a2dd643..e9e77b55ae 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -176,6 +176,10 @@ void Tracer::serialize(const QString& filename) { #endif } +int64_t Tracer::now() { + return std::chrono::duration_cast(p_high_resolution_clock::now().time_since_epoch()).count(); +} + void Tracer::traceEvent(const QLoggingCategory& category, const QString& name, EventType type, qint64 timestamp, qint64 processID, qint64 threadID, @@ -226,9 +230,17 @@ void Tracer::traceEvent(const QLoggingCategory& category, return; } - auto timestamp = std::chrono::duration_cast(p_high_resolution_clock::now().time_since_epoch()).count(); + traceEvent(category, name, type, now(), id, args, extra); +} + +void Tracer::traceEvent(const QLoggingCategory& category, + const QString& name, EventType type, int64_t timestamp, const QString& id, + const QVariantMap& args, const QVariantMap& extra) { + if (!_enabled && type != Metadata) { + return; + } + auto processID = QCoreApplication::applicationPid(); auto threadID = int64_t(QThread::currentThreadId()); - traceEvent(category, name, type, timestamp, processID, threadID, id, args, extra); } diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 1e1326968f..917530d6ca 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -78,11 +78,18 @@ struct TraceEvent { class Tracer : public Dependency { public: + static int64_t now(); void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()); + void traceEvent(const QLoggingCategory& category, + const QString& name, EventType type, + int64_t timestamp, + const QString& id = "", + const QVariantMap& args = QVariantMap(), const QVariantMap& extra = QVariantMap()); + void startTracing(); void stopTracing(); void serialize(const QString& file); @@ -101,6 +108,16 @@ private: std::mutex _eventsMutex; }; +inline void traceEvent(const QLoggingCategory& category, int64_t timestamp, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } + const auto& tracer = DependencyManager::get(); + if (tracer) { + tracer->traceEvent(category, name, type, timestamp, id, args, extra); + } +} + inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { if (!DependencyManager::isSet()) { return; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index 690a488b34..ec05cd92e2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -16,7 +16,7 @@ public: bool isSupported() const override; protected: - void hmdPresent(const gpu::FramebufferPointer&) override {} + void hmdPresent() override {} bool isHmdMounted() const override { return true; } private: diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 48440ac80f..df01591639 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -108,16 +108,13 @@ void OculusDisplayPlugin::customizeContext() { } void OculusDisplayPlugin::uncustomizeContext() { - #if 0 - if (_currentFrame && _currentFrame->framebuffer) { - // Present a final black frame to the HMD - _currentFrame->framebuffer->Bound(FramebufferTarget::Draw, [] { - Context::ClearColor(0, 0, 0, 1); - Context::Clear().ColorBuffer(); - }); - hmdPresent(); - } + // Present a final black frame to the HMD + _compositeFramebuffer->Bound(FramebufferTarget::Draw, [] { + Context::ClearColor(0, 0, 0, 1); + Context::Clear().ColorBuffer(); + }); + hmdPresent(); #endif ovr_DestroyTextureSwapChain(_session, _textureSwapChain); @@ -130,7 +127,7 @@ void OculusDisplayPlugin::uncustomizeContext() { static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC); static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC); -void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OculusDisplayPlugin::hmdPresent() { static uint64_t lastSubmitEnd = 0; if (!_customized) { @@ -160,8 +157,15 @@ void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFra auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0); render([&](gpu::Batch& batch) { - auto viewport = ivec4(uvec2(), _outputFramebuffer->getSize()); - renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, viewport, _outputFramebuffer); + batch.enableStereo(false); + batch.setFramebuffer(_outputFramebuffer); + batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize())); + batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); + batch.resetViewTransform(); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_presentPipeline); + batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index a0126d2e58..9209fd373e 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -28,7 +28,7 @@ protected: QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; } bool internalActivate() override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void hmdPresent() override; bool isHmdMounted() const override; void customizeContext() override; void uncustomizeContext() override; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index a928887866..e6b555443f 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -237,7 +237,7 @@ void OculusLegacyDisplayPlugin::uncustomizeContext() { Parent::uncustomizeContext(); } -void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OculusLegacyDisplayPlugin::hmdPresent() { if (!_hswDismissed) { ovrHSWDisplayState hswState; ovrHmd_GetHSWDisplayState(_hmd, &hswState); @@ -252,7 +252,7 @@ void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compos memset(eyePoses, 0, sizeof(ovrPosef) * 2); eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation; - GLint texture = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0)); + GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); if (_hmdWindow->makeCurrent()) { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 241d626f0c..36bdd1c792 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -39,7 +39,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void hmdPresent() override; bool isHmdMounted() const override { return true; } private: diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 3d22268472..11d941dcd0 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -511,13 +511,13 @@ void OpenVrDisplayPlugin::customizeContext() { Parent::customizeContext(); if (_threadedSubmit) { -// _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); + _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) { -// if (0 != i) { + if (0 != i) { _compositeInfos[i].texture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)); -// } + } _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture); } _submitThread->_canvas = _submitCanvas; @@ -613,17 +613,17 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return Parent::beginFrameRender(frameIndex); } -void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenVrDisplayPlugin::compositeLayers() { if (_threadedSubmit) { ++_renderingIndex; _renderingIndex %= COMPOSITING_BUFFER_SIZE; auto& newComposite = _compositeInfos[_renderingIndex]; newComposite.pose = _currentPresentFrameInfo.presentPose; - compositeFramebuffer->setRenderBuffer(0, newComposite.texture); + _compositeFramebuffer->setRenderBuffer(0, newComposite.texture); } - Parent::compositeLayers(compositeFramebuffer); + Parent::compositeLayers(); if (_threadedSubmit) { auto& newComposite = _compositeInfos[_renderingIndex]; @@ -645,13 +645,13 @@ void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi } } -void OpenVrDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenVrDisplayPlugin::hmdPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) if (_threadedSubmit) { _submitThread->waitForPresent(); } else { - GLuint glTexId = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0)); + GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); vr::Texture_t vrTexture{ (void*)(uintptr_t)glTexId, vr::TextureType_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT); vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 923a0f7a8f..265f328920 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -72,8 +72,8 @@ protected: void internalDeactivate() override; void updatePresentPose() override; - void compositeLayers(const gpu::FramebufferPointer&) override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void compositeLayers() override; + void hmdPresent() override; bool isHmdMounted() const override; void postPreview() override; diff --git a/scripts/developer/createAvatarInputsBarEntity.js b/scripts/developer/createAvatarInputsBarEntity.js new file mode 100644 index 0000000000..deb0cfdd89 --- /dev/null +++ b/scripts/developer/createAvatarInputsBarEntity.js @@ -0,0 +1,138 @@ +"use strict"; + +(function(){ + var AppUi = Script.require("appUi"); + + var ui; + + var onCreateAvatarInputsBarEntity = false; + var micBarEntity = null; + var bubbleIconEntity = null; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; + + // DPI + var ENTITY_DPI = 60.0; + // QML NATURAL DIMENSIONS + var MIC_BAR_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.048, z: 0.3}); + var BUBBLE_ICON_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.036, z: 0.3}); + // ENTITY NAMES + var MIC_BAR_NAME = "AvatarInputsMicBarEntity"; + var BUBBLE_ICON_NAME = "AvatarInputsBubbleIconEntity"; + // CONSTANTS + var LOCAL_POSITION_X_OFFSET = -0.2; + var LOCAL_POSITION_Y_OFFSET = -0.125; + var LOCAL_POSITION_Z_OFFSET = -0.5; + + function fromQml(message) { + if (message.method === "reposition") { + var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition; + var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; + var newMicBarLocalPosition, newBubbleIconLocalPosition; + if (message.x !== undefined) { + newMicBarLocalPosition = { x: -((MIC_BAR_DIMENSIONS.x) / 2) + message.x, y: micBarLocalPosition.y, z: micBarLocalPosition.z }; + newBubbleIconLocalPosition = { x: ((MIC_BAR_DIMENSIONS.x) * 1.2 / 2) + message.x, y: bubbleIconLocalPosition.y, z: bubbleIconLocalPosition.z }; + } else if (message.y !== undefined) { + newMicBarLocalPosition = { x: micBarLocalPosition.x, y: message.y, z: micBarLocalPosition.z }; + newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + message.y), z: bubbleIconLocalPosition.z }; + } else if (message.z !== undefined) { + newMicBarLocalPosition = { x: micBarLocalPosition.x, y: micBarLocalPosition.y, z: message.z }; + newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: bubbleIconLocalPosition.y, z: message.z }; + } + var micBarProps = { + localPosition: newMicBarLocalPosition + }; + var bubbleIconProps = { + localPosition: newBubbleIconLocalPosition + }; + + Entities.editEntity(micBarEntity, micBarProps); + Entities.editEntity(bubbleIconEntity, bubbleIconProps); + } else if (message.method === "setVisible") { + if (message.visible !== undefined) { + var props = { + visible: message.visible + }; + Entities.editEntity(micBarEntity, props); + Entities.editEntity(bubbleIconEntity, props); + } + } else if (message.method === "print") { + // prints the local position into the hifi log. + var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition; + var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; + console.log("mic bar local position is at " + JSON.stringify(micBarLocalPosition)); + console.log("bubble icon local position is at " + JSON.stringify(bubbleIconLocalPosition)); + } + }; + + function createEntities() { + if (micBarEntity != null && bubbleIconEntity != null) { + return; + } + // POSITIONS + var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; + var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; + var props = { + type: "Web", + name: MIC_BAR_NAME, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + localPosition: micBarLocalPosition, + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), + sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", + // cutoff alpha for detecting transparency + alpha: 0.98, + dimensions: MIC_BAR_DIMENSIONS, + dpi: ENTITY_DPI, + drawInFront: true, + userData: { + grabbable: false + }, + }; + micBarEntity = Entities.addEntity(props, "local"); + var props = { + type: "Web", + name: BUBBLE_ICON_NAME, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + localPosition: bubbleIconLocalPosition, + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), + sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", + // cutoff alpha for detecting transparency + alpha: 0.98, + dimensions: BUBBLE_ICON_DIMENSIONS, + dpi: ENTITY_DPI, + drawInFront: true, + userData: { + grabbable: false + }, + }; + bubbleIconEntity = Entities.addEntity(props, "local"); + tablet.loadQMLSource(AVATAR_INPUTS_EDIT_QML_SOURCE); + }; + function cleanup() { + if (micBarEntity) { + Entities.deleteEntity(micBarEntity); + } + if (bubbleIconEntity) { + Entities.deleteEntity(bubbleIconEntity); + } + }; + + function setup() { + ui = new AppUi({ + buttonName: "AVBAR", + home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml", + onMessage: fromQml, + onOpened: createEntities, + // onClosed: cleanup, + normalButton: "icons/tablet-icons/edit-i.svg", + activeButton: "icons/tablet-icons/edit-a.svg", + }); + }; + + setup(); + + Script.scriptEnding.connect(cleanup); + +}()); diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 3e8e0b1008..9771348377 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -353,10 +353,11 @@ function AppUi(properties) { // Close if necessary, clean up any remaining handlers, and remove the button. GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); GlobalServices.findableByChanged.disconnect(restartNotificationPoll); + that.tablet.screenChanged.disconnect(that.onScreenChanged); if (that.isOpen) { that.close(); + that.onScreenChanged("", ""); } - that.tablet.screenChanged.disconnect(that.onScreenChanged); if (that.button) { if (that.onClicked) { that.button.clicked.disconnect(that.onClicked); diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 7201cdecad..01f471a5cb 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -227,6 +227,14 @@ "speedSpread": { "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." }, + "particleShapeType": { + "tooltip": "The shape of the surface from which to emit particles.", + "jsPropertyName": "shapeType" + }, + "particleCompoundShapeURL": { + "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, "emitDimensions": { "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." }, @@ -549,6 +557,27 @@ "acceleration": { "tooltip": "A acceleration that the entity should move with, in world space." }, + "renderLayer": { + "tooltip": "The layer on which this entity is rendered." + }, + "primitiveMode": { + "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." + }, + "groupCulled": { + "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." + }, + "webColor": { + "tooltip": "The tint of the web entity." + }, + "webAlpha": { + "tooltip": "The alpha of the web entity." + }, + "maxFPS": { + "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." + }, + "scriptURL": { + "tooltip": "The URL of a script to inject into the web page." + }, "alignToGrid": { "tooltip": "Used to align entities to the grid, or floor of the environment.", "skipJSProperty": true diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 19ed3faef2..a161b40ffd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -75,6 +75,7 @@ button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); Audio.pushToTalkChanged.connect(onMuteToggled); +HMD.displayModeChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -84,6 +85,7 @@ Script.scriptEnding.connect(function () { tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); Audio.pushToTalkChanged.disconnect(onMuteToggled); + HMD.displayModeChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index e715e97575..9acc5ab123 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -43,7 +43,7 @@ if (HMD.active) { warningOverlayID = Overlays.addOverlay("text3d", { name: "Muted-Warning", - localPosition: { x: 0.0, y: -0.5, z: -1.0 }, + localPosition: { x: 0.0, y: -0.45, z: -1.0 }, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), text: warningText, textAlpha: 1, @@ -58,20 +58,6 @@ parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") }); - } else { - var textDimensions = { x: 100, y: 50 }; - warningOverlayID = Overlays.addOverlay("text", { - name: "Muted-Warning", - font: { size: 36 }, - text: warningText, - x: (Window.innerWidth - textDimensions.x) / 2, - y: (Window.innerHeight - textDimensions.y), - width: textDimensions.x, - height: textDimensions.y, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - visible: true - }); } } @@ -141,4 +127,4 @@ Audio.mutedChanged.connect(startOrStopPoll); Audio.warnWhenMutedChanged.connect(startOrStopPoll); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE diff --git a/scripts/system/away.js b/scripts/system/away.js index 2af43b2055..e45041139a 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -171,7 +171,7 @@ function goAway(fromStartup) { if (!previousBubbleState) { Users.toggleIgnoreRadius(); } - UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); + UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled()); UserActivityLogger.toggledAway(true); MyAvatar.isAway = true; } @@ -186,7 +186,7 @@ function goActive() { if (Users.getIgnoreRadiusEnabled() !== previousBubbleState) { Users.toggleIgnoreRadius(); - UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); + UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled()); } if (!Window.hasFocus()) { diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 6ca624872e..eca3b3dcd4 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -90,7 +90,7 @@ // Called from the C++ scripting interface to show the bubble overlay function enteredIgnoreRadius() { createOverlays(); - UserActivityLogger.bubbleActivated(); + UserActivityLogger.privacyShieldActivated(); } // Used to set the state of the bubble HUD button @@ -160,7 +160,7 @@ function onBubbleToggled(enabled, doNotLog) { writeButtonProperties(enabled); if (doNotLog !== true) { - UserActivityLogger.bubbleToggled(enabled); + UserActivityLogger.privacyShieldToggled(enabled); } if (enabled) { createOverlays(); @@ -174,7 +174,7 @@ } // Setup the bubble button - var buttonName = "BUBBLE"; + var buttonName = "SHIELD"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/bubble-i.svg", diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index 65a3671cae..ecafa3cb26 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -18,7 +18,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function() { +(function () { var MARGIN = 25; function TargetObject(entityID, entityProps) { @@ -27,12 +27,13 @@ Script.include("/~/system/libraries/controllers.js"); this.targetEntityID = null; this.targetEntityProps = null; - this.getTargetEntity = function() { + this.getTargetEntity = function () { var parentPropsLength = this.parentProps.length; if (parentPropsLength !== 0) { var targetEntity = { id: this.parentProps[parentPropsLength - 1].id, - props: this.parentProps[parentPropsLength - 1]}; + props: this.parentProps[parentPropsLength - 1] + }; this.targetEntityID = targetEntity.id; this.targetEntityProps = targetEntity.props; return targetEntity; @@ -41,7 +42,8 @@ Script.include("/~/system/libraries/controllers.js"); this.targetEntityProps = this.entityProps; return { id: this.entityID, - props: this.entityProps}; + props: this.entityProps + }; }; } @@ -62,8 +64,6 @@ Script.include("/~/system/libraries/controllers.js"); this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms this.disabled = false; var _this = this; - this.leftTrigger = 0.0; - this.rightTrigger = 0.0; this.initialControllerRotation = Quat.IDENTITY; this.currentControllerRotation = Quat.IDENTITY; this.manipulating = false; @@ -101,13 +101,9 @@ Script.include("/~/system/libraries/controllers.js"); return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND); } - this.getOffhandTrigger = function () { - return (_this.hand === RIGHT_HAND ? _this.leftTrigger : _this.rightTrigger); - } - // Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it. - this.shouldManipulateTarget = function () { - return (_this.getOffhandTrigger() > TRIGGER_ON_VALUE) ? true : false; + this.shouldManipulateTarget = function (controllerData) { + return (controllerData.triggerValues[this.getOffhand()] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.getOffhand()] > TRIGGER_ON_VALUE) ? true : false; }; // Get the delta between the current rotation and where the controller was when manipulation started. @@ -123,11 +119,15 @@ Script.include("/~/system/libraries/controllers.js"); MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal); }; - this.handToController = function() { + this.setJointRotation = function (newTargetRotLocal) { + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal); + }; + + this.handToController = function () { return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; - this.distanceGrabTimescale = function(mass, distance) { + this.distanceGrabTimescale = function (mass, distance) { var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_DISTANCE; @@ -137,7 +137,7 @@ Script.include("/~/system/libraries/controllers.js"); return timeScale; }; - this.getMass = function(dimensions, density) { + this.getMass = function (dimensions, density) { return (dimensions.x * dimensions.y * dimensions.z) * density; }; @@ -204,8 +204,8 @@ Script.include("/~/system/libraries/controllers.js"); } var farJointIndex = FAR_GRAB_JOINTS[this.hand]; this.grabID = MyAvatar.grab(targetProps.id, farJointIndex, - Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), - Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); + Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), + Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', @@ -217,7 +217,7 @@ Script.include("/~/system/libraries/controllers.js"); this.previousRoomControllerPosition = roomControllerPosition; }; - this.continueDistanceHolding = function(controllerData) { + this.continueDistanceHolding = function (controllerData) { var controllerLocation = controllerData.controllerLocations[this.hand]; var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; @@ -263,7 +263,7 @@ Script.include("/~/system/libraries/controllers.js"); var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius * RADIAL_GRAB_AMPLIFIER); } // don't let grabRadius go all the way to zero, because it can't come back from that @@ -278,7 +278,7 @@ Script.include("/~/system/libraries/controllers.js"); var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); // This block handles the user's ability to rotate the object they're FarGrabbing - if (this.shouldManipulateTarget()) { + if (this.shouldManipulateTarget(controllerData)) { // Get the pose of the controller that is not grabbing. var pose = Controller.getPoseValue((this.getOffhand() ? Controller.Standard.RightHand : Controller.Standard.LeftHand)); if (pose.valid) { @@ -345,13 +345,13 @@ Script.include("/~/system/libraries/controllers.js"); otherModule.disabled = false; }; - this.updateRecommendedArea = function() { + this.updateRecommendedArea = function () { var dims = Controller.getViewportDimensions(); this.reticleMaxX = dims.x - MARGIN; this.reticleMaxY = dims.y - MARGIN; }; - this.calculateNewReticlePosition = function(intersection) { + this.calculateNewReticlePosition = function (intersection) { this.updateRecommendedArea(); var point2d = HMD.overlayFromWorldPoint(intersection); point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); @@ -359,7 +359,7 @@ Script.include("/~/system/libraries/controllers.js"); return point2d; }; - this.notPointingAtEntity = function(controllerData) { + this.notPointingAtEntity = function (controllerData) { var intersection = controllerData.rayPicks[this.hand]; var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); var entityType = entityProperty.type; @@ -372,7 +372,7 @@ Script.include("/~/system/libraries/controllers.js"); return false; }; - this.destroyContextOverlay = function(controllerData) { + this.destroyContextOverlay = function (controllerData) { if (this.entityWithContextOverlay) { ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); this.entityWithContextOverlay = false; @@ -380,7 +380,7 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.targetIsNull = function() { + this.targetIsNull = function () { var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); if (Object.keys(properties).length === 0 && this.distanceHolding) { return true; @@ -424,8 +424,6 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - this.leftTrigger = controllerData.triggerValues[LEFT_HAND]; - this.rightTrigger = controllerData.triggerValues[RIGHT_HAND]; if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { this.endFarGrabEntity(controllerData); return makeRunningValues(false, [], []); @@ -522,12 +520,12 @@ Script.include("/~/system/libraries/controllers.js"); _this.contextOverlayTimer && _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { var cotProps = Entities.getEntityProperties(rayPickInfo.objectID, - DISPATCHER_PROPERTIES); + DISPATCHER_PROPERTIES); var pointerEvent = { type: "Move", id: _this.hand + 1, // 0 is reserved for hardware mouse pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, - rayPickInfo.intersection, cotProps), + rayPickInfo.intersection, cotProps), pos3D: rayPickInfo.intersection, normal: rayPickInfo.surfaceNormal, direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), @@ -546,7 +544,7 @@ Script.include("/~/system/libraries/controllers.js"); return this.exitIfDisabled(controllerData); }; - this.exitIfDisabled = function(controllerData) { + this.exitIfDisabled = function (controllerData) { var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; var disableModule = getEnabledModuleByName(moduleName); if (disableModule) { @@ -563,10 +561,10 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], [], laserLockInfo); }; - this.calculateOffset = function(controllerData) { + this.calculateOffset = function (controllerData) { if (this.distanceHolding) { var targetProps = Entities.getEntityProperties(this.targetObject.entityID, - [ "position", "rotation", "registrationPoint", "dimensions" ]); + ["position", "rotation", "registrationPoint", "dimensions"]); return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 23457cdd85..5a51773930 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -129,6 +129,8 @@ Script.include("/~/system/libraries/controllers.js"); this.init = false; this.hand = hand; this.buttonValue = 0; + this.standardAxisLY = 0.0; + this.standardAxisRY = 0.0; this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler this.active = false; this.state = TELEPORTER_STATES.IDLE; @@ -690,6 +692,44 @@ Script.include("/~/system/libraries/controllers.js"); } }; + this.getStandardLY = function (value) { + _this.standardAxisLY = value; + }; + + this.getStandardRY = function (value) { + _this.standardAxisRY = value; + }; + + // Return value for the getDominantY and getOffhandY functions has to be inverted. + this.getDominantY = function () { + return (MyAvatar.getDominantHand() === "left") ? -(_this.standardAxisLY) : -(_this.standardAxisRY); + }; + + this.getOffhandY = function () { + return (MyAvatar.getDominantHand() === "left") ? -(_this.standardAxisRY) : -(_this.standardAxisLY); + }; + + this.getDominantHand = function () { + return (MyAvatar.getDominantHand() === "left") ? LEFT_HAND : RIGHT_HAND; + } + + this.getOffHand = function () { + return (MyAvatar.getDominantHand() === "left") ? RIGHT_HAND : LEFT_HAND; + } + + this.showReticle = function () { + return (_this.getDominantY() > TELEPORT_DEADZONE) ? true : false; + }; + + this.shouldTeleport = function () { + return (_this.getDominantY() > TELEPORT_DEADZONE && _this.getOffhandY() > TELEPORT_DEADZONE) ? true : false; + }; + + this.shouldCancel = function () { + //return (_this.getDominantY() < -TELEPORT_DEADZONE || _this.getOffhandY() < -TELEPORT_DEADZONE) ? true : false; + return (_this.getDominantY() <= TELEPORT_DEADZONE) ? true : false; + }; + this.parameters = makeDispatcherModuleParameters( 80, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], @@ -706,7 +746,7 @@ Script.include("/~/system/libraries/controllers.js"); } var otherModule = this.getOtherModule(); - if (!this.disabled && this.buttonValue !== 0 && !otherModule.active) { + if (!this.disabled && this.showReticle() && !otherModule.active && this.hand === this.getDominantHand()) { this.active = true; this.enterTeleport(); return makeRunningValues(true, [], []); @@ -715,6 +755,12 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function(controllerData, deltaTime) { + // Kill condition: + if (_this.shouldCancel()) { + _this.disableLasers(); + this.active = false; + return makeRunningValues(false, [], []); + } // Get current hand pose information to see if the pose is valid var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); @@ -778,7 +824,7 @@ Script.include("/~/system/libraries/controllers.js"); this.teleport = function(newResult, target) { var result = newResult; _this.teleportedPosition = newResult.intersection; - if (_this.buttonValue !== 0) { + if (!_this.shouldTeleport()) { return makeRunningValues(true, [], []); } @@ -801,8 +847,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - _this.setPlayAreaVisible(false, null, true); - _this.setTeleportVisible(false, null, true); + _this.setPlayAreaVisible(false, null, false); + _this.setTeleportVisible(false, null, false); Pointers.disablePointer(_this.teleportParabolaHandVisuals); Pointers.disablePointer(_this.teleportParabolaHandCollisions); Pointers.disablePointer(_this.teleportParabolaHeadVisuals); @@ -982,6 +1028,10 @@ Script.include("/~/system/libraries/controllers.js"); // Teleport actions. teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftTeleporter.buttonPress); teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); + teleportMapping.from(Controller.Standard.LY).peek().to(leftTeleporter.getStandardLY); + teleportMapping.from(Controller.Standard.RY).peek().to(leftTeleporter.getStandardRY); + teleportMapping.from(Controller.Standard.LY).peek().to(rightTeleporter.getStandardLY); + teleportMapping.from(Controller.Standard.RY).peek().to(rightTeleporter.getStandardRY); } var leftTeleporter = new Teleporter(LEFT_HAND); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index ca7d041792..c9cb61b5f5 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -15,7 +15,7 @@ var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", "grab.js", - "toggleAdvancedMovementForHandControllers.js", + //"toggleAdvancedMovementForHandControllers.js", "handTouch.js", "controllerDispatcher.js", "controllerModules/nearParentGrabOverlay.js", diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 894ea2b696..11fc88dcbd 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -282,6 +282,28 @@ function checkEditPermissionsAndUpdate() { } } +// Copies the properties in `b` into `a`. `a` will be modified. +function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; +} + +const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, +}; + +const DEFAULT_NON_DYNAMIC_PROPERTIES = { + dynamic: false, + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, +}; + const DEFAULT_ENTITY_PROPERTIES = { All: { description: "", @@ -299,26 +321,14 @@ const DEFAULT_ENTITY_PROPERTIES = { y: 0, z: 0 }, - damping: 0, angularVelocity: { x: 0, y: 0, z: 0 }, - angularDamping: 0, restitution: 0.5, friction: 0.5, density: 1000, - gravity: { - x: 0, - y: 0, - z: 0 - }, - acceleration: { - x: 0, - y: 0, - z: 0 - }, dynamic: false, }, Shape: { @@ -484,11 +494,6 @@ var toolBar = (function () { dialogWindow = null, tablet = null; - function applyProperties(originalProperties, newProperties) { - for (var key in newProperties) { - originalProperties[key] = newProperties[key]; - } - } function createNewEntity(requestedProperties) { var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); @@ -496,17 +501,23 @@ var toolBar = (function () { var properties = {}; - applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); var type = requestedProperties.type; if (type === "Box" || type === "Sphere") { - applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); } else { - applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); } // We apply the requested properties first so that they take priority over any default properties. - applyProperties(properties, requestedProperties); + copyProperties(properties, requestedProperties); + + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } if (position !== null && position !== undefined) { @@ -675,7 +686,6 @@ var toolBar = (function () { grabbable: result.grabbable }, dynamic: dynamic, - gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 } }); } } @@ -1620,7 +1630,7 @@ function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { if (entityHostTypes[i].entityHostType !== entityHostType) { if (wantDebug) { console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + - entityHostTypes[i].entityHostType); + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); } continue; } @@ -2512,7 +2522,19 @@ var PropertiesTool = function (opts) { type: 'propertyRangeReply', propertyRanges: propertyRanges, }); - } + } else if (data.type === "materialTargetRequest") { + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + var parentModel = properties.parentID !== Uuid.NULL && + Entities.getEntityProperties(properties.parentID, ["type"]).type === "Model"; + var parentModelData; + if (properties.type === "Material" && parentModel) { + parentModelData = Graphics.getModel(properties.parentID); + } + emitScriptEvent({ + type: 'materialTargetReply', + materialTargetData: parentModelData, + }); + } }; HMD.displayModeChanged.connect(function() { diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 926eeaeeb3..d6a281b0c4 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -861,10 +861,16 @@ span.indented { .multiselect-options input[type=checkbox]:checked + label { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); } -.multiselect-options input[type=checkbox]:checked + label:hover { +.multiselect-options input[type=checkbox]:checked + label:hover { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); } +.dynamic-multiselect { + position: relative; + top: 6px; + padding-bottom: 6px; +} + div.refresh { box-sizing: border-box; padding-right: 44px; diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js index a5c961a7e2..3eb206d8a3 100644 --- a/scripts/system/html/js/createAppTooltip.js +++ b/scripts/system/html/js/createAppTooltip.js @@ -43,7 +43,7 @@ CreateAppTooltip.prototype = { this._tooltipData = tooltipData; }, - registerTooltipElement: function(element, tooltipID) { + registerTooltipElement: function(element, tooltipID, jsPropertyName) { element.addEventListener("mouseover", function() { if (!this._isEnabled) { return; @@ -70,7 +70,7 @@ CreateAppTooltip.prototype = { elTipDescription.innerText = tooltipData.tooltip; elTip.appendChild(elTipDescription); - let jsAttribute = tooltipID; + let jsAttribute = jsPropertyName; if (tooltipData.jsPropertyName) { jsAttribute = tooltipData.jsPropertyName; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f259b0a017..ca04f36dc9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -75,6 +75,25 @@ const GROUPS = [ propertyID: "visible", replaceID: "placeholder-property-visible", }, + { + label: "Render Layer", + type: "dropdown", + options: { + world: "World", + front: "Front", + hud: "HUD" + }, + propertyID: "renderLayer", + }, + { + label: "Primitive Mode", + type: "dropdown", + options: { + solid: "Solid", + lines: "Wireframe", + }, + propertyID: "primitiveMode", + }, ] }, { @@ -520,6 +539,11 @@ const GROUPS = [ readOnly: true, hideIfCertified: true, }, + { + label: "Group Culled", + type: "bool", + propertyID: "groupCulled", + }, ] }, { @@ -579,6 +603,33 @@ const GROUPS = [ type: "number-draggable", propertyID: "dpi", }, + { + label: "Web Color", + type: "color", + propertyID: "webColor", + propertyName: "color", // actual entity property name + }, + { + label: "Web Alpha", + type: "number-draggable", + step: 0.001, + decimals: 3, + propertyID: "webAlpha", + propertyName: "alpha", + }, + { + label: "Max FPS", + type: "number-draggable", + step: 1, + decimals: 0, + propertyID: "maxFPS", + }, + { + label: "Script URL", + type: "string", + propertyID: "scriptURL", + placeholder: "URL", + }, ] }, { @@ -650,23 +701,10 @@ const GROUPS = [ propertyID: "materialData", }, { - label: "Select Submesh", - type: "bool", - propertyID: "selectSubmesh", - }, - { - label: "Submesh to Replace", - type: "number-draggable", - min: 0, - step: 1, - propertyID: "submeshToReplace", - indentedLabel: true, - }, - { - label: "Material to Replace", - type: "string", - propertyID: "materialNameToReplace", - indentedLabel: true, + label: "Material Target", + type: "dynamic-multiselect", + propertyUpdate: materialTargetPropertyUpdate, + propertyID: "parentMaterialName", }, { label: "Priority", @@ -807,6 +845,21 @@ const GROUPS = [ decimals: 2, propertyID: "speedSpread", }, + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane", + "compound": "Use Compound Shape URL" }, + propertyID: "particleShapeType", + propertyName: "shapeType", + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "particleCompoundShapeURL", + propertyName: "compoundShapeURL", + }, { label: "Emit Dimensions", type: "vec3", @@ -1573,6 +1626,8 @@ function getPropertyInputElement(propertyID) { return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; case 'icon': return property.elLabel; + case 'dynamic-multiselect': + return property.elDivOptions; default: return undefined; } @@ -1695,6 +1750,10 @@ function resetProperties() { property.elInput.imageLoad(property.elInput.value); break; } + case 'dynamic-multiselect': { + resetDynamicMultiselectProperty(property.elDivOptions); + break; + } } let showPropertyRules = properties[propertyID].showPropertyRules; @@ -2371,7 +2430,7 @@ function createTextureProperty(property, elProperty) { return elResult; } -function createButtonsProperty(property, elProperty, elLabel) { +function createButtonsProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; @@ -2384,6 +2443,43 @@ function createButtonsProperty(property, elProperty, elLabel) { return elProperty; } +function createDynamicMultiselectProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "dynamic-multiselect"; + + let elDivOptions = document.createElement('div'); + elDivOptions.setAttribute("id", elementID + "-options"); + elDivOptions.style = "overflow-y:scroll;max-height:160px;" + + let elDivButtons = document.createElement('div'); + elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons"); + + let elLabel = document.createElement('label'); + elLabel.innerText = "No Options"; + elDivOptions.appendChild(elLabel); + + let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, + { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ]; + addButtons(elDivButtons, elementID, buttons, false); + + elProperty.appendChild(elDivOptions); + elProperty.appendChild(elDivButtons); + + return elDivOptions; +} + +function resetDynamicMultiselectProperty(elDivOptions) { + let elInputs = elDivOptions.getElementsByTagName("input"); + while (elInputs.length > 0) { + let elDivOption = elInputs[0].parentNode; + elDivOption.parentNode.removeChild(elDivOption); + } + elDivOptions.firstChild.style.display = "block"; // show "No Options" text + elDivOptions.parentNode.lastChild.style.display = "none"; // hide Select/Clear all buttons +} + function createTupleNumberInput(property, subLabel) { let propertyElementID = property.elementID; let propertyData = property.data; @@ -2507,6 +2603,10 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI property.elProperty = createButtonsProperty(property, elProperty); break; } + case 'dynamic-multiselect': { + property.elDivOptions = createDynamicMultiselectProperty(property, elProperty); + break; + } case 'placeholder': case 'sub-header': { break; @@ -3043,7 +3143,7 @@ function setDropdownValue(event) { /** - * TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + * TEXTAREA FUNCTIONS */ function setTextareaScrolling(element) { @@ -3051,16 +3151,140 @@ function setTextareaScrolling(element) { element.setAttribute("scrolling", isScrolling ? "true" : "false"); } -function showParentMaterialNameBox(number, elNumber, elString) { - if (number) { - $('#property-submeshToReplace').parent().show(); - $('#property-materialNameToReplace').parent().hide(); - elString.value = ""; - } else { - $('#property-materialNameToReplace').parent().show(); - $('#property-submeshToReplace').parent().hide(); - elNumber.value = 0; + +/** + * MATERIAL TARGET FUNCTIONS + */ + +function setMaterialTargetData(materialTargetData) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + resetDynamicMultiselectProperty(elDivOptions); + + if (materialTargetData === undefined) { + return; } + + elDivOptions.firstChild.style.display = "none"; // hide "No Options" text + elDivOptions.parentNode.lastChild.style.display = "block"; // show Select/Clear all buttons + + let numMeshes = materialTargetData.numMeshes; + for (let i = 0; i < numMeshes; ++i) { + addMaterialTarget(elDivOptions, i, false); + } + + let materialNames = materialTargetData.materialNames; + let materialNamesAdded = []; + for (let i = 0; i < materialNames.length; ++i) { + let materialName = materialNames[i]; + if (materialNamesAdded.indexOf(materialName) === -1) { + addMaterialTarget(elDivOptions, materialName, true); + materialNamesAdded.push(materialName); + } + } + + materialTargetPropertyUpdate(elDivOptions.propertyValue); +} + +function addMaterialTarget(elDivOptions, targetID, isMaterialName) { + let elementID = elDivOptions.getAttribute("id"); + elementID += isMaterialName ? "-material-" : "-mesh-"; + elementID += targetID; + + let elDiv = document.createElement('div'); + elDiv.className = "materialTargetDiv"; + elDiv.onclick = onToggleMaterialTarget; + elDivOptions.appendChild(elDiv); + + let elInput = document.createElement('input'); + elInput.className = "materialTargetInput"; + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", elementID); + elInput.setAttribute("targetID", targetID); + elInput.setAttribute("isMaterialName", isMaterialName); + elDiv.appendChild(elInput); + + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", elementID); + elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID; + elDiv.appendChild(elLabel); + + return elDiv; +} + +function onToggleMaterialTarget(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + sendMaterialTargetProperty(); + } + event.stopPropagation(); +} + +function setAllMaterialTargetInputs(checked) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + for (let i = 0; i < elInputs.length; ++i) { + elInputs[i].checked = checked; + } +} + +function selectAllMaterialTarget() { + setAllMaterialTargetInputs(true); + sendMaterialTargetProperty(); +} + +function clearAllMaterialTarget() { + setAllMaterialTargetInputs(false); + sendMaterialTargetProperty(); +} + +function sendMaterialTargetProperty() { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + let materialTargetList = ""; + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + if (elInput.checked) { + let targetID = elInput.getAttribute("targetID"); + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetList += "mat::" + targetID + ","; + } else { + materialTargetList += targetID + ","; + } + } + } + + if (materialTargetList !== "") { + materialTargetList = materialTargetList.substring(0, materialTargetList.length - 1); + materialTargetList = "[" + materialTargetList + "]"; + } + + updateProperty("parentMaterialName", materialTargetList, false); +} + +function materialTargetPropertyUpdate(propertyValue) { + let elDivOptions = getPropertyInputElement("parentMaterialName"); + let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); + + if (propertyValue.charAt(0) === '[') { + propertyValue = propertyValue.substring(1, propertyValue.length); + } + if (propertyValue.charAt(propertyValue.length - 1) === ']') { + propertyValue = propertyValue.substring(0, propertyValue.length - 1); + } + + let materialTargets = propertyValue.split(","); + for (let i = 0; i < elInputs.length; ++i) { + let elInput = elInputs[i]; + let targetID = elInput.getAttribute("targetID"); + let materialTargetName = targetID; + if (elInput.getAttribute("isMaterialName") === "true") { + materialTargetName = "mat::" + targetID; + } + elInput.checked = materialTargets.indexOf(materialTargetName) >= 0; + } + + elDivOptions.propertyValue = propertyValue; } @@ -3152,7 +3376,7 @@ function loaded() { } if (elLabel) { - createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID); + createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName); } let elProperty = createElementFromHTML('
'); @@ -3177,7 +3401,7 @@ function loaded() { property.spaceMode = propertySpaceMode; let elLabel = createElementFromHTML(`
${innerPropertyData.label}
`); - createAppTooltip.registerTooltipElement(elLabel, propertyID); + createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName); elWrapper.appendChild(elLabel); @@ -3489,6 +3713,12 @@ function loaded() { property.elInput.imageLoad(property.elInput.value); break; } + case 'dynamic-multiselect': { + if (property.data.propertyUpdate) { + property.data.propertyUpdate(propertyValue); + } + break; + } } let showPropertyRules = property.showPropertyRules; @@ -3502,22 +3732,6 @@ function loaded() { } updateVisibleSpaceModeProperties(); - - if (selectedEntityProperties.type === "Material") { - let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); - let parentMaterialName = selectedEntityProperties.parentMaterialName; - if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { - elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); - showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); - elParentMaterialNameCheckbox.checked = false; - } else { - elParentMaterialNameNumber.value = parseInt(parentMaterialName); - showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); - elParentMaterialNameCheckbox.checked = true; - } - } let json = null; try { @@ -3565,6 +3779,10 @@ function loaded() { hideMaterialDataSaved(); } + if (hasSelectedEntityChanged && selectedEntityProperties.type === "Material") { + EventBridge.emitWebEvent(JSON.stringify({ type: 'materialTargetRequest', entityID: selectedEntityProperties.id })); + } + let activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); @@ -3614,6 +3832,8 @@ function loaded() { } } } + } else if (data.type === 'materialTargetReply') { + setMaterialTargetData(data.materialTargetData); } }); @@ -3683,26 +3903,6 @@ function loaded() { elDiv.insertBefore(elMaterialDataEditor, elMaterialData); elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); - // Special Property Callbacks - let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); - elParentMaterialNameString.addEventListener('change', function () { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value, false); - }); - elParentMaterialNameNumber.addEventListener('change', function () { - updateProperty("parentMaterialName", this.value, false); - }); - elParentMaterialNameCheckbox.addEventListener('change', function () { - if (this.checked) { - updateProperty("parentMaterialName", elParentMaterialNameNumber.value, false); - showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); - } else { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value, false); - showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); - } - }); - // Collapsible sections let elCollapsible = document.getElementsByClassName("collapse-icon"); diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 51645e5502..3b81e17473 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -103,6 +103,8 @@ TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this fa TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks +TELEPORT_DEADZONE = 0.15; + NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. // Smaller than TEAR_AWAY_DISTANCE for hysteresis. diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 064dafec06..d2f3ece5f1 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -206,7 +206,7 @@ SelectionManager = (function() { if (entityHostTypes[i].entityHostType !== entityHostType) { if (wantDebug) { console.log("Skipping addition of entity " + childID + " with conflicting entityHostType: " + - entityHostTypes[i].entityHostType); + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); } continue; } @@ -398,15 +398,15 @@ SelectionManager = (function() { if (entityHostTypes[i].entityHostType !== entityHostType) { if (wantDebug) { - console.warn("Skipping deletion of entity " + id + " with conflicting entityHostType: " + - entityHostTypes[i].entityHostType); + console.warn("Skipping addition of entity " + id + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); } continue; } if (!(id in entities)) { entities[id] = Entities.getEntityProperties(id); - appendChildren(id, entities); + appendChildren(id, entities, entityHostType); } } } @@ -1334,14 +1334,7 @@ SelectionDisplay = (function() { ctrlPressed = false; that.updateActiveRotateRing(); } - if (activeTool && lastMouseEvent !== null) { - lastMouseEvent.isShifted = event.isShifted; - lastMouseEvent.isMeta = event.isMeta; - lastMouseEvent.isControl = event.isControl; - lastMouseEvent.isAlt = event.isAlt; - activeTool.onMove(lastMouseEvent); - SelectionManager._update(false, this); - } + that.updateLastMouseEvent(event); }; // Triggers notification on specific key driven events @@ -1350,13 +1343,16 @@ SelectionDisplay = (function() { ctrlPressed = true; that.updateActiveRotateRing(); } - if (activeTool && lastMouseEvent !== null) { - lastMouseEvent.isShifted = event.isShifted; - lastMouseEvent.isMeta = event.isMeta; - lastMouseEvent.isControl = event.isControl; - lastMouseEvent.isAlt = event.isAlt; - activeTool.onMove(lastMouseEvent); - SelectionManager._update(false, this); + that.updateLastMouseEvent(event); + }; + + that.updateLastMouseEvent = function(event) { + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); } }; diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 60848224bb..a38febaa77 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -291,7 +291,7 @@ var clickMapping = Controller.newMapping('tabletToggle-click'); var wantsMenu = 0; clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); - clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { + clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().when(Controller.Hardware.Application.LeftHandDominant).to(function (clicked) { if (clicked) { //activeHudPoint2d(Controller.Standard.RightHand); Messages.sendLocalMessage("toggleHand", Controller.Standard.RightHand); @@ -299,7 +299,7 @@ wantsMenu = clicked; }); - clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) { + clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().when(Controller.Hardware.Application.RightHandDominant).to(function (clicked) { if (clicked) { //activeHudPoint2d(Controller.Standard.LeftHand); Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand); diff --git a/scripts/tutorials/makeBlocks.js b/scripts/tutorials/makeBlocks.js index c7d0425863..d750689f4d 100644 --- a/scripts/tutorials/makeBlocks.js +++ b/scripts/tutorials/makeBlocks.js @@ -56,7 +56,7 @@ grab: { grabbable: true }, cloneable: true, cloneLifetime: LIFETIME, - cloneLimit: 9999 + cloneLimit: 9999, position: Vec3.sum(MyAvatar.position, Vec3.sum(forwardOffset, forwardVector)), color: newColor(), script: SCRIPT_URL diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md index f3dda84291..8526bccc4e 100644 --- a/tools/jsdoc/README.md +++ b/tools/jsdoc/README.md @@ -11,7 +11,7 @@ If you would like the extra functionality for gravPrep: To generate html documentation for the High Fidelity JavaScript API: * `cd tools/jsdoc` -* `jsdoc root.js -c config.json` +* `jsdoc root.js -r api-mainpage.md -c config.json` The out folder should contain index.html. diff --git a/tools/jsdoc/api-mainpage.md b/tools/jsdoc/api-mainpage.md new file mode 100644 index 0000000000..e1ec140af3 --- /dev/null +++ b/tools/jsdoc/api-mainpage.md @@ -0,0 +1,9 @@ +The High Fidelity JavaScript API lets content creators and developers create new experiences and transform virtual worlds within the High Fidelity metaverse. With it, you can build great content, customize avatars, play audio and so much more. + +You are most likely to interact with these APIs: + +* The **[Entities](Entities.html)** namespace lets you add, remove, and edit entities around you to build an interactive environment. In addition, you can use this namespace to find entities in range, direction, collision, or raytrace. +* The **[AvatarList](AvatarList.html)**, **[MyAvatar](MyAvatar.html)**, and **[Avatar](Avatar.html)** namespaces affect your personal avatars, and lets you get information on other people's avatars. +* The **[Script](Script.html)** namespace lets you to connect callbacks from your client to script, such as functionality that is dependent on time (`Script.update`, `Script.setTimeout`, `Script.setInterval`, etc), connect paths relatively to assets (`Script.resolvePath`), refer to other scripts (`Script.require`, `Script.include`), or connect functions to events which occur when the script is turned off (`Script.scriptEnding`). + +To learn more about using High Fidelity and exploring the metaverse, visit the [High Fidelity Documentation](https://docs.highfidelity.com). \ No newline at end of file diff --git a/tools/jsdoc/hifi-jsdoc-template/publish.js b/tools/jsdoc/hifi-jsdoc-template/publish.js index 9cd428bbbb..7d0ded5a0b 100644 --- a/tools/jsdoc/hifi-jsdoc-template/publish.js +++ b/tools/jsdoc/hifi-jsdoc-template/publish.js @@ -420,7 +420,7 @@ function linktoExternal(longName, name) { */ function buildNav(members) { - var nav = '

Home

'; + var nav = '

API Reference Home

'; var seen = {}; var seenTutorials = {}; var docdash = env && env.conf && env.conf.docdash || {}; diff --git a/tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf deleted file mode 100644 index ad884391d7..0000000000 Binary files a/tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf and /dev/null differ diff --git a/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Regular.otf b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Regular.otf new file mode 100644 index 0000000000..8100438827 Binary files /dev/null and b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Regular.otf differ diff --git a/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Semibold.otf b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Semibold.otf new file mode 100644 index 0000000000..45cf6bc865 Binary files /dev/null and b/tools/jsdoc/hifi-jsdoc-template/static/fonts/Graphik-Semibold.otf differ diff --git a/tools/jsdoc/hifi-jsdoc-template/static/images/fav-icon.ico b/tools/jsdoc/hifi-jsdoc-template/static/images/fav-icon.ico new file mode 100644 index 0000000000..2bacaf1083 Binary files /dev/null and b/tools/jsdoc/hifi-jsdoc-template/static/images/fav-icon.ico differ diff --git a/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png b/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png index 18cd2d88dd..b23aa64e94 100644 Binary files a/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png and b/tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png differ diff --git a/tools/jsdoc/hifi-jsdoc-template/static/styles/jsdoc.css b/tools/jsdoc/hifi-jsdoc-template/static/styles/jsdoc.css index 2ba9c84954..2232491d04 100644 --- a/tools/jsdoc/hifi-jsdoc-template/static/styles/jsdoc.css +++ b/tools/jsdoc/hifi-jsdoc-template/static/styles/jsdoc.css @@ -4,8 +4,13 @@ ********************************************************************/ @font-face{ - font-family: 'Cairo'; - src: url('../fonts/Cairo-Bold.ttf') format('truetype'); + font-family: 'Graphik Semibold'; + src: url('../fonts/Graphik-SemiBold.otf') format('opentype'); +} + +@font-face{ + font-family: 'Graphik Regular'; + src: url('../fonts/Graphik-Regular.otf') format('opentype'); } @font-face{ @@ -29,12 +34,15 @@ html body { - font-family: 'Proxima Nova', sans-serif; - font-size: 1rem; - line-height: 1.5; - letter-spacing: 0.5px; - margin: 1.5rem; - color: #555; + font-family: 'Graphik Regular', 'Proxima Nova', arial, sans-serif; + font-weight: 400; + color: #000000; + letter-spacing: 0.5px; +} + +#main p { + line-height: 24px; + margin-bottom: 24px; } section @@ -50,14 +58,13 @@ section ********************************************************************/ h1, h2, h3, h4 { - font-family: "Cairo", Helvetica, sans-serif; + font-family: "Graphik Semibold", Helvetica, sans-serif; } h1 { font-size: 3.25rem; text-align: center; - letter-spacing: 1.5px; margin: 50px 25px 25px; } @@ -69,15 +76,13 @@ h2 h3 { - font-size: 1.5rem; + font-size: 28px; } h4 { - font-size: 18px; - letter-spacing: -0.33px; - margin-bottom: 12px; - color: #4d4e53; + font-family: 'Graphik Regular'; + font-size: 1.03rem; } h6 @@ -119,7 +124,7 @@ table background-color: #fff; border-collapse: collapse; border-spacing: 0; - border: 1px solid #ccc; + border: solid #d8e1d9 1px; text-align: left; overflow: auto; font-size: 0.9rem; @@ -128,18 +133,19 @@ table } table > thead { - background-color: #ddd; - border-bottom: 1px solid #ccc; + border-color: #d8e1d9; + background: #d8e1d9; font-weight: 400; } table th, table td { padding: 0.5rem; - border-left: 1px solid #ccc; + border-left: 1px solid #d8e1d9; + font-size: .95em; } table tr { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid #d8e1d9; } table tr:nth-child(even) { @@ -150,18 +156,14 @@ table tr:nth-child(even) { ****************************** Link styles ************************* ********************************************************************/ -a, a:visited, a:active { - color: #1694CA; - text-decoration: none; +a, a:hover, a:active, a:visited { + text-decoration: none; } -article a:hover { - color: #0e6185; - text-decoration: none; - font-weight: bold; +#main a, #main a:visited, #main a:active, #main a:hover { + color: #009ee0; } - /******************************************************************* ***************************** List styles ************************** ********************************************************************/ @@ -171,20 +173,29 @@ article ul { } article li { + font-size: .95rem; padding-bottom: 5px; } +.readme ul { + font-size: 0.95rem; + line-height: 24px; + margin-bottom: 24px; + +} + /******************************************************************* ********************** Navigation sidebar styles ******************* ********************************************************************/ nav { position: fixed; - top: 165px; + top: 260px; bottom: 0; left: 0; right: 0; - width: 305px; + width: 300px; + background-color: #000000; border-right: 1px solid #ccc; overflow-y: scroll; padding-left: 20px; @@ -192,6 +203,11 @@ nav { box-sizing: border-box; } +nav::-webkit-scrollbar { + width: 0; + height: 0; +} + nav #nav-search { width: 210px; height: 30px; @@ -207,21 +223,28 @@ nav #nav-search { position: fixed; top: 0; left: 0; - height: 165px; - width: 305px; - background-color: #00B4EF; - vertical-align: middle; + height: 260px; + width: 300px; + background-color: #000000; + color: #FFFFFF; text-align: center; margin-top: 0px; } .nav-header p { - padding-top: 15px; + padding-top: 8px; +} + +.nav-header a { + color: #FFFFFF; + font-size: .9rem; + line-height: 1.5; } nav h3 { - font-family: "Proxima Nova", sans-serif; + font-family: "Graphik Regular", sans-serif; font-size: 0.9rem; + color: #FFFFFF; text-transform: uppercase; letter-spacing: 0.5px; } @@ -233,8 +256,16 @@ nav ul { list-style: none; } +nav h3 a { + color: #FFFFFF; +} + nav ul a, nav ul a:visited, nav ul a:active { - color: #a1a1a1; + color: #FFFFFF; +} + +nav ul a:hover { + font-weight: bold; } nav ul ul { @@ -250,7 +281,7 @@ nav ul ul li:first-child nav li { - margin-top: 3px; + margin-top: 14px; } @@ -333,14 +364,14 @@ nav > h2 > a { .search-input { - font-family: 'Proxima Nova', sans-serif; - font-size: 0.9rem; + font-family: 'Graphik Regular', sans-serif; + font-size: 80%; border: 1px solid #ddd; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06); - border-radius: 0.1875rem; + border-radius: 0; color: #3A3F3E; - width: 75%; - padding: 0.425rem; + width: 70%; + padding: 10px; } /******************************************************************** @@ -349,23 +380,20 @@ nav > h2 > a { tt, code, kbd, samp { font-family: Consolas, Monaco, 'Andale Mono', monospace; - font-size: 0.8rem; + font-size: 0.9rem; } .name, .signature { font-family: Consolas, Monaco, 'Andale Mono', monospace; - font-size: 0.8rem; + font-size: 0.9rem; } - - - - - img { + display: block; max-width: 100%; + margin: auto; } p, ul, ol, blockquote { @@ -427,7 +455,7 @@ header { color: #999 !important; } -.availableIn +#main p.availableIn { font-size: 0.8rem; } @@ -517,7 +545,7 @@ header { .prettyprint code { - font-size: 0.65rem; + font-size: 0.7rem; line-height: 18px; display: block; padding: 4px 12px; @@ -580,13 +608,8 @@ header { padding-top: 0; } -.params td.description > p:last-child, .props td.description > p:last-child { - margin-bottom: 0; - padding-bottom: 0; -} - span.param-type, .params td .param-type, .param-type dd { - color: #606; + color: #606; font-family: Consolas, Monaco, 'Andale Mono', monospace } diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/layout.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/layout.tmpl index 6f267587c0..41a324fdaf 100644 --- a/tools/jsdoc/hifi-jsdoc-template/tmpl/layout.tmpl +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/layout.tmpl @@ -16,10 +16,11 @@