diff --git a/.gitignore b/.gitignore index 1ffb93fe80..df91e0ca7b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,8 @@ TAGS node_modules npm-debug.log +# ignore qmlc files generated from qml as cache +*.qmlc # Android studio files *___jb_old___ @@ -88,4 +90,4 @@ android/app/src/main/assets interface/compiledResources # GPUCache -interface/resources/GPUCache/* \ No newline at end of file +interface/resources/GPUCache/* diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index 3d6d056d79..8c9263b6e7 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -53,7 +53,7 @@ Enter the repository `android` directory Execute a gradle pre-build setup. This step should only need to be done once -`gradle setupDepedencies` +`gradle setupDependencies` # Building & Running diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5e93bdffa3..b3a8c87649 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,9 +19,9 @@ android:allowBackup="true" android:screenOrientation="unspecified" android:theme="@style/NoSystemUI" - android:icon="@mipmap/ic_launcher" + android:icon="@drawable/ic_launcher" android:launchMode="singleTop" - android:roundIcon="@mipmap/ic_launcher_round"> + android:roundIcon="@drawable/ic_launcher"> diff --git a/android/app/src/main/res/drawable/ic_launcher.xml b/android/app/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/icon.png b/android/app/src/main/res/drawable/icon.png deleted file mode 100644 index 70aaf9b4ed..0000000000 Binary files a/android/app/src/main/res/drawable/icon.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index d376d7af88..0000000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 81a137957d..0000000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 88b1be5903..0000000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index d032c30014..0000000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4a018d4ed9..0000000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 3cccf1037b..0000000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index e97062e0ee..0000000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8d142881da..0000000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index f01203c738..0000000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 211e298961..0000000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/build.gradle b/android/build.gradle index 22273f9059..1dfef97f91 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -137,6 +137,13 @@ def packages = [ checksum: '20768f298f53b195e71b414b0ae240c4', sharedLibFolder: 'lib/release', includeLibs: ['libtbb.so', 'libtbbmalloc.so'], + ], + hifiAC: [ + file: 'libplugins_libhifiCodec.zip', + versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G', + checksum: '9412a8e12c88a4096c1fc843bb9fe52d', + sharedLibFolder: '', + includeLibs: ['libplugins_libhifiCodec.so'] ] ] @@ -353,6 +360,7 @@ task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyDependencyDownloads(dependsOn: downloadDependencies) { } verifyDependencyDownloads.dependsOn verifyQt @@ -362,6 +370,7 @@ verifyDependencyDownloads.dependsOn verifyGvr verifyDependencyDownloads.dependsOn verifyOpenSSL verifyDependencyDownloads.dependsOn verifyPolyvox verifyDependencyDownloads.dependsOn verifyTBB +verifyDependencyDownloads.dependsOn verifyHifiAC task extractDependencies(dependsOn: verifyDependencyDownloads) { doLast { diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3ca924b007..929941c05c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -42,7 +42,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message) { // make sure we hear about node kills so we can tell the other nodes - connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); + connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket"); @@ -423,14 +423,15 @@ void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { } } -void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { - if (killedNode->getType() == NodeType::Agent - && killedNode->getLinkedData()) { + +void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) { + if (avatarNode->getType() == NodeType::Agent + && avatarNode->getLinkedData()) { auto nodeList = DependencyManager::get(); { // decrement sessionDisplayNames table and possibly remove - QMutexLocker nodeDataLocker(&killedNode->getLinkedData()->getMutex()); - AvatarMixerClientData* nodeData = dynamic_cast(killedNode->getLinkedData()); + QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex()); + AvatarMixerClientData* nodeData = dynamic_cast(avatarNode->getLinkedData()); const QString& baseDisplayName = nodeData->getBaseDisplayName(); // No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case. if (--_sessionDisplayNames[baseDisplayName].second <= 0) { @@ -447,12 +448,12 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { // we relay avatar kill packets to agents that are not upstream // and downstream avatar mixers, if the node that was just killed was being replicated return (node->getType() == NodeType::Agent && !node->isUpstream()) || - (killedNode->isReplicated() && shouldReplicateTo(*killedNode, *node)); + (avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node)); }, [&](const SharedNodePointer& node) { if (node->getType() == NodeType::Agent) { if (!killPacket) { killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); - killPacket->write(killedNode->getUUID().toRfc4122()); + killPacket->write(avatarNode->getUUID().toRfc4122()); killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } @@ -462,7 +463,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (!replicatedKillPacket) { replicatedKillPacket = NLPacket::create(PacketType::ReplicatedKillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason)); - replicatedKillPacket->write(killedNode->getUUID().toRfc4122()); + replicatedKillPacket->write(avatarNode->getUUID().toRfc4122()); replicatedKillPacket->writePrimitive(KillAvatarReason::AvatarDisconnected); } @@ -479,7 +480,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { return false; } - if (node->getUUID() == killedNode->getUUID()) { + if (node->getUUID() == avatarNode->getUUID()) { return false; } @@ -489,7 +490,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { QMetaObject::invokeMethod(node->getLinkedData(), "cleanupKilledNode", Qt::AutoConnection, - Q_ARG(const QUuid&, QUuid(killedNode->getUUID()))); + Q_ARG(const QUuid&, QUuid(avatarNode->getUUID()))); } ); } @@ -605,7 +606,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes void AvatarMixer::handleKillAvatarPacket(QSharedPointer message, SharedNodePointer node) { auto start = usecTimestampNow(); - DependencyManager::get()->processKillNode(*message); + handleAvatarKilled(node); + + node->setLinkedData(nullptr); auto end = usecTimestampNow(); _handleKillAvatarPacketElapsedTime += (end - start); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index cb5f536faa..1fbfd7338b 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -39,7 +39,7 @@ public slots: /// runs the avatar mixer void run() override; - void nodeKilled(SharedNodePointer killedNode); + void handleAvatarKilled(SharedNodePointer killedNode); void sendStatsPacket() override; diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index e5cee84f1b..94d21f1c9a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -442,12 +442,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); if (entity) { - // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again + const QUuid& entityID = entity->getID(); + // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again; + // also send if we previously matched since this represents change to a matched item. bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); - if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { + bool entityPreviouslyMatchedFilter = entityNodeData->sentFilteredEntity(entityID); + + if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entityID) || entityPreviouslyMatchedFilter) { if (!jsonFilters.isEmpty() && entityMatchesFilters) { // Record explicitly filtered-in entity so that extra entities can be flagged. - entityNodeData->insertSentFilteredEntity(entity->getID()); + entityNodeData->insertSentFilteredEntity(entityID); } OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); @@ -458,6 +462,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream params.stopReason = EncodeBitstreamParams::DIDNT_FIT; break; } + + if (entityPreviouslyMatchedFilter && !entityMatchesFilters) { + entityNodeData->removeSentFilteredEntity(entityID); + } ++_numEntities; } if (queuedItem.shouldForceRemove()) { diff --git a/cmake/externals/nvtt/CMakeLists.txt b/cmake/externals/nvtt/CMakeLists.txt index 00722bd1e0..3076217c33 100644 --- a/cmake/externals/nvtt/CMakeLists.txt +++ b/cmake/externals/nvtt/CMakeLists.txt @@ -29,8 +29,8 @@ else () ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip - URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb + URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi-83462e4.zip + URL_MD5 602776e08515b54bfa1b8dc455003f0f CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 3ed5445493..49877cacc3 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -23,12 +23,22 @@ set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *) -add_custom_command( - OUTPUT ${RESOURCES_RCC} - DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} - COMMAND "${QT_DIR}/bin/rcc" - ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC} -) +if (ANDROID) + # on Android, don't compress the rcc binary + add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${QT_DIR}/bin/rcc" + ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC} + ) +else () + add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${QT_DIR}/bin/rcc" + ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC} + ) +endif() list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) diff --git a/interface/resources/controllers/touchscreenvirtualpad.json b/interface/resources/controllers/touchscreenvirtualpad.json index 8c21044c3b..907ff8b403 100644 --- a/interface/resources/controllers/touchscreenvirtualpad.json +++ b/interface/resources/controllers/touchscreenvirtualpad.json @@ -5,8 +5,8 @@ { "from": "TouchscreenVirtualPad.LX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" }, + { "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05} , "invert" ], "to": "Actions.Yaw" }, - { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "to": "Actions.Pitch" } + { "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05}, "invert" ], "to": "Actions.Pitch" } ] } diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml index 50552f0d3b..b8d6b5e270 100644 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ b/interface/resources/qml/+android/AddressBarDialog.qml @@ -76,33 +76,33 @@ Item { HifiStyles.RalewayRegular { id: notice text: "YOUR LOCATION" - font.pixelSize: (hifi.fonts.pixelSize * 2.15)*(android.dimen.atLeast1440p?1:.75); + font.pixelSize: (hifi.fonts.pixelSize * 2.15) * (android.dimen.atLeast1440p ? 1 : .75); color: "#2CD7FF" anchors { bottom: addressBackground.top - bottomMargin: android.dimen.atLeast1440p?45:34 + bottomMargin: android.dimen.atLeast1440p ? 45 : 34 left: addressBackground.left - leftMargin: android.dimen.atLeast1440p?60:45 + leftMargin: android.dimen.atLeast1440p ? 60 : 45 } } - property int inputAreaHeight: android.dimen.atLeast1440p?210:156 + property int inputAreaHeight: android.dimen.atLeast1440p ? 210 : 156 property int inputAreaStep: (height - inputAreaHeight) / 2 ToolbarButton { id: homeButton - y: android.dimen.atLeast1440p?280:210 + y: android.dimen.atLeast1440p ? 280 : 210 imageURL: "../../icons/home.svg" onClicked: { addressBarDialog.loadHome(); bar.shown = false; } anchors { - leftMargin: android.dimen.atLeast1440p?75:56 + leftMargin: android.dimen.atLeast1440p ? 75 : 56 left: parent.left } - size: android.dimen.atLeast1440p?150:150//112 + size: android.dimen.atLeast1440p ? 150 : 150//112 } ToolbarButton { @@ -111,10 +111,10 @@ Item { onClicked: addressBarDialog.loadBack(); anchors { left: homeButton.right - leftMargin: android.dimen.atLeast1440p?70:52 + leftMargin: android.dimen.atLeast1440p ? 70 : 52 verticalCenter: homeButton.verticalCenter } - size: android.dimen.atLeast1440p?150:150 + size: android.dimen.atLeast1440p ? 150 : 150 } ToolbarButton { id: forwardArrow; @@ -122,10 +122,10 @@ Item { onClicked: addressBarDialog.loadForward(); anchors { left: backArrow.right - leftMargin: android.dimen.atLeast1440p?60:45 + leftMargin: android.dimen.atLeast1440p ? 60 : 45 verticalCenter: homeButton.verticalCenter } - size: android.dimen.atLeast1440p?150:150 + size: android.dimen.atLeast1440p ? 150 : 150 } HifiStyles.FiraSansRegular { @@ -140,25 +140,22 @@ Item { Rectangle { id: addressBackground - x: android.dimen.atLeast1440p?780:585 - y: android.dimen.atLeast1440p?280:235 // tweaking by hand - width: android.dimen.atLeast1440p?1270:952 - height: android.dimen.atLeast1440p?150:112 + x: android.dimen.atLeast1440p ? 780 : 585 + y: android.dimen.atLeast1440p ? 280 : 235 // tweaking by hand + width: android.dimen.atLeast1440p ? 1270 : 952 + height: android.dimen.atLeast1440p ? 150 : 112 color: "#FFFFFF" } TextInput { id: addressLine focus: true - x: android.dimen.atLeast1440p?870:652 - y: android.dimen.atLeast1440p?300:245 // tweaking by hand - width: android.dimen.atLeast1440p?1200:900 - height: android.dimen.atLeast1440p?120:90 + x: android.dimen.atLeast1440p ? 870 : 652 + y: android.dimen.atLeast1440p ? 300 : 245 // tweaking by hand + width: android.dimen.atLeast1440p ? 1200 : 900 + height: android.dimen.atLeast1440p ? 120 : 90 inputMethodHints: Qt.ImhNoPredictiveText //helperText: "Hint is here" - anchors { - //verticalCenter: addressBackground.verticalCenter - } font.pixelSize: hifi.fonts.pixelSize * 3.75 onTextChanged: { //filterChoicesByText(); @@ -228,4 +225,4 @@ Item { } } -} \ No newline at end of file +} diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml index 9eb2c74147..567cca9bcf 100644 --- a/interface/resources/qml/+android/LoginDialog.qml +++ b/interface/resources/qml/+android/LoginDialog.qml @@ -38,7 +38,8 @@ ModalWindow { keyboardOverride: true // Disable ModalWindow's keyboard. function tryDestroy() { - root.destroy() + Controller.setVPadHidden(false); + root.destroy(); } LoginDialog { @@ -52,8 +53,9 @@ ModalWindow { Component.onCompleted: { this.anchors.centerIn = undefined; - this.y=150; - this.x=(parent.width - this.width)/2; + this.y = 150; + this.x = (parent.width - this.width) / 2; + Controller.setVPadHidden(true); } Keys.onPressed: { diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputsBar.qml similarity index 88% rename from interface/resources/qml/AvatarInputs.qml rename to interface/resources/qml/AvatarInputsBar.qml index be4bf03465..a88a42080e 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -14,9 +14,9 @@ import Qt.labs.settings 1.0 import "./hifi/audio" as HifiAudio -Hifi.AvatarInputs { +Item { id: root; - objectName: "AvatarInputs" + objectName: "AvatarInputsBar" property int modality: Qt.NonModal width: audio.width; height: audio.height; @@ -26,7 +26,7 @@ Hifi.AvatarInputs { HifiAudio.MicBar { id: audio; - visible: root.showAudioTools; + visible: AvatarInputs.showAudioTools; standalone: true; dragTarget: parent; } diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 7eced0c751..38e65af4ca 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -224,7 +224,7 @@ Item { onClicked: { Qt.inputMethod.hide(); - root.destroy(); + root.tryDestroy(); } } } diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index e60f646327..973bbee60e 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -42,8 +42,8 @@ Original.CheckBox { style: CheckBoxStyle { indicator: Rectangle { id: box - width: boxSize - height: boxSize + implicitWidth: boxSize + implicitHeight: boxSize radius: boxRadius border.width: 1 border.color: pressed || hovered @@ -101,8 +101,8 @@ Original.CheckBox { } label: Label { - text: control.text - color: control.color + text: checkBox.text + color: checkBox.color x: 2 wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml index e7056baa36..85d7e52eb2 100644 --- a/interface/resources/qml/hifi/+android/AvatarOption.qml +++ b/interface/resources/qml/hifi/+android/AvatarOption.qml @@ -26,13 +26,13 @@ ColumnLayout { property string methodName: ""; property string actionText: ""; - spacing: 4*3 + spacing: 4 * 3 signal sendToParentQml(var message); Image { id: itemImage - Layout.preferredWidth: 250*3 - Layout.preferredHeight: 140*3 + Layout.preferredWidth: 250 * 3 + Layout.preferredHeight: 140 * 3 source: thumbnailUrl asynchronous: true fillMode: Image.PreserveAspectFit @@ -81,7 +81,7 @@ ColumnLayout { HifiControlsUit.ImageButton { width: 140*3 height: 35*3 - text: type=="extra"? actionText: "CHOOSE" + text: type=="extra" ? actionText: "CHOOSE" source: "../../../../icons/button.svg" hoverSource: "../../../../icons/button-a.svg" fontSize: 18*3 @@ -102,8 +102,8 @@ ColumnLayout { Image { id: tickImage - width: 35*3 - height: 35*3 + width: 35 * 3 + height: 35 * 3 source: "../../../icons/tick.svg" anchors { horizontalCenter: itemName.horizontalCenter @@ -114,4 +114,4 @@ ColumnLayout { Component.onCompleted:{ sendToParentQml.connect(sendToScript); } -} \ No newline at end of file +} diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android/HifiConstants.qml index fbdf60dcb0..4c161da259 100644 --- a/interface/resources/qml/hifi/+android/HifiConstants.qml +++ b/interface/resources/qml/hifi/+android/HifiConstants.qml @@ -22,26 +22,26 @@ Item { Item { id: dimen readonly property bool atLeast1440p: Screen.width >= 2560 && Screen.height >= 1440 - readonly property real windowLessWidth: atLeast1440p?378:284 - readonly property real windowLessHeight: atLeast1440p?192:144 + readonly property real windowLessWidth: atLeast1440p ? 378 : 284 + readonly property real windowLessHeight: atLeast1440p ? 192 : 144 readonly property real windowZ: 100 - readonly property real headerHeight: atLeast1440p?276:207 + readonly property real headerHeight: atLeast1440p ? 276 : 207 - readonly property real headerIconPosX: atLeast1440p?90:67 - readonly property real headerIconPosY: atLeast1440p?108:81 - readonly property real headerIconWidth: atLeast1440p?111:83 - readonly property real headerIconHeight: atLeast1440p?111:83 - readonly property real headerIconTitleDistance: atLeast1440p?151:113 + readonly property real headerIconPosX: atLeast1440p ? 90 : 67 + readonly property real headerIconPosY: atLeast1440p ? 108 : 81 + readonly property real headerIconWidth: atLeast1440p ? 111 : 83 + readonly property real headerIconHeight: atLeast1440p ? 111 : 83 + readonly property real headerIconTitleDistance: atLeast1440p ? 151 : 113 - readonly property real headerHideWidth: atLeast1440p?150:112 - readonly property real headerHideHeight: atLeast1440p?150:112 - readonly property real headerHideRightMargin: atLeast1440p?110:82 - readonly property real headerHideTopMargin: atLeast1440p?90:67 - readonly property real headerHideIconWidth: atLeast1440p?70:52 - readonly property real headerHideIconHeight: atLeast1440p?45:33 - readonly property real headerHideTextTopMargin: atLeast1440p?36:27 + readonly property real headerHideWidth: atLeast1440p ? 150 : 112 + readonly property real headerHideHeight: atLeast1440p ? 150 : 112 + readonly property real headerHideRightMargin: atLeast1440p ? 110 : 82 + readonly property real headerHideTopMargin: atLeast1440p ? 90 : 67 + readonly property real headerHideIconWidth: atLeast1440p ? 70 : 52 + readonly property real headerHideIconHeight: atLeast1440p ? 45 : 33 + readonly property real headerHideTextTopMargin: atLeast1440p ? 36 : 27 readonly property real botomHudWidth: 366 readonly property real botomHudHeight: 180 diff --git a/interface/resources/qml/hifi/overlays/TextOverlay.qml b/interface/resources/qml/hifi/overlays/TextOverlay.qml index 301a2aa0bf..2fdc9c7cbb 100644 --- a/interface/resources/qml/hifi/overlays/TextOverlay.qml +++ b/interface/resources/qml/hifi/overlays/TextOverlay.qml @@ -20,6 +20,7 @@ Overlay { font.family: "Helvetica" font.pixelSize: 18 lineHeight: 18 + clip: true } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 210a3c76d0..98bfb52c38 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -16,10 +16,11 @@ import "../js/Utils.js" as Utils Item { id: frame + objectName: "Frame" HifiConstants { id: hifi } default property var decoration - + property string qmlFile: "N/A" property bool gradientsSupported: desktop.gradientsSupported readonly property int frameMarginLeft: frame.decoration ? frame.decoration.frameMarginLeft : 0 @@ -44,7 +45,7 @@ Item { id: debugZ visible: DebugQML color: "red" - text: (window ? "Z: " + window.z : "") + text: (window ? "Z: " + window.z : "") + " " + qmlFile y: window ? window.height + 4 : 0 } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f0e7db399e..cc5adc170e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2007,20 +2007,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool))); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); - { - PROFILE_RANGE(render, "Process Default Skybox"); - auto textureCache = DependencyManager::get(); - - QFileSelector fileSelector; - fileSelector.setExtraSelectors(FileUtils::getFileSelectors()); - auto skyboxUrl = fileSelector.select(PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.ktx"); - - _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl.toStdString()); - _defaultSkyboxAmbientTexture = _defaultSkyboxTexture; - - _defaultSkybox->setCubemap(_defaultSkyboxTexture); - } - EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); return entityServerNode && !isPhysicsEnabled(); @@ -2470,7 +2456,6 @@ void Application::initializeGL() { DeadlockWatchdogThread::withPause([&] { // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; - static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; _renderEngine->addJob("UpdateScene"); #ifndef Q_OS_ANDROID _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); @@ -2732,10 +2717,12 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { Stats::show(); - AvatarInputs::show(); auto surfaceContext = DependencyManager::get()->getSurfaceContext(); surfaceContext->setContextProperty("Stats", Stats::getInstance()); - surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); + + auto offscreenUi = DependencyManager::get(); + auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml"); + offscreenUi->show(qml, "AvatarInputsBar"); } void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { @@ -5444,7 +5431,7 @@ void Application::update(float deltaTime) { editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) { PerformanceTimer perfTimer("editRenderArgs"); - appRenderArgs._headPose= getHMDSensorPose(); + appRenderArgs._headPose = getHMDSensorPose(); auto myAvatar = getMyAvatar(); @@ -5464,10 +5451,10 @@ void Application::update(float deltaTime) { { QMutexLocker viewLocker(&_viewMutex); // adjust near clip plane to account for sensor scaling. - auto adjustedProjection = glm::perspective(_viewFrustum.getFieldOfView(), - _viewFrustum.getAspectRatio(), - DEFAULT_NEAR_CLIP * sensorToWorldScale, - _viewFrustum.getFarClip()); + auto adjustedProjection = glm::perspective(glm::radians(_fieldOfView.get()), + getActiveDisplayPlugin()->getRecommendedAspectRatio(), + DEFAULT_NEAR_CLIP * sensorToWorldScale, + DEFAULT_FAR_CLIP); _viewFrustum.setProjection(adjustedProjection); _viewFrustum.calculate(); } @@ -5549,6 +5536,7 @@ void Application::update(float deltaTime) { { QMutexLocker viewLocker(&_viewMutex); _myCamera.loadViewFrustum(_displayViewFrustum); + appRenderArgs._view = glm::inverse(_displayViewFrustum.getView()); } { diff --git a/interface/src/Application.h b/interface/src/Application.h index b50239a38d..3eed506ead 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -272,10 +272,6 @@ public: void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); - graphics::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } - gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; } - gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; } - OverlayID getTabletScreenID() const; OverlayID getTabletHomeButtonID() const; QUuid getTabletFrameID() const; // may be an entity or an overlay @@ -623,6 +619,7 @@ private: struct AppRenderArgs { render::Args _renderArgs; glm::mat4 _eyeToWorld; + glm::mat4 _view; glm::mat4 _eyeOffsets[2]; glm::mat4 _eyeProjections[2]; glm::mat4 _headPose; @@ -678,10 +675,6 @@ private: ConnectionMonitor _connectionMonitor; - graphics::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ; - gpu::TexturePointer _defaultSkyboxTexture; - gpu::TexturePointer _defaultSkyboxAmbientTexture; - QTimer _addAssetToWorldResizeTimer; QHash _addAssetToWorldResizeList; diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 5cc072df37..e1015ca5d1 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -90,10 +90,10 @@ void Application::paintGL() { { PROFILE_RANGE(render, "/gpuContextReset"); - _gpuContext->beginFrame(HMDSensorPose); + _gpuContext->beginFrame(_appRenderArgs._view, HMDSensorPose); // Reset the gpu::Context Stages // Back to the default framebuffer; - gpu::doInBatch(_gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("Application_render::gpuContextReset", _gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); } @@ -216,7 +216,7 @@ void Application::runRenderFrame(RenderArgs* renderArgs) { // Make sure the WorldBox is in the scene // For the record, this one RenderItem is the first one we created and added to the scene. - // We could meoee that code elsewhere but you know... + // We could move that code elsewhere but you know... if (!render::Item::isValidID(WorldBoxRenderData::_item)) { auto worldBoxRenderData = std::make_shared(); auto worldBoxRenderPayload = std::make_shared(worldBoxRenderData); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ca6f7a31d1..fa0e8087f0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -45,6 +45,9 @@ #include "LocationBookmarks.h" #include "DeferredLightingEffect.h" +#include "AmbientOcclusionEffect.h" +#include "RenderShadowTask.h" + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -361,18 +364,6 @@ Menu::Menu() { // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); - // Developer > Graphics - MenuWrapper* graphicsOptionsMenu = developerMenu->addMenu("Render"); - action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::Shadows, 0, true); - connect(action, &QAction::triggered, [action] { - DependencyManager::get()->setShadowMapEnabled(action->isChecked()); - }); - - action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::AmbientOcclusion, 0, false); - connect(action, &QAction::triggered, [action] { - DependencyManager::get()->setAmbientOcclusionEnabled(action->isChecked()); - }); - // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, @@ -389,6 +380,36 @@ Menu::Menu() { // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); + action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, true); + connect(action, &QAction::triggered, [action] { + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + if (renderConfig) { + auto mainViewShadowTaskConfig = renderConfig->getConfig("RenderMainView.RenderShadowTask"); + if (mainViewShadowTaskConfig) { + if (action->isChecked()) { + mainViewShadowTaskConfig->setPreset("Enabled"); + } else { + mainViewShadowTaskConfig->setPreset("None"); + } + } + } + }); + + action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion, 0, false); + connect(action, &QAction::triggered, [action] { + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + if (renderConfig) { + auto mainViewAmbientOcclusionConfig = renderConfig->getConfig("RenderMainView.AmbientOcclusion"); + if (mainViewAmbientOcclusionConfig) { + if (action->isChecked()) { + mainViewAmbientOcclusionConfig->setPreset("Enabled"); + } else { + mainViewAmbientOcclusionConfig->setPreset("None"); + } + } + } + }); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DefaultSkybox, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 1d37b74ffe..cf9eed1a27 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -205,7 +205,7 @@ namespace MenuOption { const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar"; const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar"; const QString Shadows = "Shadows"; - const QString AmbientOcclusion = "AmbientOcclusion"; + const QString AmbientOcclusion = "Ambient Occlusion"; } #endif // hifi_Menu_h diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 45f1756eb7..a4bf0bcefe 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -107,7 +107,7 @@ public: args->_displayMode = RenderArgs::MONO; args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) { batch.disableContextStereo(); batch.disableContextViewCorrection(); }); @@ -196,7 +196,7 @@ public: args->_displayMode = cachedArgs->_displayMode; args->_renderMode = cachedArgs->_renderMode; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("EndSecondaryCameraFrame::run", args->_context, [&](gpu::Batch& batch) { batch.restoreContextStereo(); batch.restoreContextViewCorrection(); }); diff --git a/interface/src/avatar/AvatarActionFarGrab.h b/interface/src/avatar/AvatarActionFarGrab.h index bcaf7f2f3c..97d4a6bb03 100644 --- a/interface/src/avatar/AvatarActionFarGrab.h +++ b/interface/src/avatar/AvatarActionFarGrab.h @@ -15,6 +15,24 @@ #include #include +/**jsdoc + * The "far-grab" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * orientation, optionally relative to another entity. Collisions between the entity and the user's avatar are disabled during + * the far-grab. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-FarGrab + * @property {Vec3} targetPosition=0,0,0 - The target position. + * @property {Quat} targetRotation=0,0,0,1 - The target rotation. + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to this entity's position and rotation. + * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ class AvatarActionFarGrab : public ObjectActionTractor { public: AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index d4fe8574ca..9d568e6d73 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -416,6 +416,26 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "hold" {@link Entities.ActionType|ActionType} positions and rotates an entity relative to an avatar's hand. + * Collisions between the entity and the user's avatar are disabled during the hold. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Hold + * @property {Uuid} holderID=MyAvatar.sessionUUID - The ID of the avatar holding the entity. + * @property {Vec3} relativePosition=0,0,0 - The target position relative to the avatar's hand. + * @property {Vec3} relativeRotation=0,0,0,1 - The target rotation relative to the avatar's hand. + * @property {number} timeScale=3.4e+38 - Controls how long it takes for the entity's position and rotation to catch up with + * the target. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action is + * applied using an exponential decay. + * @property {string} hand=right - The hand holding the entity: "left" or "right". + * @property {boolean} kinematic=false - If true, the entity is made kinematic during the action; the entity won't + * lag behind the hand but constraint actions such as "hinge" won't act properly. + * @property {boolean} kinematicSetVelocity=false - If true and kinematic is true, the + * entity's velocity property will be set during the action, e.g., so that other scripts may use the value. + * @property {boolean} ignoreIK=false - If true, the entity follows the HMD controller rather than the avatar's + * hand. + */ QVariantMap AvatarActionHold::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&]{ diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a5e24b8707..9620a2dcec 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1115,7 +1115,6 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1468,7 +1467,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); _headBoneSet.clear(); _cauterizationNeedsUpdate = true; @@ -2043,8 +2041,8 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(), - render::ItemKey::TAG_BITS_NONE, true); + _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(), + render::ItemKey::TAG_BITS_NONE, true); } } } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 7cce12c8e0..9a7ebdf784 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -64,7 +64,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { } // Execute the batch into our framebuffer - doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { + doInBatch("ApplicationOverlay::render", renderArgs->_context, [&](gpu::Batch& batch) { PROFILE_RANGE_BATCH(batch, "ApplicationOverlayRender"); renderArgs->_batch = &batch; batch.enableStereo(false); diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 51d1c9bf6b..3053cb8855 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -16,19 +16,19 @@ #include "Application.h" #include "Menu.h" -HIFI_QML_DEF(AvatarInputs) - static AvatarInputs* INSTANCE{ nullptr }; Setting::Handle showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, false }; AvatarInputs* AvatarInputs::getInstance() { - Q_ASSERT(INSTANCE); + if (!INSTANCE) { + INSTANCE = new AvatarInputs(); + Q_ASSERT(INSTANCE); + } return INSTANCE; } -AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) { - INSTANCE = this; +AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index fb48df9d73..47f520a639 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -19,7 +19,7 @@ public: \ private: \ type _##name{ initialValue }; -class AvatarInputs : public QQuickItem { +class AvatarInputs : public QObject { Q_OBJECT HIFI_QML_DECL @@ -32,7 +32,7 @@ class AvatarInputs : public QQuickItem { public: static AvatarInputs* getInstance(); Q_INVOKABLE float loudnessToAudioLevel(float loudness); - AvatarInputs(QQuickItem* parent = nullptr); + AvatarInputs(QObject* parent = nullptr); void update(); bool showAudioTools() const { return _showAudioTools; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 48b56c7ced..4c233b986c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -24,10 +24,6 @@ #include "SnapshotAnimated.h" #include "UserActivityLogger.h" -#include "AmbientOcclusionEffect.h" -#include "AntialiasingEffect.h" -#include "RenderShadowTask.h" - void setupPreferences() { auto preferences = DependencyManager::get(); auto nodeList = DependencyManager::get(); @@ -295,30 +291,6 @@ void setupPreferences() { } #endif - - { - static const QString RENDER("Graphics"); - auto renderConfig = qApp->getRenderEngine()->getConfiguration(); - if (renderConfig) { - auto mainViewAmbientOcclusionConfig = renderConfig->getConfig("RenderMainView.AmbientOcclusion"); - if (mainViewAmbientOcclusionConfig) { - auto getter = [mainViewAmbientOcclusionConfig]()->QString { return mainViewAmbientOcclusionConfig->getPreset(); }; - auto setter = [mainViewAmbientOcclusionConfig](QString preset) { mainViewAmbientOcclusionConfig->setPreset(preset); }; - auto preference = new ComboBoxPreference(RENDER, "Ambient occlusion", getter, setter); - preference->setItems(mainViewAmbientOcclusionConfig->getPresetList()); - preferences->addPreference(preference); - } - - auto mainViewShadowConfig = renderConfig->getConfig("RenderMainView.RenderShadowTask"); - if (mainViewShadowConfig) { - auto getter = [mainViewShadowConfig]()->QString { return mainViewShadowConfig->getPreset(); }; - auto setter = [mainViewShadowConfig](QString preset) { mainViewShadowConfig->setPreset(preset); }; - auto preference = new ComboBoxPreference(RENDER, "Shadows", getter, setter); - preference->setItems(mainViewShadowConfig->getPresetList()); - preferences->addPreference(preference); - } - } - } { static const QString NETWORKING("Networking"); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index ff5a202910..f4efd1301d 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -37,7 +37,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), _drawHUDLayer(base3DOverlay->_drawHUDLayer), - _isGrabbable(base3DOverlay->_isGrabbable) + _isGrabbable(base3DOverlay->_isGrabbable), + _isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera) { setTransform(base3DOverlay->getTransform()); } @@ -142,6 +143,13 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { setIsGrabbable(isGrabbable.toBool()); } + auto isVisibleInSecondaryCamera = properties["isVisibleInSecondaryCamera"]; + if (isVisibleInSecondaryCamera.isValid()) { + bool value = isVisibleInSecondaryCamera.toBool(); + setIsVisibleInSecondaryCamera(value); + needRenderItemUpdate = true; + } + if (properties["position"].isValid()) { setLocalPosition(vec3FromVariant(properties["position"])); needRenderItemUpdate = true; @@ -221,6 +229,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. + * @property {boolean} isVisibleInSecondaryCamera=false - If true, the overlay is rendered in secondary + * camera views. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". @@ -259,6 +269,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "grabbable") { return _isGrabbable; } + if (property == "isVisibleInSecondaryCamera") { + return _isVisibleInSecondaryCamera; + } if (property == "parentID") { return getParentID(); } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index bbf064fddd..ab83a64273 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -48,6 +48,7 @@ public: bool getDrawInFront() const { return _drawInFront; } bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } + virtual bool getIsVisibleInSecondaryCamera() const override { return _isVisibleInSecondaryCamera; } void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } @@ -55,6 +56,7 @@ public: virtual void setDrawInFront(bool value) { _drawInFront = value; } virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } + virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } virtual AABox getBounds() const override = 0; @@ -92,6 +94,7 @@ protected: bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; + bool _isVisibleInSecondaryCamera { false }; mutable bool _renderVariableDirty { true }; QString _name; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 651213ae99..1c00f57eec 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -89,8 +89,11 @@ void ModelOverlay::update(float deltatime) { } if (_visibleDirty) { _visibleDirty = false; - // don't show overlays in mirrors - _model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false); + // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true + _model->setVisibleInScene(getVisible(), scene, + render::ItemKey::TAG_BITS_0 | + (_isVisibleInSecondaryCamera ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE), + false); } if (_drawInFrontDirty) { _drawInFrontDirty = false; diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 88a1729d68..3ef3f23fec 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -36,6 +36,11 @@ public: void clearSubRenderItemIDs(); void setSubRenderItemIDs(const render::ItemIDs& ids); + virtual void setIsVisibleInSecondaryCamera(bool value) override { + Base3DOverlay::setIsVisibleInSecondaryCamera(value); + _visibleDirty = true; + } + void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index f1be23ed39..2f27d50f7e 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -56,6 +56,8 @@ public: bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; + virtual bool getIsVisibleInSecondaryCamera() const { return false; } + xColor getColor(); float getAlpha(); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 3efe94c206..c3d87642f1 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -44,6 +44,8 @@ void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPro const OverlayID UNKNOWN_OVERLAY_ID = OverlayID(); /**jsdoc + * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection} or + * {@link Overlays.findRayIntersectionVector|findRayIntersectionVector}. * @typedef {object} Overlays.RayToOverlayIntersectionResult * @property {boolean} intersects - true if the {@link PickRay} intersected with a 3D overlay, otherwise * false. @@ -75,7 +77,8 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R * yourself and that aren't persisted to the domain. They are used for UI. * @namespace Overlays * @property {Uuid} keyboardFocusOverlay - Get or set the {@link Overlays.OverlayType|web3d} overlay that has keyboard focus. - * If no overlay is set, get returns null; set to null to clear keyboard focus. + * If no overlay has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to + * clear keyboard focus. */ class Overlays : public QObject { @@ -116,7 +119,7 @@ public slots: * @function Overlays.addOverlay * @param {Overlays.OverlayType} type - The type of the overlay to add. * @param {Overlays.OverlayProperties} properties - The properties of the overlay to add. - * @returns {Uuid} The ID of the newly created overlay. + * @returns {Uuid} The ID of the newly created overlay if successful, otherwise {@link Uuid|Uuid.NULL}. * @example Add a cube overlay in front of your avatar. * var overlay = Overlays.addOverlay("cube", { * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), @@ -131,7 +134,7 @@ public slots: * Create a clone of an existing overlay. * @function Overlays.cloneOverlay * @param {Uuid} overlayID - The ID of the overlay to clone. - * @returns {Uuid} The ID of the new overlay. + * @returns {Uuid} The ID of the new overlay if successful, otherwise {@link Uuid|Uuid.NULL}. * @example Add an overlay in front of your avatar, clone it, and move the clone to be above the * original. * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })); @@ -322,10 +325,8 @@ public slots: * @function Overlays.findRayIntersection * @param {PickRay} pickRay - The PickRay to use for finding overlays. * @param {boolean} [precisionPicking=false] - Unused; exists to match Entity API. - * @param {Array.} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited - * to overlays in the list. - * @param {Array.} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't - * exclude overlays in the list. + * @param {Array.} [overlayIDsToInclude=[]] - If not empty then the search is restricted to these overlays. + * @param {Array.} [overlayIDsToExclude=[]] - Overlays to ignore during the search. * @param {boolean} [visibleOnly=false] - Unused; exists to match Entity API. * @param {boolean} [collidableOnly=false] - Unused; exists to match Entity API. * @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by pickRay, taking @@ -531,7 +532,7 @@ public slots: * Set the Web3D overlay that has keyboard focus. * @function Overlays.setKeyboardFocusOverlay * @param {Uuid} overlayID - The ID of the {@link Overlays.OverlayType|web3d} overlay to set keyboard focus to. Use - * {@link Uuid|Uuid.NULL} or null to unset keyboard focus from an overlay. + * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an overlay. */ void setKeyboardFocusOverlay(const OverlayID& id); diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index f99ced0021..185547a333 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -49,7 +49,11 @@ namespace render { builder.withInvisible(); } - builder.withTagBits(render::ItemKey::TAG_BITS_0); // Only draw overlays in main view + // always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view + uint32_t viewTaskBits = render::ItemKey::TAG_BITS_0 | + (overlay->getIsVisibleInSecondaryCamera() ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE); + + builder.withTagBits(viewTaskBits); return builder.build(); } diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index e9ec0d6cf4..c27faf6f0f 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -67,6 +67,32 @@ Shape3DOverlay* Shape3DOverlay::createClone() const { } +/**jsdoc + *

A shape {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDimensionsDescription
"Circle"2DA circle oriented in 3D.
"Cone"3D
"Cube"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Line"1DA line oriented in 3D.
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
+ * @typedef {string} Overlays.Shape + */ static const std::array shapeStrings { { "Line", "Triangle", @@ -80,7 +106,7 @@ static const std::array shapeStrings "Octahedron", "Dodecahedron", "Icosahedron", - "Torus", + "Torus", // Not implemented yet. "Cone", "Cylinder" } }; @@ -145,7 +171,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * * @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: scale, size. * - * @property {Shape} shape=Hexagon - The geometrical shape of the overlay. + * @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay. */ QVariant Shape3DOverlay::getProperty(const QString& property) { if (property == "shape") { diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index bed20be698..9bb1da59d2 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -114,7 +114,7 @@ void Text3DOverlay::render(RenderArgs* args) { glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND); glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND); - DependencyManager::get()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false, false); + DependencyManager::get()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, quadColor, _geometryId); // Same font properties as textSize() diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index fba36f0c77..e7641ab2c2 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -40,8 +40,10 @@ QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml")); * * @property {number} margin=0 - Sets the leftMargin and topMargin values, in pixels. * Write-only. - * @property {number} leftMargin=0 - The left margin's size, in pixels. Write-only. - * @property {number} topMargin=0 - The top margin's size, in pixels. Write-only. + * @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin. + * Write-only. + * @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin. + * Write-only. * @property {string} text="" - The text to display. Text does not automatically wrap; use \n for a line break. Text * is clipped to the bounds. Write-only. * @property {number} font.size=18 - The size of the text, in pixels. Write-only. diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 2c37d2d089..e00cad9bc7 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -78,6 +78,20 @@ int AnimSkeleton::getParentIndex(int jointIndex) const { return _joints[jointIndex].parentIndex; } +std::vector AnimSkeleton::getChildrenOfJoint(int jointIndex) const { + // Children and grandchildren, etc. + std::vector result; + if (jointIndex != -1) { + for (int i = jointIndex + 1; i < (int)_joints.size(); i++) { + if (_joints[i].parentIndex == jointIndex + || (std::find(result.begin(), result.end(), _joints[i].parentIndex) != result.end())) { + result.push_back(i); + } + } + } + return result; +} + const QString& AnimSkeleton::getJointName(int jointIndex) const { return _joints[jointIndex].name; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 664358f414..27dbf5ea92 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -43,6 +43,7 @@ public: const AnimPose& getPostRotationPose(int jointIndex) const; int getParentIndex(int jointIndex) const; + std::vector getChildrenOfJoint(int jointIndex) const; AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& relativePoses) const; diff --git a/libraries/animation/src/ElbowConstraint.cpp b/libraries/animation/src/ElbowConstraint.cpp index 17c6bb2da6..deaf1a0945 100644 --- a/libraries/animation/src/ElbowConstraint.cpp +++ b/libraries/animation/src/ElbowConstraint.cpp @@ -66,16 +66,12 @@ bool ElbowConstraint::apply(glm::quat& rotation) const { bool twistWasClamped = (twistAngle != clampedTwistAngle); // update rotation - const float MIN_SWING_REAL_PART = 0.99999f; - if (twistWasClamped || fabsf(swingRotation.w) < MIN_SWING_REAL_PART) { - if (twistWasClamped) { - twistRotation = glm::angleAxis(clampedTwistAngle, _axis); - } - // we discard all swing and only keep twist - rotation = twistRotation * _referenceRotation; - return true; + if (twistWasClamped) { + twistRotation = glm::angleAxis(clampedTwistAngle, _axis); } - return false; + // we discard all swing and only keep twist + rotation = twistRotation * _referenceRotation; + return true; } glm::quat ElbowConstraint::computeCenterRotation() const { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d6791ab0b8..848f384687 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -199,6 +199,8 @@ void Rig::destroyAnimGraph() { _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); _numOverrides = 0; + _leftEyeJointChildren.clear(); + _rightEyeJointChildren.clear(); } void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { @@ -225,12 +227,17 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; + _leftEyeJointIndex = geometry.leftEyeJointIndex; + _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; _rightHandJointIndex = geometry.rightHandJointIndex; _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); } void Rig::reset(const FBXGeometry& geometry) { @@ -253,6 +260,8 @@ void Rig::reset(const FBXGeometry& geometry) { buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); _rootJointIndex = geometry.rootJointIndex; + _leftEyeJointIndex = geometry.leftEyeJointIndex; + _rightEyeJointIndex = geometry.rightEyeJointIndex; _leftHandJointIndex = geometry.leftHandJointIndex; _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; @@ -260,6 +269,9 @@ void Rig::reset(const FBXGeometry& geometry) { _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); + if (!_animGraphURL.isEmpty()) { _animNode.reset(); initAnimGraph(_animGraphURL); @@ -1430,6 +1442,15 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm // directly set absolutePose rotation _internalPoseSet._absolutePoses[index].rot() = deltaQuat * headQuat; + + // Update eye joint's children. + auto children = index == _leftEyeJointIndex ? _leftEyeJointChildren : _rightEyeJointChildren; + for (int i = 0; i < (int)children.size(); i++) { + int jointIndex = children[i]; + int parentIndex = _animSkeleton->getParentIndex(jointIndex); + _internalPoseSet._absolutePoses[jointIndex] = + _internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[jointIndex]; + } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7230d05e2a..e30b5d655c 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -267,6 +267,11 @@ protected: int _rootJointIndex { -1 }; + int _leftEyeJointIndex { -1 }; + int _rightEyeJointIndex { -1 }; + std::vector _leftEyeJointChildren; + std::vector _rightEyeJointChildren; + int _leftHandJointIndex { -1 }; int _leftElbowJointIndex { -1 }; int _leftShoulderJointIndex { -1 }; diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp index 3df469b830..0b8921a117 100644 --- a/libraries/audio-client/src/AudioPeakValues.cpp +++ b/libraries/audio-client/src/AudioPeakValues.cpp @@ -43,14 +43,19 @@ void AudioClient::checkPeakValues() { // prepare the windows environment CoInitialize(NULL); + std::unique_lock lock(_deviceMutex, std::defer_lock); + // if disabled, clean up active clients if (!_enablePeakValues) { - activeClients.clear(); + if (lock.try_lock()) { + // deferred, if timer callbacks overlap + activeClients.clear(); + } return; } // lock the devices so the _inputDevices list is static - std::unique_lock lock(_deviceMutex); + lock.lock(); HRESULT result; // initialize the payload diff --git a/libraries/audio/src/AudioSRCData.h b/libraries/audio/src/AudioSRCData.h index 872a8b7ea9..4a51038683 100644 --- a/libraries/audio/src/AudioSRCData.h +++ b/libraries/audio/src/AudioSRCData.h @@ -294,786 +294,1042 @@ static const float prototypeFilterLQ[PROTOTYPE_COEFS_LQ] = { }; // Minimum-phase equiripple FIR lowpass -// taps = 96, phases = 32 +// taps = 128, phases = 32 // -// passband = 20.4khz @ 44.1khz sample rate -// stopband = 22.3khz @ 44.1khz sample rate +// passband = 20.8khz @ 44.1khz sample rate +// stopband = 22.25khz @ 44.1khz sample rate // passband ripple = +-0.01dB // stopband attn = -120dB (-60dB at Fs/2) // -static const int PROTOTYPE_TAPS_MQ = 96; // filter taps per phase +static const int PROTOTYPE_TAPS_MQ = 128; // filter taps per phase static const int PROTOTYPE_PHASES_MQ = 32; // oversampling factor static const int PROTOTYPE_COEFS_MQ = PROTOTYPE_TAPS_MQ * PROTOTYPE_PHASES_MQ; static const float prototypeFilterMQ[PROTOTYPE_COEFS_MQ] = { - 0.00000000e+00f, 2.66261387e-05f, 2.32573075e-05f, 3.40598514e-05f, - 4.71976232e-05f, 6.41453728e-05f, 8.54998397e-05f, 1.11687756e-04f, - 1.43801203e-04f, 1.82845116e-04f, 2.30066959e-04f, 2.86345358e-04f, - 3.53237802e-04f, 4.32445137e-04f, 5.25448765e-04f, 6.34181809e-04f, - 7.60751248e-04f, 9.07447815e-04f, 1.07664885e-03f, 1.27106372e-03f, - 1.49358697e-03f, 1.74743568e-03f, 2.03588950e-03f, 2.36271119e-03f, - 2.73178932e-03f, 3.14735017e-03f, 3.61394575e-03f, 4.13622717e-03f, - 4.71948430e-03f, 5.36900611e-03f, 6.09056802e-03f, 6.89015275e-03f, - 7.77420063e-03f, 8.74929355e-03f, 9.82247765e-03f, 1.10010827e-02f, - 1.22926302e-02f, 1.37050920e-02f, 1.52467086e-02f, 1.69259236e-02f, - 1.87515164e-02f, 2.07324611e-02f, 2.28780155e-02f, 2.51976248e-02f, - 2.77009067e-02f, 3.03976923e-02f, 3.32978310e-02f, 3.64113360e-02f, - 3.97483567e-02f, 4.33188718e-02f, 4.71330394e-02f, 5.12008208e-02f, - 5.55321084e-02f, 6.01366326e-02f, 6.50238954e-02f, 7.02031239e-02f, - 7.56832648e-02f, 8.14728338e-02f, 8.75798730e-02f, 9.40119584e-02f, - 1.00776061e-01f, 1.07878507e-01f, 1.15324915e-01f, 1.23120096e-01f, - 1.31268060e-01f, 1.39771791e-01f, 1.48633353e-01f, 1.57853754e-01f, - 1.67432845e-01f, 1.77369245e-01f, 1.87660373e-01f, 1.98302270e-01f, - 2.09289566e-01f, 2.20615469e-01f, 2.32271687e-01f, 2.44248375e-01f, - 2.56534006e-01f, 2.69115519e-01f, 2.81978070e-01f, 2.95105197e-01f, - 3.08478608e-01f, 3.22078299e-01f, 3.35882501e-01f, 3.49867623e-01f, - 3.64008344e-01f, 3.78277579e-01f, 3.92646502e-01f, 4.07084554e-01f, - 4.21559509e-01f, 4.36037587e-01f, 4.50483333e-01f, 4.64859851e-01f, - 4.79128834e-01f, 4.93250606e-01f, 5.07184249e-01f, 5.20887717e-01f, - 5.34317956e-01f, 5.47430992e-01f, 5.60182068e-01f, 5.72525885e-01f, - 5.84416587e-01f, 5.95808093e-01f, 6.06654109e-01f, 6.16908418e-01f, - 6.26525013e-01f, 6.35458287e-01f, 6.43663262e-01f, 6.51095737e-01f, - 6.57712516e-01f, 6.63471582e-01f, 6.68332383e-01f, 6.72255982e-01f, - 6.75205267e-01f, 6.77145171e-01f, 6.78042866e-01f, 6.77868024e-01f, - 6.76592912e-01f, 6.74192735e-01f, 6.70645661e-01f, 6.65933116e-01f, - 6.60039959e-01f, 6.52954537e-01f, 6.44668978e-01f, 6.35179268e-01f, - 6.24485354e-01f, 6.12591298e-01f, 5.99505382e-01f, 5.85240204e-01f, - 5.69812635e-01f, 5.53244046e-01f, 5.35560224e-01f, 5.16791389e-01f, - 4.96972230e-01f, 4.76141864e-01f, 4.54343785e-01f, 4.31625775e-01f, - 4.08039850e-01f, 3.83642160e-01f, 3.58492768e-01f, 3.32655624e-01f, - 3.06198278e-01f, 2.79191781e-01f, 2.51710369e-01f, 2.23831288e-01f, - 1.95634574e-01f, 1.67202627e-01f, 1.38620172e-01f, 1.09973678e-01f, - 8.13511972e-02f, 5.28420521e-02f, 2.45362976e-02f, -3.47536059e-03f, - -3.11022613e-02f, -5.82539130e-02f, -8.48404277e-02f, -1.10773085e-01f, - -1.35964505e-01f, -1.60329143e-01f, -1.83783713e-01f, -2.06247521e-01f, - -2.27642912e-01f, -2.47895575e-01f, -2.66934994e-01f, -2.84694759e-01f, - -3.01112940e-01f, -3.16132369e-01f, -3.29701030e-01f, -3.41772346e-01f, - -3.52305311e-01f, -3.61264967e-01f, -3.68622493e-01f, -3.74355402e-01f, - -3.78447769e-01f, -3.80890353e-01f, -3.81680737e-01f, -3.80823346e-01f, - -3.78329604e-01f, -3.74217856e-01f, -3.68513368e-01f, -3.61248333e-01f, - -3.52461771e-01f, -3.42199371e-01f, -3.30513331e-01f, -3.17462278e-01f, - -3.03110942e-01f, -2.87529909e-01f, -2.70795402e-01f, -2.52988932e-01f, - -2.34196901e-01f, -2.14510337e-01f, -1.94024366e-01f, -1.72837915e-01f, - -1.51053161e-01f, -1.28775054e-01f, -1.06110949e-01f, -8.31699904e-02f, - -6.00625904e-02f, -3.68999700e-02f, -1.37935592e-02f, 9.14553458e-03f, - 3.18070733e-02f, 5.40822352e-02f, 7.58641385e-02f, 9.70484745e-02f, - 1.17533938e-01f, 1.37222760e-01f, 1.56021286e-01f, 1.73840407e-01f, - 1.90596011e-01f, 2.06209480e-01f, 2.20608090e-01f, 2.33725388e-01f, - 2.45501544e-01f, 2.55883728e-01f, 2.64826361e-01f, 2.72291364e-01f, - 2.78248393e-01f, 2.82675023e-01f, 2.85556849e-01f, 2.86887621e-01f, - 2.86669233e-01f, 2.84911794e-01f, 2.81633533e-01f, 2.76860737e-01f, - 2.70627618e-01f, 2.62976159e-01f, 2.53955831e-01f, 2.43623409e-01f, - 2.32042617e-01f, 2.19283824e-01f, 2.05423637e-01f, 1.90544470e-01f, - 1.74734152e-01f, 1.58085383e-01f, 1.40695263e-01f, 1.22664710e-01f, - 1.04097951e-01f, 8.51019110e-02f, 6.57856138e-02f, 4.62595576e-02f, - 2.66351206e-02f, 7.02392479e-03f, -1.24628349e-02f, -3.17149760e-02f, - -5.06239785e-02f, -6.90836040e-02f, -8.69905146e-02f, -1.04244853e-01f, - -1.20750775e-01f, -1.36417108e-01f, -1.51157814e-01f, -1.64892426e-01f, - -1.77546604e-01f, -1.89052578e-01f, -1.99349459e-01f, -2.08383604e-01f, - -2.16108994e-01f, -2.22487452e-01f, -2.27488844e-01f, -2.31091294e-01f, - -2.33281282e-01f, -2.34053710e-01f, -2.33411914e-01f, -2.31367695e-01f, - -2.27941159e-01f, -2.23160659e-01f, -2.17062542e-01f, -2.09691005e-01f, - -2.01097713e-01f, -1.91341620e-01f, -1.80488420e-01f, -1.68610294e-01f, - -1.55785433e-01f, -1.42097452e-01f, -1.27634978e-01f, -1.12491085e-01f, - -9.67626792e-02f, -8.05498893e-02f, -6.39555117e-02f, -4.70843056e-02f, - -3.00423695e-02f, -1.29364721e-02f, 4.12658213e-03f, 2.10406497e-02f, - 3.77009117e-02f, 5.40045195e-02f, 6.98511685e-02f, 8.51438453e-02f, - 9.97893434e-02f, 1.13698854e-01f, 1.26788523e-01f, 1.38979975e-01f, - 1.50200835e-01f, 1.60385073e-01f, 1.69473512e-01f, 1.77414145e-01f, - 1.84162459e-01f, 1.89681664e-01f, 1.93943026e-01f, 1.96925891e-01f, - 1.98617880e-01f, 1.99014945e-01f, 1.98121379e-01f, 1.95949757e-01f, - 1.92520816e-01f, 1.87863336e-01f, 1.82013990e-01f, 1.75016936e-01f, - 1.66923648e-01f, 1.57792538e-01f, 1.47688544e-01f, 1.36682666e-01f, - 1.24851512e-01f, 1.12276865e-01f, 9.90449817e-02f, 8.52461277e-02f, - 7.09739571e-02f, 5.63248677e-02f, 4.13973844e-02f, 2.62914655e-02f, - 1.11078925e-02f, -4.05239147e-03f, -1.90890617e-02f, -3.39029253e-02f, - -4.83966164e-02f, -6.24753705e-02f, -7.60474312e-02f, -8.90248310e-02f, - -1.01323842e-01f, -1.12865634e-01f, -1.23576716e-01f, -1.33389421e-01f, - -1.42242349e-01f, -1.50080822e-01f, -1.56857133e-01f, -1.62530890e-01f, - -1.67069336e-01f, -1.70447432e-01f, -1.72648091e-01f, -1.73662223e-01f, - -1.73488855e-01f, -1.72134990e-01f, -1.69615638e-01f, -1.65953690e-01f, - -1.61179685e-01f, -1.55331592e-01f, -1.48454607e-01f, -1.40600737e-01f, - -1.31828470e-01f, -1.22202323e-01f, -1.11792473e-01f, -1.00674140e-01f, - -8.89271262e-02f, -7.66352871e-02f, -6.38858531e-02f, -5.07689244e-02f, - -3.73767275e-02f, -2.38031024e-02f, -1.01427700e-02f, 3.50934751e-03f, - 1.70586918e-02f, 3.04117852e-02f, 4.34768954e-02f, 5.61645819e-02f, - 6.83884030e-02f, 8.00654394e-02f, 9.11168932e-02f, 1.01468633e-01f, - 1.11051648e-01f, 1.19802610e-01f, 1.27664173e-01f, 1.34585462e-01f, - 1.40522346e-01f, 1.45437787e-01f, 1.49302022e-01f, 1.52092772e-01f, - 1.53795398e-01f, 1.54402941e-01f, 1.53916196e-01f, 1.52343637e-01f, - 1.49701396e-01f, 1.46013026e-01f, 1.41309468e-01f, 1.35628632e-01f, - 1.29015253e-01f, 1.21520496e-01f, 1.13201567e-01f, 1.04121296e-01f, - 9.43477288e-02f, 8.39535107e-02f, 7.30154990e-02f, 6.16141003e-02f, - 4.98327306e-02f, 3.77571918e-02f, 2.54750964e-02f, 1.30751821e-02f, - 6.46703310e-04f, -1.17211963e-02f, -2.39401591e-02f, -3.59231836e-02f, - -4.75853156e-02f, -5.88442141e-02f, -6.96206901e-02f, -7.98393844e-02f, - -8.94291504e-02f, -9.83236922e-02f, -1.06461911e-01f, -1.13788431e-01f, - -1.20253885e-01f, -1.25815350e-01f, -1.30436540e-01f, -1.34088139e-01f, - -1.36747929e-01f, -1.38400952e-01f, -1.39039622e-01f, -1.38663699e-01f, - -1.37280328e-01f, -1.34903937e-01f, -1.31556108e-01f, -1.27265426e-01f, - -1.22067204e-01f, -1.16003256e-01f, -1.09121554e-01f, -1.01475836e-01f, - -9.31252402e-02f, -8.41338287e-02f, -7.45701074e-02f, -6.45065373e-02f, - -5.40189487e-02f, -4.31860278e-02f, -3.20886964e-02f, -2.08095155e-02f, - -9.43209682e-03f, 1.95952348e-03f, 1.32815415e-02f, 2.44509086e-02f, - 3.53860502e-02f, 4.60073767e-02f, 5.62379012e-02f, 6.60038045e-02f, - 7.52349145e-02f, 8.38653047e-02f, 9.18337009e-02f, 9.90839224e-02f, - 1.05565308e-01f, 1.11233075e-01f, 1.16048569e-01f, 1.19979672e-01f, - 1.23000853e-01f, 1.25093440e-01f, 1.26245746e-01f, 1.26453062e-01f, - 1.25717722e-01f, 1.24049103e-01f, 1.21463405e-01f, 1.17983660e-01f, - 1.13639474e-01f, 1.08466740e-01f, 1.02507425e-01f, 9.58092271e-02f, - 8.84251889e-02f, 8.04132568e-02f, 7.18358762e-02f, 6.27595398e-02f, - 5.32541869e-02f, 4.33927283e-02f, 3.32505260e-02f, 2.29047308e-02f, - 1.24337991e-02f, 1.91679812e-03f, -8.56707996e-03f, -1.89392067e-02f, - -2.91220390e-02f, -3.90397544e-02f, -4.86187492e-02f, -5.77882518e-02f, - -6.64808274e-02f, -7.46328184e-02f, -8.21849070e-02f, -8.90824700e-02f, - -9.52760405e-02f, -1.00721606e-01f, -1.05380953e-01f, -1.09221973e-01f, - -1.12218800e-01f, -1.14352078e-01f, -1.15609043e-01f, -1.15983584e-01f, - -1.15476334e-01f, -1.14094552e-01f, -1.11852147e-01f, -1.08769485e-01f, - -1.04873243e-01f, -1.00196154e-01f, -9.47768230e-02f, -8.86593336e-02f, - -8.18929115e-02f, -7.45315642e-02f, -6.66336429e-02f, -5.82614122e-02f, - -4.94804661e-02f, -4.03593526e-02f, -3.09689748e-02f, -2.13820268e-02f, - -1.16724544e-02f, -1.91492456e-03f, 7.81582939e-03f, 1.74454843e-02f, - 2.69007705e-02f, 3.61099984e-02f, 4.50035684e-02f, 5.35145862e-02f, - 6.15792651e-02f, 6.91374558e-02f, 7.61331132e-02f, 8.25146874e-02f, - 8.82354960e-02f, 9.32540795e-02f, 9.75345512e-02f, 1.01046761e-01f, - 1.03766581e-01f, 1.05676059e-01f, 1.06763536e-01f, 1.07023704e-01f, - 1.06457645e-01f, 1.05072793e-01f, 1.02882886e-01f, 9.99077707e-02f, - 9.61733515e-02f, 9.17112437e-02f, 8.65585836e-02f, 8.07577320e-02f, - 7.43559143e-02f, 6.74048223e-02f, 5.99602286e-02f, 5.20815485e-02f, - 4.38313441e-02f, 3.52748647e-02f, 2.64794784e-02f, 1.75142425e-02f, - 8.44923569e-03f, -6.44874246e-04f, -9.69744245e-03f, -1.86383724e-02f, - -2.73986645e-02f, -3.59109639e-02f, -4.41100113e-02f, -5.19332538e-02f, - -5.93212174e-02f, -6.62180197e-02f, -7.25717930e-02f, -7.83350496e-02f, - -8.34650279e-02f, -8.79240738e-02f, -9.16798665e-02f, -9.47056367e-02f, - -9.69804367e-02f, -9.84892103e-02f, -9.92229391e-02f, -9.91786555e-02f, - -9.83594994e-02f, -9.67746323e-02f, -9.44391692e-02f, -9.13740384e-02f, - -8.76057968e-02f, -8.31664374e-02f, -7.80930677e-02f, -7.24276448e-02f, - -6.62166182e-02f, -5.95105803e-02f, -5.23638154e-02f, -4.48338833e-02f, - -3.69811953e-02f, -2.88684416e-02f, -2.05602211e-02f, -1.21223919e-02f, - -3.62166500e-03f, 4.87497472e-03f, 1.33008118e-02f, 2.15898817e-02f, - 2.96774903e-02f, 3.75007710e-02f, 4.49990900e-02f, 5.21145974e-02f, - 5.87926126e-02f, 6.49820878e-02f, 7.06359734e-02f, 7.57115940e-02f, - 8.01709511e-02f, 8.39810244e-02f, 8.71140281e-02f, 8.95475866e-02f, - 9.12649204e-02f, 9.22549366e-02f, 9.25123481e-02f, 9.20376276e-02f, - 9.08370647e-02f, 8.89226233e-02f, 8.63119090e-02f, 8.30279355e-02f, - 7.90990135e-02f, 7.45584160e-02f, 6.94441713e-02f, 6.37987430e-02f, - 5.76686372e-02f, 5.11040823e-02f, 4.41585505e-02f, 3.68884245e-02f, - 2.93523999e-02f, 2.16111886e-02f, 1.37268658e-02f, 5.76249790e-03f, - -2.21844412e-03f, -1.01525268e-02f, -1.79769055e-02f, -2.56297984e-02f, - -3.30509798e-02f, -4.01822180e-02f, -4.69678148e-02f, -5.33549679e-02f, - -5.92942039e-02f, -6.47397915e-02f, -6.96500034e-02f, -7.39875785e-02f, - -7.77198807e-02f, -8.08192090e-02f, -8.32629929e-02f, -8.50339403e-02f, - -8.61201727e-02f, -8.65153352e-02f, -8.62185642e-02f, -8.52345328e-02f, - -8.35733873e-02f, -8.12506452e-02f, -7.82870548e-02f, -7.47083936e-02f, - -7.05453033e-02f, -6.58330086e-02f, -6.06109755e-02f, -5.49226267e-02f, - -4.88150284e-02f, -4.23383952e-02f, -3.55457806e-02f, -2.84925949e-02f, - -2.12361386e-02f, -1.38352158e-02f, -6.34954674e-03f, 1.16060193e-03f, - 8.63496366e-03f, 1.60137521e-02f, 2.32381310e-02f, 3.02506552e-02f, - 3.69957912e-02f, 4.34202737e-02f, 4.94736003e-02f, 5.51084064e-02f, - 6.02807966e-02f, 6.49507475e-02f, 6.90823813e-02f, 7.26442553e-02f, - 7.56096025e-02f, 7.79565049e-02f, 7.96681192e-02f, 8.07327409e-02f, - 8.11438831e-02f, 8.09003904e-02f, 8.00063059e-02f, 7.84709455e-02f, - 7.63087509e-02f, 7.35391471e-02f, 7.01864251e-02f, 6.62794512e-02f, - 6.18515053e-02f, 5.69399870e-02f, 5.15860100e-02f, 4.58342039e-02f, - 3.97322256e-02f, 3.33304486e-02f, 2.66814783e-02f, 1.98397856e-02f, - 1.28612257e-02f, 5.80259393e-03f, -1.27886112e-03f, -8.32584019e-03f, - -1.52814930e-02f, -2.20899334e-02f, -2.86965591e-02f, -3.50486065e-02f, - -4.10954995e-02f, -4.67893136e-02f, -5.20850948e-02f, -5.69412403e-02f, - -6.13198474e-02f, -6.51869890e-02f, -6.85129647e-02f, -7.12725306e-02f, - -7.34451585e-02f, -7.50150843e-02f, -7.59714899e-02f, -7.63085524e-02f, - -7.60255166e-02f, -7.51266167e-02f, -7.36211027e-02f, -7.15230741e-02f, - -6.88514539e-02f, -6.56297130e-02f, -6.18857220e-02f, -5.76515554e-02f, - -5.29631207e-02f, -4.78599233e-02f, -4.23847313e-02f, -3.65832079e-02f, - -3.05035130e-02f, -2.41959566e-02f, -1.77125171e-02f, -1.11065052e-02f, - -4.43199940e-03f, 2.25646608e-03f, 8.90442951e-03f, 1.54579021e-02f, - 2.18638281e-02f, 2.80705462e-02f, 3.40280794e-02f, 3.96887204e-02f, - 4.50072604e-02f, 4.99414316e-02f, 5.44522079e-02f, 5.85041536e-02f, - 6.20656397e-02f, 6.51091720e-02f, 6.76114887e-02f, 6.95538952e-02f, - 7.09222580e-02f, 7.17071898e-02f, 7.19040960e-02f, 7.15131728e-02f, - 7.05394351e-02f, 6.89926231e-02f, 6.68871545e-02f, 6.42419416e-02f, - 6.10802598e-02f, 5.74295313e-02f, 5.33211189e-02f, 4.87899934e-02f, - 4.38745121e-02f, 3.86160670e-02f, 3.30586967e-02f, 2.72487970e-02f, - 2.12346816e-02f, 1.50661848e-02f, 8.79427315e-03f, 2.47061577e-03f, - -3.85286592e-03f, -1.01243770e-02f, -1.62927172e-02f, -2.23076581e-02f, - -2.81203948e-02f, -3.36839076e-02f, -3.89533679e-02f, -4.38864517e-02f, - -4.84437422e-02f, -5.25890421e-02f, -5.62895706e-02f, -5.95163477e-02f, - -6.22443207e-02f, -6.44526403e-02f, -6.61247499e-02f, -6.72485494e-02f, - -6.78164946e-02f, -6.78255920e-02f, -6.72774647e-02f, -6.61782995e-02f, - -6.45388112e-02f, -6.23740965e-02f, -5.97035328e-02f, -5.65505921e-02f, - -5.29426881e-02f, -4.89108139e-02f, -4.44894260e-02f, -3.97160733e-02f, - -3.46310671e-02f, -2.92771850e-02f, -2.36993008e-02f, -1.79439902e-02f, - -1.20591515e-02f, -6.09362869e-03f, -9.67646869e-05f, 5.88193161e-03f, - 1.17933059e-02f, 1.75888522e-02f, 2.32211575e-02f, 2.86443333e-02f, - 3.38142963e-02f, 3.86892021e-02f, 4.32297427e-02f, 4.73994738e-02f, - 5.11651476e-02f, 5.44969062e-02f, 5.73685597e-02f, 5.97578063e-02f, - 6.16463827e-02f, 6.30201712e-02f, 6.38694025e-02f, 6.41886234e-02f, - 6.39767725e-02f, 6.32372156e-02f, 6.19775942e-02f, 6.02098911e-02f, - 5.79502219e-02f, 5.52186945e-02f, 5.20393403e-02f, 4.84397091e-02f, - 4.44508421e-02f, 4.01068641e-02f, 3.54447399e-02f, 3.05039700e-02f, - 2.53262499e-02f, 1.99550952e-02f, 1.44355284e-02f, 8.81365390e-03f, - 3.13628120e-03f, -2.54943138e-03f, -8.19639523e-03f, -1.37579753e-02f, - -1.91883895e-02f, -2.44430618e-02f, -2.94789913e-02f, -3.42551088e-02f, - -3.87326249e-02f, -4.28753310e-02f, -4.66498901e-02f, -5.00261035e-02f, - -5.29771799e-02f, -5.54799037e-02f, -5.75148383e-02f, -5.90665317e-02f, - -6.01234993e-02f, -6.06784603e-02f, -6.07283027e-02f, -6.02740708e-02f, - -5.93210354e-02f, -5.78785948e-02f, -5.59601225e-02f, -5.35829951e-02f, - -5.07682523e-02f, -4.75405757e-02f, -4.39279759e-02f, -3.99615901e-02f, - -3.56753971e-02f, -3.11059445e-02f, -2.62920307e-02f, -2.12743678e-02f, - -1.60952574e-02f, -1.07981986e-02f, -5.42758786e-03f, -2.82820897e-05f, - 5.35475816e-03f, 1.06767918e-02f, 1.58937793e-02f, 2.09626000e-02f, - 2.58415407e-02f, 3.04905869e-02f, 3.48717018e-02f, 3.89492209e-02f, - 4.26901199e-02f, 4.60642317e-02f, 4.90445439e-02f, 5.16074029e-02f, - 5.37326813e-02f, 5.54039762e-02f, 5.66086803e-02f, 5.73381350e-02f, - 5.75876520e-02f, 5.73565533e-02f, 5.66481680e-02f, 5.54698025e-02f, - 5.38326410e-02f, 5.17516706e-02f, 4.92455315e-02f, 4.63363691e-02f, - 4.30496184e-02f, 3.94137631e-02f, 3.54601772e-02f, 3.12227524e-02f, - 2.67376509e-02f, 2.20430273e-02f, 1.71786786e-02f, 1.21856622e-02f, - 7.10604036e-03f, 1.98245601e-03f, -3.14218264e-03f, -8.22510738e-03f, - -1.32239734e-02f, -1.80973012e-02f, -2.28047577e-02f, -2.73075056e-02f, - -3.15685177e-02f, -3.55529069e-02f, -3.92281902e-02f, -4.25645528e-02f, - -4.55351013e-02f, -4.81160565e-02f, -5.02870006e-02f, -5.20309689e-02f, - -5.33346001e-02f, -5.41883074e-02f, -5.45862315e-02f, -5.45263860e-02f, - -5.40105902e-02f, -5.30444830e-02f, -5.16374652e-02f, -4.98025685e-02f, - -4.75564009e-02f, -4.49189535e-02f, -4.19134372e-02f, -3.85660549e-02f, - -3.49058685e-02f, -3.09643739e-02f, -2.67754233e-02f, -2.23747988e-02f, - -1.77999754e-02f, -1.30897755e-02f, -8.28403078e-03f, -3.42329111e-03f, - 1.45156003e-03f, 6.29963147e-03f, 1.10803882e-02f, 1.57539476e-02f, - 2.02814494e-02f, 2.46253752e-02f, 2.87498179e-02f, 3.26208554e-02f, - 3.62067406e-02f, 3.94782545e-02f, 4.24088471e-02f, 4.49749385e-02f, - 4.71560491e-02f, 4.89349963e-02f, 5.02980346e-02f, 5.12349025e-02f, - 5.17389769e-02f, 5.18073110e-02f, 5.14405369e-02f, 5.06430401e-02f, - 4.94227353e-02f, 4.77911027e-02f, 4.57630135e-02f, 4.33566667e-02f, - 4.05933433e-02f, 3.74973028e-02f, 3.40955069e-02f, 3.04173973e-02f, - 2.64947139e-02f, 2.23610806e-02f, 1.80518625e-02f, 1.36037814e-02f, - 9.05461294e-03f, 4.44289505e-03f, -1.92440179e-04f, -4.81233436e-03f, - -9.37799893e-03f, -1.38512169e-02f, -1.81946099e-02f, -2.23720431e-02f, - -2.63488613e-02f, -3.00921879e-02f, -3.35711998e-02f, -3.67574146e-02f, - -3.96248452e-02f, -4.21502967e-02f, -4.43135012e-02f, -4.60972960e-02f, - -4.74877737e-02f, -4.84743534e-02f, -4.90498864e-02f, -4.92107432e-02f, - -4.89567473e-02f, -4.82912269e-02f, -4.72210048e-02f, -4.57562287e-02f, - -4.39103965e-02f, -4.17001535e-02f, -3.91451580e-02f, -3.62679524e-02f, - -3.30936915e-02f, -2.96499930e-02f, -2.59666361e-02f, -2.20753766e-02f, - -1.80096001e-02f, -1.38040903e-02f, -9.49470398e-03f, -5.11805324e-03f, - -7.11263614e-04f, 3.68845212e-03f, 8.04397633e-03f, 1.23187361e-02f, - 1.64768878e-02f, 2.04837107e-02f, 2.43058243e-02f, 2.79115316e-02f, - 3.12710307e-02f, 3.43566819e-02f, 3.71432406e-02f, 3.96080600e-02f, - 4.17312589e-02f, 4.34958998e-02f, 4.48881324e-02f, 4.58972728e-02f, - 4.65159011e-02f, 4.67399486e-02f, 4.65686301e-02f, 4.60045573e-02f, - 4.50536126e-02f, 4.37249572e-02f, 4.20309099e-02f, 3.99868149e-02f, - 3.76109658e-02f, 3.49244023e-02f, 3.19507068e-02f, 2.87158399e-02f, - 2.52478981e-02f, 2.15768722e-02f, 1.77343611e-02f, 1.37533396e-02f, - 9.66788343e-03f, 5.51281005e-03f, 1.32343973e-03f, -2.86465940e-03f, - -7.01611627e-03f, -1.10959241e-02f, -1.50697748e-02f, -1.89043738e-02f, - -2.25676833e-02f, -2.60292100e-02f, -2.92602731e-02f, -3.22341600e-02f, - -3.49264870e-02f, -3.73152937e-02f, -3.93812165e-02f, -4.11077217e-02f, - -4.24811803e-02f, -4.34909828e-02f, -4.41296386e-02f, -4.43928125e-02f, - -4.42793688e-02f, -4.37913678e-02f, -4.29339923e-02f, -4.17156128e-02f, - -4.01475749e-02f, -3.82441650e-02f, -3.60224960e-02f, -3.35022818e-02f, - -3.07057642e-02f, -2.76574300e-02f, -2.43838133e-02f, -2.09133302e-02f, - -1.72759525e-02f, -1.35029658e-02f, -9.62678336e-03f, -5.68051801e-03f, - -1.69782450e-03f, 2.28741709e-03f, 6.24142092e-03f, 1.01307640e-02f, - 1.39226541e-02f, 1.75852206e-02f, 2.10877784e-02f, 2.44011197e-02f, - 2.74976630e-02f, 3.03517861e-02f, 3.29399891e-02f, 3.52410789e-02f, - 3.72364000e-02f, 3.89098736e-02f, 4.02482712e-02f, 4.12411943e-02f, - 4.18812295e-02f, 4.21639571e-02f, 4.20880553e-02f, 4.16551548e-02f, - 4.08700173e-02f, 3.97403235e-02f, 3.82766485e-02f, 3.64924037e-02f, - 3.44036670e-02f, 3.20290679e-02f, 2.93895781e-02f, 2.65084084e-02f, - 2.34107263e-02f, 2.01234514e-02f, 1.66750736e-02f, 1.30953481e-02f, - 9.41506492e-03f, 5.66577314e-03f, 1.87953721e-03f, -1.91138340e-03f, - -5.67474970e-03f, -9.37866958e-03f, -1.29918571e-02f, -1.64838496e-02f, - -1.98253468e-02f, -2.29883663e-02f, -2.59465660e-02f, -2.86753884e-02f, - -3.11523087e-02f, -3.33570024e-02f, -3.52715334e-02f, -3.68804752e-02f, - -3.81710220e-02f, -3.91331489e-02f, -3.97596215e-02f, -4.00461207e-02f, - -3.99911716e-02f, -3.95962751e-02f, -3.88657519e-02f, -3.78068352e-02f, - -3.64294686e-02f, -3.47463159e-02f, -3.27725952e-02f, -3.05259482e-02f, - -2.80262873e-02f, -2.52956368e-02f, -2.23579079e-02f, -1.92387018e-02f, - -1.59651184e-02f, -1.25654653e-02f, -9.06903728e-03f, -5.50589383e-03f, - -1.90658612e-03f, 1.69819644e-03f, 5.27767591e-03f, 8.80149662e-03f, - 1.22398002e-02f, 1.55635605e-02f, 1.87448095e-02f, 2.17568784e-02f, - 2.45746067e-02f, 2.71745749e-02f, 2.95352660e-02f, 3.16372730e-02f, - 3.34634806e-02f, 3.49991083e-02f, 3.62319569e-02f, 3.71524003e-02f, - 3.77535229e-02f, 3.80311360e-02f, 3.79838595e-02f, 3.76130115e-02f, - 3.69227353e-02f, 3.59198588e-02f, 3.46138556e-02f, 3.30167422e-02f, - 3.11429955e-02f, 2.90094255e-02f, 2.66349737e-02f, 2.40406079e-02f, - 2.12490922e-02f, 1.82848227e-02f, 1.51735699e-02f, 1.19423378e-02f, - 8.61899874e-03f, 5.23220696e-03f, 1.81100681e-03f, -1.61529876e-03f, - -5.01747104e-03f, -8.36657631e-03f, -1.16341674e-02f, -1.47926336e-02f, - -1.78153429e-02f, -2.06769168e-02f, -2.33533934e-02f, -2.58225130e-02f, - -2.80638107e-02f, -3.00588248e-02f, -3.17912703e-02f, -3.32471461e-02f, - -3.44148496e-02f, -3.52852564e-02f, -3.58518459e-02f, -3.61106776e-02f, - -3.60604528e-02f, -3.57025239e-02f, -3.50408687e-02f, -3.40820566e-02f, - -3.28351332e-02f, -3.13116305e-02f, -2.95253828e-02f, -2.74924261e-02f, - -2.52308529e-02f, -2.27606768e-02f, -2.01036311e-02f, -1.72829328e-02f, - -1.43232049e-02f, -1.12501406e-02f, -8.09033460e-03f, -4.87102079e-03f, - -1.61991454e-03f, 1.63511391e-03f, 4.86626363e-03f, 8.04597224e-03f, - 1.11472224e-02f, 1.41437409e-02f, 1.70102606e-02f, 1.97226336e-02f, - 2.22581567e-02f, 2.45957058e-02f, 2.67158611e-02f, 2.86011679e-02f, - 3.02362198e-02f, 3.16077891e-02f, 3.27049359e-02f, 3.35191077e-02f, - 3.40442066e-02f, 3.42766034e-02f, 3.42151913e-02f, 3.38613937e-02f, - 3.32191151e-02f, 3.22947056e-02f, 3.10969473e-02f, 2.96368987e-02f, - 2.79278426e-02f, 2.59851513e-02f, 2.38261551e-02f, 2.14699783e-02f, - 1.89373695e-02f, 1.62505430e-02f, 1.34329466e-02f, 1.05090853e-02f, - 7.50431274e-03f, 4.44457496e-03f, 1.35623378e-03f, -1.73420401e-03f, - -4.80027676e-03f, -7.81581580e-03f, -1.07551646e-02f, -1.35933944e-02f, - -1.63065125e-02f, -1.88716732e-02f, -2.12673829e-02f, -2.34736329e-02f, - -2.54721072e-02f, -2.72463321e-02f, -2.87818120e-02f, -3.00661165e-02f, - -3.10890008e-02f, -3.18425307e-02f, -3.23210635e-02f, -3.25213258e-02f, - -3.24424720e-02f, -3.20860357e-02f, -3.14558962e-02f, -3.05582934e-02f, - -2.94017600e-02f, -2.79969684e-02f, -2.63567172e-02f, -2.44957911e-02f, - -2.24308005e-02f, -2.01800752e-02f, -1.77634589e-02f, -1.52021705e-02f, - -1.25186056e-02f, -9.73612725e-03f, -6.87889048e-03f, -3.97162201e-03f, - -1.03936748e-03f, 1.89264412e-03f, 4.79928751e-03f, 7.65573326e-03f, - 1.04376485e-02f, 1.31214182e-02f, 1.56843167e-02f, 1.81047816e-02f, - 2.03624722e-02f, 2.24385491e-02f, 2.43157747e-02f, 2.59786613e-02f, - 2.74136059e-02f, 2.86090270e-02f, 2.95554085e-02f, 3.02453933e-02f, - 3.06738695e-02f, 3.08379709e-02f, 3.07371080e-02f, 3.03729625e-02f, - 2.97494799e-02f, 2.88728239e-02f, 2.77512742e-02f, 2.63952326e-02f, - 2.48170551e-02f, 2.30309672e-02f, 2.10529469e-02f, 1.89005531e-02f, - 1.65928079e-02f, 1.41499753e-02f, 1.15934547e-02f, 8.94553439e-03f, - 6.22921565e-03f, 3.46802456e-03f, 6.85807660e-04f, -2.09351762e-03f, - -4.84607801e-03f, -7.54834950e-03f, -1.01772976e-02f, -1.27105702e-02f, - -1.51267312e-02f, -1.74054170e-02f, -1.95274752e-02f, -2.14752102e-02f, - -2.32324132e-02f, -2.47846112e-02f, -2.61191248e-02f, -2.72251852e-02f, - -2.80939857e-02f, -2.87188307e-02f, -2.90951296e-02f, -2.92204205e-02f, - -2.90944185e-02f, -2.87189899e-02f, -2.80981757e-02f, -2.72380366e-02f, - -2.61467465e-02f, -2.48343900e-02f, -2.33129547e-02f, -2.15961399e-02f, - -1.96993447e-02f, -1.76394103e-02f, -1.54345417e-02f, -1.31041691e-02f, - -1.06686731e-02f, -8.14934589e-03f, -5.56806233e-03f, -2.94722846e-03f, - -3.09480282e-04f, 2.32248018e-03f, 4.92603655e-03f, 7.47890755e-03f, - 9.95933342e-03f, 1.23462104e-02f, 1.46193259e-02f, 1.67595114e-02f, - 1.87488002e-02f, 2.05705588e-02f, 2.22096708e-02f, 2.36526409e-02f, - 2.48876669e-02f, 2.59048208e-02f, 2.66960464e-02f, 2.72552501e-02f, - 2.75783775e-02f, 2.76634150e-02f, 2.75103717e-02f, 2.71213402e-02f, - 2.65004170e-02f, 2.56536729e-02f, 2.45891352e-02f, 2.33166362e-02f, - 2.18477931e-02f, 2.01958648e-02f, 1.83756573e-02f, 1.64033346e-02f, - 1.42963876e-02f, 1.20733364e-02f, 9.75368948e-03f, 7.35772256e-03f, - 4.90625681e-03f, 2.42058549e-03f, -7.78123459e-05f, -2.56740681e-03f, - -5.02679577e-03f, -7.43492567e-03f, -9.77127015e-03f, -1.20159192e-02f, - -1.41498747e-02f, -1.61551576e-02f, -1.80148982e-02f, -1.97135784e-02f, - -2.12371093e-02f, -2.25729462e-02f, -2.37102058e-02f, -2.46397233e-02f, - -2.53542230e-02f, -2.58482111e-02f, -2.61181554e-02f, -2.61624917e-02f, - -2.59815513e-02f, -2.55776241e-02f, -2.49549139e-02f, -2.41194964e-02f, - -2.30792716e-02f, -2.18438342e-02f, -2.04244587e-02f, -1.88339919e-02f, - -1.70866610e-02f, -1.51980228e-02f, -1.31848099e-02f, -1.10647537e-02f, - -8.85646007e-03f, -6.57922197e-03f, -4.25289625e-03f, -1.89763580e-03f, - 4.66153419e-04f, 2.81808458e-03f, 5.13797202e-03f, 7.40592877e-03f, - 9.60259119e-03f, 1.17092799e-02f, 1.37081595e-02f, 1.55823451e-02f, - 1.73161398e-02f, 1.88950442e-02f, 2.03059901e-02f, 2.15373720e-02f, - 2.25791706e-02f, 2.34230473e-02f, 2.40623953e-02f, 2.44923608e-02f, - 2.47099481e-02f, 2.47139948e-02f, 2.45051449e-02f, 2.40859346e-02f, - 2.34606570e-02f, 2.26353881e-02f, 2.16179088e-02f, 2.04175981e-02f, - 1.90454439e-02f, 1.75138196e-02f, 1.58364356e-02f, 1.40282761e-02f, - 1.21053168e-02f, 1.00845358e-02f, 7.98365780e-03f, 5.82106543e-03f, - 3.61560367e-03f, 1.38641929e-03f, -8.47218784e-04f, -3.06599466e-03f, - -5.25087774e-03f, -7.38313991e-03f, -9.44455890e-03f, -1.14176395e-02f, - -1.32856363e-02f, -1.50328238e-02f, -1.66445337e-02f, -1.81073376e-02f, - -1.94091057e-02f, -2.05391909e-02f, -2.14883924e-02f, -2.22491632e-02f, - -2.28155723e-02f, -2.31833780e-02f, -2.33501014e-02f, -2.33149599e-02f, - -2.30789680e-02f, -2.26448166e-02f, -2.20169170e-02f, -2.12013570e-02f, - -2.02057946e-02f, -1.90394186e-02f, -1.77128837e-02f, -1.62381452e-02f, - -1.46284655e-02f, -1.28981349e-02f, -1.10625252e-02f, -9.13782192e-03f, - -7.14089458e-03f, -5.08925289e-03f, -3.00076224e-03f, -8.93563929e-04f, - 1.21412493e-03f, 3.30408095e-03f, 5.35835840e-03f, 7.35936068e-03f, - 9.29000657e-03f, 1.11338879e-02f, 1.28753714e-02f, 1.44998123e-02f, - 1.59935874e-02f, 1.73442409e-02f, 1.85406270e-02f, 1.95729684e-02f, - 2.04328853e-02f, 2.11135770e-02f, 2.16097716e-02f, 2.19178390e-02f, - 2.20357483e-02f, 2.19631551e-02f, 2.17013288e-02f, 2.12531940e-02f, - 2.06232436e-02f, 1.98175530e-02f, 1.88436810e-02f, 1.77106134e-02f, - 1.64286792e-02f, 1.50094525e-02f, 1.34656661e-02f, 1.18110672e-02f, - 1.00603222e-02f, 8.22887936e-03f, 6.33283241e-03f, 4.38874672e-03f, - 2.41360251e-03f, 4.24522718e-04f, -1.56124462e-03f, -3.52657933e-03f, - -5.45459389e-03f, -7.32876872e-03f, -9.13310880e-03f, -1.08522951e-02f, - -1.24717730e-02f, -1.39778955e-02f, -1.53580868e-02f, -1.66008540e-02f, - -1.76959435e-02f, -1.86344131e-02f, -1.94087423e-02f, -2.00127825e-02f, - -2.04419305e-02f, -2.06931003e-02f, -2.07647439e-02f, -2.06568662e-02f, - -2.03710410e-02f, -1.99103562e-02f, -1.92794106e-02f, -1.84842504e-02f, - -1.75323167e-02f, -1.64323977e-02f, -1.51944972e-02f, -1.38297917e-02f, - -1.23505473e-02f, -1.07699177e-02f, -9.10190890e-03f, -7.36125336e-03f, - -5.56322740e-03f, -3.72358380e-03f, -1.85834629e-03f, 1.62797268e-05f, - 1.88406706e-03f, 3.72890929e-03f, 5.53494881e-03f, 7.28672421e-03f, - 8.96929998e-03f, 1.05683732e-02f, 1.20704430e-02f, 1.34628666e-02f, - 1.47340226e-02f, 1.58733625e-02f, 1.68714835e-02f, 1.77202945e-02f, - 1.84129995e-02f, 1.89441562e-02f, 1.93097640e-02f, 1.95072568e-02f, - 1.95355280e-02f, 1.93949451e-02f, 1.90873200e-02f, 1.86159281e-02f, - 1.79854186e-02f, 1.72018134e-02f, 1.62724351e-02f, 1.52058602e-02f, - 1.40117601e-02f, 1.27009429e-02f, 1.12851595e-02f, 9.77701852e-03f, - 8.18987013e-03f, 6.53774227e-03f, 4.83514325e-03f, 3.09695569e-03f, - 1.33834551e-03f, -4.25417736e-04f, -2.17907719e-03f, -3.90750635e-03f, - -5.59584405e-03f, -7.22967588e-03f, -8.79504135e-03f, -1.02786946e-02f, - -1.16680841e-02f, -1.29515500e-02f, -1.41183852e-02f, -1.51589335e-02f, - -1.60646521e-02f, -1.68282128e-02f, -1.74435295e-02f, -1.79058268e-02f, - -1.82116669e-02f, -1.83589783e-02f, -1.83470786e-02f, -1.81766429e-02f, - -1.78497359e-02f, -1.73697470e-02f, -1.67414052e-02f, -1.59706714e-02f, - -1.50647456e-02f, -1.40319474e-02f, -1.28817007e-02f, -1.16243487e-02f, - -1.02711700e-02f, -8.83423668e-03f, -7.32626993e-03f, -5.76055872e-03f, - -4.15088347e-03f, -2.51130684e-03f, -8.56121139e-04f, 8.00308080e-04f, - 2.44364656e-03f, 4.05971160e-03f, 5.63466432e-03f, 7.15502326e-03f, - 8.60784346e-03f, 9.98079276e-03f, 1.12623356e-02f, 1.24416991e-02f, - 1.35090584e-02f, 1.44556378e-02f, 1.52736545e-02f, 1.59565533e-02f, - 1.64989049e-02f, 1.68965640e-02f, 1.71466261e-02f, 1.72474699e-02f, - 1.71988001e-02f, 1.70016009e-02f, 1.66581092e-02f, 1.61718740e-02f, - 1.55476166e-02f, 1.47912749e-02f, 1.39098794e-02f, 1.29115146e-02f, - 1.18052872e-02f, 1.06011344e-02f, 9.30987887e-03f, 7.94299705e-03f, - 6.51261294e-03f, 5.03132373e-03f, 3.51216505e-03f, 1.96838522e-03f, - 4.13468447e-04f, -1.13910857e-03f, -2.67593573e-03f, -4.18373241e-03f, - -5.64960835e-03f, -7.06102465e-03f, -8.40597413e-03f, -9.67306954e-03f, - -1.08516566e-02f, -1.19318601e-02f, -1.29047339e-02f, -1.37622291e-02f, - -1.44974073e-02f, -1.51043649e-02f, -1.55783142e-02f, -1.59157002e-02f, - -1.61141039e-02f, -1.61723481e-02f, -1.60904670e-02f, -1.58696900e-02f, - -1.55124969e-02f, -1.50224681e-02f, -1.44043694e-02f, -1.36640534e-02f, - -1.28083790e-02f, -1.18451989e-02f, -1.07832734e-02f, -9.63216695e-03f, - -8.40217979e-03f, -7.10427993e-03f, -5.74993358e-03f, -4.35111118e-03f, - -2.92006835e-03f, -1.46932453e-03f, -1.15175859e-05f, 1.44071303e-03f, - 2.87483552e-03f, 4.27848214e-03f, 5.63962178e-03f, 6.94667140e-03f, - 8.18849175e-03f, 9.35461319e-03f, 1.04352259e-02f, 1.14213169e-02f, - 1.23047082e-02f, 1.30781646e-02f, 1.37354286e-02f, 1.42712333e-02f, - 1.46814453e-02f, 1.49630117e-02f, 1.51139756e-02f, 1.51335483e-02f, - 1.50220835e-02f, 1.47810620e-02f, 1.44130827e-02f, 1.39218007e-02f, - 1.33119894e-02f, 1.25893957e-02f, 1.17606823e-02f, 1.08334768e-02f, - 9.81618503e-03f, 8.71796809e-03f, 7.54864594e-03f, 6.31863400e-03f, - 5.03883063e-03f, 3.72048341e-03f, 2.37517536e-03f, 1.01468348e-03f, - -3.49196130e-04f, -1.70461937e-03f, -3.03987673e-03f, -4.34350332e-03f, - -5.60433339e-03f, -6.81160996e-03f, -7.95512104e-03f, -9.02519096e-03f, - -1.00128542e-02f, -1.09099085e-02f, -1.17089124e-02f, -1.24033803e-02f, - -1.29876865e-02f, -1.34572282e-02f, -1.38083913e-02f, -1.40386233e-02f, - -1.41463921e-02f, -1.41312813e-02f, -1.39939047e-02f, -1.37359379e-02f, - -1.33601191e-02f, -1.28701544e-02f, -1.22707533e-02f, -1.15675607e-02f, - -1.07670757e-02f, -9.87660975e-03f, -8.90426422e-03f, -7.85876691e-03f, - -6.74947754e-03f, -5.58627059e-03f, -4.37942163e-03f, -3.13956447e-03f, - -1.87757286e-03f, -6.04471683e-04f, 6.68687153e-04f, 1.93086297e-03f, - 3.17116571e-03f, 4.37896308e-03f, 5.54391133e-03f, 6.65608640e-03f, - 7.70610236e-03f, 8.68508463e-03f, 9.58487893e-03f, 1.03979809e-02f, - 1.11177300e-02f, 1.17382727e-02f, 1.22546165e-02f, 1.26627517e-02f, - 1.29595607e-02f, 1.31429469e-02f, 1.32117696e-02f, 1.31659380e-02f, - 1.30062790e-02f, 1.27346679e-02f, 1.23539183e-02f, 1.18677784e-02f, - 1.12808819e-02f, 1.05987296e-02f, 9.82765005e-03f, 8.97466282e-03f, - 8.04750011e-03f, 7.05450097e-03f, 6.00455814e-03f, 4.90697069e-03f, - 3.77147007e-03f, 2.60803032e-03f, 1.42685205e-03f, 2.38252620e-04f, - -9.47456984e-04f, -2.12001557e-03f, -3.26930604e-03f, -4.38549571e-03f, - -5.45903303e-03f, -6.48080118e-03f, -7.44217435e-03f, -8.33506789e-03f, - -9.15202991e-03f, -9.88632138e-03f, -1.05319082e-02f, -1.10835810e-02f, - -1.15369475e-02f, -1.18884995e-02f, -1.21356183e-02f, -1.22765935e-02f, - -1.23106741e-02f, -1.22379903e-02f, -1.20596309e-02f, -1.17775907e-02f, - -1.13947504e-02f, -1.09148490e-02f, -1.03424524e-02f, -9.68292516e-03f, - -8.94233597e-03f, -8.12744125e-03f, -7.24562722e-03f, -6.30481777e-03f, - -5.31340458e-03f, -4.28019210e-03f, -3.21432388e-03f, -2.12514582e-03f, - -1.02222518e-03f, 8.48402655e-05f, 1.18644055e-03f, 2.27305998e-03f, - 3.33534080e-03f, 4.36417880e-03f, 5.35082505e-03f, 6.28688841e-03f, - 7.16448437e-03f, 7.97626556e-03f, 8.71547416e-03f, 9.37602127e-03f, - 9.95252480e-03f, 1.04403423e-02f, 1.08356349e-02f, 1.11353709e-02f, - 1.13373727e-02f, 1.14403200e-02f, 1.14437206e-02f, 1.13479929e-02f, - 1.11544081e-02f, 1.08650423e-02f, 1.04828191e-02f, 1.00114740e-02f, - 9.45546226e-03f, 8.81995782e-03f, 8.11083408e-03f, 7.33454940e-03f, - 6.49810508e-03f, 5.60902655e-03f, 4.67524423e-03f, 3.70504943e-03f, - 2.70698716e-03f, 1.68984103e-03f, 6.62511289e-04f, -3.66063830e-04f, - -1.38696193e-03f, -2.39137620e-03f, -3.37067114e-03f, -4.31647732e-03f, - -5.22074273e-03f, -6.07581700e-03f, -6.87449892e-03f, -7.61012299e-03f, - -8.27659902e-03f, -8.86843891e-03f, -9.38086115e-03f, -9.80976718e-03f, - -1.01518071e-02f, -1.04044062e-02f, -1.05657705e-02f, -1.06349047e-02f, - -1.06116070e-02f, -1.04965065e-02f, -1.02910004e-02f, -9.99726062e-03f, - -9.61823783e-03f, -9.15759359e-03f, -8.61966847e-03f, -8.00948048e-03f, - -7.33263803e-03f, -6.59530070e-03f, -5.80411820e-03f, -4.96619474e-03f, - -4.08901524e-03f, -3.18032417e-03f, -2.24815040e-03f, -1.30069573e-03f, - -3.46202014e-04f, 6.07005678e-04f, 1.55067386e-03f, 2.47666319e-03f, - 3.37705799e-03f, 4.24414413e-03f, 5.07055312e-03f, 5.84933606e-03f, - 6.57394125e-03f, 7.23833180e-03f, 7.83701102e-03f, 8.36512776e-03f, - 8.81838829e-03f, 9.19320845e-03f, 9.48670950e-03f, 9.69671225e-03f, - 9.82176780e-03f, 9.86117239e-03f, 9.81500060e-03f, 9.68401019e-03f, - 9.46973376e-03f, 9.17440866e-03f, 8.80096882e-03f, 8.35299798e-03f, - 7.83470675e-03f, 7.25092919e-03f, 6.60700863e-03f, 5.90880044e-03f, - 5.16260232e-03f, 4.37511499e-03f, 3.55333719e-03f, 2.70453571e-03f, - 1.83621654e-03f, 9.55971892e-04f, 7.14941081e-05f, -8.09546859e-04f, - -1.67949997e-03f, -2.53091770e-03f, -3.35648519e-03f, -4.14916820e-03f, - -4.90226718e-03f, -5.60941116e-03f, -6.26471868e-03f, -6.86271228e-03f, - -7.39850829e-03f, -7.86773441e-03f, -8.26664253e-03f, -8.59209655e-03f, - -8.84162491e-03f, -9.01340035e-03f, -9.10631046e-03f, -9.11992414e-03f, - -9.05447739e-03f, -8.91090648e-03f, -8.69081572e-03f, -8.39650138e-03f, - -8.03081656e-03f, -7.59728914e-03f, -7.09998516e-03f, -6.54351866e-03f, - -5.93297162e-03f, -5.27391890e-03f, -4.57225558e-03f, -3.83429395e-03f, - -3.06659425e-03f, -2.27593987e-03f, -1.46929411e-03f, -6.53717718e-04f, - 1.63676664e-04f, 9.75799545e-04f, 1.77563501e-03f, 2.55633177e-03f, - 3.31116336e-03f, 4.03378434e-03f, 4.71802893e-03f, 5.35816666e-03f, - 5.94887641e-03f, 6.48526029e-03f, 6.96292147e-03f, 7.37801172e-03f, - 7.72723246e-03f, 8.00785932e-03f, 8.21780315e-03f, 8.35557459e-03f, - 8.42032149e-03f, 8.41183281e-03f, 8.33054181e-03f, 8.17747564e-03f, - 7.95433786e-03f, 7.66336298e-03f, 7.30743051e-03f, 6.88990453e-03f, - 6.41471602e-03f, 5.88625192e-03f, 5.30935851e-03f, 4.68925554e-03f, - 4.03151761e-03f, 3.34203719e-03f, 2.62693694e-03f, 1.89253809e-03f, - 1.14528552e-03f, 3.91735329e-04f, -3.61576948e-04f, -1.10810138e-03f, - -1.84142652e-03f, -2.55525216e-03f, -3.24348047e-03f, -3.90028979e-03f, - -4.52012845e-03f, -5.09781875e-03f, -5.62855343e-03f, -6.10796080e-03f, - -6.53213670e-03f, -6.89768993e-03f, -7.20172730e-03f, -7.44190362e-03f, - -7.61648070e-03f, -7.72424208e-03f, -7.76459042e-03f, -7.73750201e-03f, - -7.64355603e-03f, -7.48387832e-03f, -7.26015909e-03f, -6.97470458e-03f, - -6.63023185e-03f, -6.23005432e-03f, -5.77789750e-03f, -5.27794195e-03f, - -4.73473676e-03f, -4.15323407e-03f, -3.53862105e-03f, -2.89641236e-03f, - -2.23229690e-03f, -1.55213945e-03f, -8.61924039e-04f, -1.67646019e-04f, - 5.24634726e-04f, 1.20893687e-03f, 1.87936876e-03f, 2.53020242e-03f, - 3.15589017e-03f, 3.75112921e-03f, 4.31093227e-03f, 4.83063434e-03f, - 5.30589437e-03f, 5.73285894e-03f, 6.10805341e-03f, 6.42847724e-03f, - 6.69165721e-03f, 6.89558077e-03f, 7.03876861e-03f, 7.12031596e-03f, - 7.13979497e-03f, 7.09737416e-03f, 6.99372292e-03f, 6.83002861e-03f, - 6.60805053e-03f, 6.32997518e-03f, 5.99849919e-03f, 5.61676889e-03f, - 5.18833988e-03f, 4.71716487e-03f, 4.20753440e-03f, 3.66405499e-03f, - 3.09162548e-03f, 2.49532169e-03f, 1.88042982e-03f, 1.25240798e-03f, - 6.16721933e-04f, -2.10678481e-05f, -6.55427849e-04f, -1.28088666e-03f, - -1.89206206e-03f, -2.48374616e-03f, -3.05092627e-03f, -3.58876931e-03f, - -4.09281312e-03f, -4.55886002e-03f, -4.98304465e-03f, -5.36192844e-03f, - -5.69246645e-03f, -5.97202847e-03f, -6.19845814e-03f, -6.37007698e-03f, - -6.48568424e-03f, -6.54455208e-03f, -6.54646884e-03f, -6.49172972e-03f, - -6.38107878e-03f, -6.21576754e-03f, -5.99751984e-03f, -5.72848662e-03f, - -5.41128232e-03f, -5.04886935e-03f, -4.64464895e-03f, -4.20232731e-03f, - -3.72592908e-03f, -3.21976323e-03f, -2.68837079e-03f, -2.13647137e-03f, - -1.56895819e-03f, -9.90826814e-04f, -4.07137101e-04f, 1.77034128e-04f, - 7.56614045e-04f, 1.32662280e-03f, 1.88216158e-03f, 2.41851689e-03f, - 2.93111653e-03f, 3.41567923e-03f, 3.86812645e-03f, 4.28473921e-03f, - 4.66208309e-03f, 4.99709627e-03f, 5.28710944e-03f, 5.52983700e-03f, - 5.72344791e-03f, 5.86650915e-03f, 5.95806206e-03f, 5.99756713e-03f, - 5.98498044e-03f, 5.92067495e-03f, 5.80547493e-03f, 5.64067393e-03f, - 5.42792578e-03f, 5.16935371e-03f, 4.86743833e-03f, 4.52499899e-03f, - 4.14524387e-03f, 3.73165445e-03f, 3.28798006e-03f, 2.81822731e-03f, - 2.32660442e-03f, 1.81747333e-03f, 1.29535738e-03f, 7.64811514e-04f, - 2.30499750e-04f, -3.02941233e-04f, -8.30904036e-04f, -1.34884935e-03f, - -1.85232914e-03f, -2.33709184e-03f, -2.79902951e-03f, -3.23425712e-03f, - -3.63917195e-03f, -4.01042034e-03f, -4.34498121e-03f, -4.64014771e-03f, - -4.89357867e-03f, -5.10333096e-03f, -5.26778390e-03f, -5.38579823e-03f, - -5.45658844e-03f, -5.47979294e-03f, -5.45548594e-03f, -5.38411670e-03f, - -5.26658130e-03f, -5.10413250e-03f, -4.89843368e-03f, -4.65147045e-03f, - -4.36565944e-03f, -4.04362854e-03f, -3.68839513e-03f, -3.30322131e-03f, - -2.89157726e-03f, -2.45718506e-03f, -2.00393056e-03f, -1.53582784e-03f, - -1.05702424e-03f, -5.71697826e-04f, -8.41072148e-05f, 4.01536814e-04f, - 8.81019836e-04f, 1.35024965e-03f, 1.80521520e-03f, 2.24205417e-03f, - 2.65710363e-03f, 3.04687963e-03f, 3.40815186e-03f, 3.73795216e-03f, - 4.03361599e-03f, 4.29276556e-03f, 4.51335779e-03f, 4.69370830e-03f, - 4.83246574e-03f, 4.92869877e-03f, 4.98175710e-03f, 4.99146876e-03f, - 4.95797374e-03f, 4.88180168e-03f, 4.76385107e-03f, 4.60539108e-03f, - 4.40799947e-03f, 4.17361084e-03f, 3.90446439e-03f, 3.60306851e-03f, - 3.27223243e-03f, 2.91495118e-03f, 2.53450126e-03f, 2.13428258e-03f, - 1.71785917e-03f, 1.28894440e-03f, 8.51307681e-04f, 4.08783855e-04f, - -3.47765574e-05f, -4.75529382e-04f, -9.09669659e-04f, -1.33349225e-03f, - -1.74339270e-03f, -2.13590830e-03f, -2.50772263e-03f, -2.85576594e-03f, - -3.17714507e-03f, -3.46924367e-03f, -3.72969621e-03f, -3.95645033e-03f, - -4.14770845e-03f, -4.30203167e-03f, -4.41831180e-03f, -4.49574815e-03f, - -4.53388522e-03f, -4.53264318e-03f, -4.49224360e-03f, -4.41325534e-03f, - -4.29659437e-03f, -4.14348253e-03f, -3.95545723e-03f, -3.73433539e-03f, - -3.48222611e-03f, -3.20148621e-03f, -2.89470832e-03f, -2.56468722e-03f, - -2.21442685e-03f, -1.84705410e-03f, -1.46585762e-03f, -1.07420242e-03f, - -6.75541964e-04f, -2.73362726e-04f, 1.28857758e-04f, 5.27625747e-04f, - 9.19505126e-04f, 1.30117518e-03f, 1.66938725e-03f, 2.02102017e-03f, - 2.35316570e-03f, 2.66303554e-03f, 2.94810484e-03f, 3.20604525e-03f, - 3.43477820e-03f, 3.63252281e-03f, 3.79773367e-03f, 3.92918244e-03f, - 4.02592670e-03f, 4.08733570e-03f, 4.11309343e-03f, 4.10318081e-03f, - 4.05789653e-03f, 3.97783502e-03f, 3.86391767e-03f, 3.71728516e-03f, - 3.53944503e-03f, 3.33208164e-03f, 3.09717361e-03f, 2.83693803e-03f, - 2.55373764e-03f, 2.25016474e-03f, 1.92898540e-03f, 1.59304145e-03f, - 1.24535047e-03f, 8.88983788e-04f, 5.27054162e-04f, 1.62739254e-04f, - -2.00809162e-04f, -5.60458110e-04f, -9.13122723e-04f, -1.25578987e-03f, - -1.58556683e-03f, -1.89968711e-03f, -2.19553534e-03f, -2.47065402e-03f, - -2.72279518e-03f, -2.94992092e-03f, -3.15023183e-03f, -3.32214990e-03f, - -3.46435451e-03f, -3.57581566e-03f, -3.65574132e-03f, -3.70363730e-03f, - -3.71929064e-03f, -3.70275212e-03f, -3.65435442e-03f, -3.57474125e-03f, - -3.46473848e-03f, -3.32552683e-03f, -3.15846353e-03f, -2.96515654e-03f, - -2.74746347e-03f, -2.50737272e-03f, -2.24712016e-03f, -1.96906951e-03f, - -1.67571539e-03f, -1.36971428e-03f, -1.05376024e-03f, -7.30644072e-04f, - -4.03206799e-04f, -7.42995897e-05f, 2.53245135e-04f, 5.76588008e-04f, - 8.92968397e-04f, 1.19970438e-03f, 1.49420457e-03f, 1.77402045e-03f, - 2.03680128e-03f, 2.28039302e-03f, 2.50282909e-03f, 2.70229005e-03f, - 2.87722528e-03f, 3.02626345e-03f, 3.14829228e-03f, 3.24241372e-03f, - 3.30798804e-03f, 3.34463320e-03f, 3.35222123e-03f, 3.33084720e-03f, - 3.28088558e-03f, 3.20297004e-03f, 3.09790204e-03f, 2.96678891e-03f, - 2.81092150e-03f, 2.63179675e-03f, 2.43109407e-03f, 2.21069486e-03f, - 1.97261876e-03f, 1.71903709e-03f, 1.45221718e-03f, 1.17455819e-03f, - 8.88520565e-04f, 5.96615677e-04f, 3.01413631e-04f, 5.46569794e-06f, - -2.88648335e-04f, -5.78440455e-04f, -8.61415446e-04f, -1.13516540e-03f, - -1.39741163e-03f, -1.64595773e-03f, -1.87874945e-03f, -2.09387292e-03f, - -2.28959324e-03f, -2.46433942e-03f, -2.61675008e-03f, -2.74564089e-03f, - -2.85005436e-03f, -2.92925341e-03f, -2.98270791e-03f, -3.01012152e-03f, - -3.01144172e-03f, -2.98680121e-03f, -2.93658715e-03f, -2.86141187e-03f, - -2.76205629e-03f, -2.63955281e-03f, -2.49510204e-03f, -2.33009109e-03f, - -2.14606014e-03f, -1.94473859e-03f, -1.72796009e-03f, -1.49770401e-03f, - -1.25602813e-03f, -1.00509543e-03f, -7.47131162e-04f, -4.84384241e-04f, - -2.19190438e-04f, 4.61883980e-05f, 3.09442481e-04f, 5.68335816e-04f, - 8.20650412e-04f, 1.06426195e-03f, 1.29712378e-03f, 1.51732041e-03f, - 1.72299209e-03f, 1.91251660e-03f, 2.08433504e-03f, 2.23708584e-03f, - 2.36958407e-03f, 2.48081558e-03f, 2.56998499e-03f, 2.63642962e-03f, - 2.67976001e-03f, 2.69973545e-03f, 2.69635545e-03f, 2.66978646e-03f, - 2.62041612e-03f, 2.54882653e-03f, 2.45577849e-03f, 2.34219502e-03f, - 2.20921910e-03f, 2.05811735e-03f, 1.89029043e-03f, 1.70732601e-03f, - 1.51087190e-03f, 1.30273238e-03f, 1.08474708e-03f, 8.58888422e-04f, - 6.27141283e-04f, 3.91541843e-04f, 1.54125345e-04f, -8.30221557e-05f, - -3.17874320e-04f, -5.48434822e-04f, -7.72741386e-04f, -9.88897922e-04f, - -1.19510300e-03f, -1.38966446e-03f, -1.57096128e-03f, -1.73754052e-03f, - -1.88804822e-03f, -2.02133530e-03f, -2.13633027e-03f, -2.23217932e-03f, - -2.30820740e-03f, -2.36387261e-03f, -2.39883936e-03f, -2.41294460e-03f, - -2.40621942e-03f, -2.37884693e-03f, -2.33121609e-03f, -2.26385904e-03f, - -2.17751835e-03f, -2.07302810e-03f, -1.95142870e-03f, -1.81390123e-03f, - -1.66170287e-03f, -1.49626799e-03f, -1.31909211e-03f, -1.13178731e-03f, - -9.36049438e-04f, -7.33582746e-04f, -5.26210187e-04f, -3.15727877e-04f, - -1.03964727e-04f, 1.07230526e-04f, 3.16062176e-04f, 5.20749304e-04f, - 7.19556957e-04f, 9.10822165e-04f, 1.09293790e-03f, 1.26442573e-03f, - 1.42387037e-03f, 1.56997858e-03f, 1.70160712e-03f, 1.81770393e-03f, - 1.91739676e-03f, 1.99993376e-03f, 2.06472082e-03f, 2.11132364e-03f, - 2.13948959e-03f, 2.14907506e-03f, 2.14015415e-03f, 2.11291404e-03f, - 2.06772062e-03f, 2.00509948e-03f, 1.92568205e-03f, 1.83030593e-03f, - 1.71988793e-03f, 1.59546278e-03f, 1.45820958e-03f, 1.30941455e-03f, - 1.15041754e-03f, 9.82655662e-04f, 8.07633897e-04f, 6.26913392e-04f, - 4.42086406e-04f, 2.54752525e-04f, 6.65589815e-05f, -1.20878459e-04f, - -3.05964268e-04f, -4.87118900e-04f, -6.62815511e-04f, -8.31597430e-04f, - -9.92029288e-04f, -1.14283171e-03f, -1.28276198e-03f, -1.41069924e-03f, - -1.52563857e-03f, -1.62665705e-03f, -1.71301571e-03f, -1.78407215e-03f, - -1.83932486e-03f, -1.87840593e-03f, -1.90110668e-03f, -1.90733981e-03f, - -1.89718173e-03f, -1.87081315e-03f, -1.82861635e-03f, -1.77102421e-03f, - -1.69867585e-03f, -1.61227322e-03f, -1.51267564e-03f, -1.40081900e-03f, - -1.27776296e-03f, -1.14464853e-03f, -1.00266848e-03f, -8.53125745e-04f, - -6.97343266e-04f, -5.36715682e-04f, -3.72641111e-04f, -2.06566113e-04f, - -3.99112362e-05f, 1.25872577e-04f, 2.89379042e-04f, 4.49221338e-04f, - 6.04060361e-04f, 7.52597124e-04f, 8.93603965e-04f, 1.02593625e-03f, - 1.14851696e-03f, 1.26037089e-03f, 1.36060965e-03f, 1.44847187e-03f, - 1.52327677e-03f, 1.58450468e-03f, 1.63170530e-03f, 1.66459341e-03f, - 1.68299465e-03f, 1.68684492e-03f, 1.67622052e-03f, 1.65133740e-03f, - 1.61248307e-03f, 1.56013086e-03f, 1.49479356e-03f, 1.41715103e-03f, - 1.32795375e-03f, 1.22803948e-03f, 1.11836065e-03f, 9.99922127e-04f, - 8.73805232e-04f, 7.41131956e-04f, 6.03105296e-04f, 4.60940344e-04f, - 3.15890241e-04f, 1.69211261e-04f, 2.21743638e-05f, -1.23952607e-04f, - -2.67936295e-04f, -4.08555731e-04f, -5.44640381e-04f, -6.75036718e-04f, - -7.98694471e-04f, -9.14605111e-04f, -1.02181314e-03f, -1.11948762e-03f, - -1.20686651e-03f, -1.28326662e-03f, -1.34811836e-03f, -1.40096877e-03f, - -1.44142571e-03f, -1.46927100e-03f, -1.48434461e-03f, -1.48661782e-03f, - -1.47616538e-03f, -1.45317443e-03f, -1.41795006e-03f, -1.37085590e-03f, - -1.31241749e-03f, -1.24319769e-03f, -1.16388507e-03f, -1.07522322e-03f, - -9.78051491e-04f, -8.73267846e-04f, -7.61808745e-04f, -6.44689116e-04f, - -5.22962242e-04f, -3.97698841e-04f, -2.69974946e-04f, -1.40936535e-04f, - -1.16687372e-05f, 1.16707935e-04f, 2.43105542e-04f, 3.66459900e-04f, - 4.85746921e-04f, 5.99973582e-04f, 7.08198423e-04f, 8.09540404e-04f, - 9.03214311e-04f, 9.88445526e-04f, 1.06458286e-03f, 1.13105319e-03f, - 1.18736348e-03f, 1.23309711e-03f, 1.26795195e-03f, 1.29173611e-03f, - 1.30429959e-03f, 1.30564635e-03f, 1.29584484e-03f, 1.27505558e-03f, - 1.24356178e-03f, 1.20169119e-03f, 1.14990793e-03f, 1.08871802e-03f, - 1.01870853e-03f, 9.40552377e-04f, 8.55007232e-04f, 7.62816811e-04f, - 6.64843157e-04f, 5.61968820e-04f, 4.55113887e-04f, 3.45206551e-04f, - 2.33202943e-04f, 1.20101771e-04f, 6.86134614e-06f, -1.05554304e-04f, - -2.16195795e-04f, -3.24112900e-04f, -4.28434961e-04f, -5.28276628e-04f, - -6.22838378e-04f, -7.11351653e-04f, -7.93096664e-04f, -8.67456763e-04f, - -9.33831175e-04f, -9.91726329e-04f, -1.04072669e-03f, -1.08046986e-03f, - -1.11070332e-03f, -1.13122472e-03f, -1.14196584e-03f, -1.14289978e-03f, - -1.13408364e-03f, -1.11569239e-03f, -1.08794173e-03f, -1.05115450e-03f, - -1.00570315e-03f, -9.52066366e-04f, -8.90742618e-04f, -8.22333877e-04f, - -7.47460323e-04f, -6.66834457e-04f, -5.81183573e-04f, -4.91248607e-04f, - -3.97865398e-04f, -3.01852004e-04f, -2.04027600e-04f, -1.05245839e-04f, - -6.36777404e-06f, 9.17617553e-05f, 1.88340766e-04f, 2.82546451e-04f, - 3.73570554e-04f, 4.60711065e-04f, 5.43222306e-04f, 6.20446879e-04f, - 6.91790312e-04f, 7.56668845e-04f, 8.14584433e-04f, 8.65136460e-04f, - 9.07903855e-04f, 9.42617554e-04f, 9.69039487e-04f, 9.87009408e-04f, - 9.96454207e-04f, 9.97361571e-04f, 9.89773454e-04f, 9.73845965e-04f, - 9.49773639e-04f, 9.17830053e-04f, 8.78358737e-04f, 8.31731409e-04f, - 7.78436246e-04f, 7.18952835e-04f, 6.53849580e-04f, 5.83726405e-04f, - 5.09215445e-04f, 4.30986185e-04f, 3.49740961e-04f, 2.66183564e-04f, - 1.81053864e-04f, 9.50814188e-05f, 9.00101989e-06f, -7.64476466e-05f, - -1.60555268e-04f, -2.42615492e-04f, -3.21939784e-04f, -3.97891256e-04f, - -4.69842651e-04f, -5.37218972e-04f, -5.99490711e-04f, -6.56157341e-04f, - -7.06805747e-04f, -7.51040629e-04f, -7.88542406e-04f, -8.19057673e-04f, - -8.42365219e-04f, -8.58354952e-04f, -8.66924438e-04f, -8.68087147e-04f, - -8.61872021e-04f, -8.48416980e-04f, -8.27864530e-04f, -8.00485643e-04f, - -7.66529791e-04f, -7.26368966e-04f, -6.80390151e-04f, -6.29012607e-04f, - -5.72740364e-04f, -5.12074275e-04f, -4.47577967e-04f, -3.79824551e-04f, - -3.09404546e-04f, -2.36955845e-04f, -1.63096797e-04f, -8.84666689e-05f, - -1.36918345e-05f, 6.05605282e-05f, 1.33706352e-04f, 2.05104934e-04f, - 2.74177775e-04f, 3.40364816e-04f, 4.03127428e-04f, 4.61941353e-04f, - 5.16384122e-04f, 5.65987864e-04f, 6.10406960e-04f, 6.49289729e-04f, - 6.82362251e-04f, 7.09386220e-04f, 7.30190172e-04f, 7.44646203e-04f, - 7.52685452e-04f, 7.54304020e-04f, 7.49522579e-04f, 7.38444760e-04f, - 7.21207483e-04f, 6.98036730e-04f, 6.69144529e-04f, 6.34844810e-04f, - 5.95456793e-04f, 5.51362735e-04f, 5.02982434e-04f, 4.50740873e-04f, - 3.95141218e-04f, 3.36652582e-04f, 2.75799155e-04f, 2.13134574e-04f, - 1.49173607e-04f, 8.44743226e-05f, 1.96125552e-05f, -4.48903541e-05f, - -1.08473952e-04f, -1.70630967e-04f, -2.30822756e-04f, -2.88566894e-04f, - -3.43417761e-04f, -3.94905833e-04f, -4.42640702e-04f, -4.86249354e-04f, - -5.25408193e-04f, -5.59793704e-04f, -5.89180289e-04f, -6.13368302e-04f, - -6.32178408e-04f, -6.45490725e-04f, -6.53274170e-04f, -6.55472793e-04f, - -6.52128126e-04f, -6.43307606e-04f, -6.29155845e-04f, -6.09800934e-04f, - -5.85463412e-04f, -5.56409754e-04f, -5.22887459e-04f, -4.85231865e-04f, - -4.43797790e-04f, -3.98970397e-04f, -3.51128444e-04f, -3.00728426e-04f, - -2.48188000e-04f, -1.94019174e-04f, -1.38619235e-04f, -8.25046619e-05f, - -2.61663235e-05f, 2.99583710e-05f, 8.53795123e-05f, 1.39627331e-04f, - 1.92270974e-04f, 2.42876498e-04f, 2.91041116e-04f, 3.36365129e-04f, - 3.78493606e-04f, 4.17129701e-04f, 4.51931766e-04f, 4.82649784e-04f, - 5.09080493e-04f, 5.31030056e-04f, 5.48333695e-04f, 5.60878484e-04f, - 5.68641598e-04f, 5.71552610e-04f, 5.69629306e-04f, 5.62942331e-04f, - 5.51586568e-04f, 5.35680325e-04f, 5.15392595e-04f, 4.90931349e-04f, - 4.62558320e-04f, 4.30500957e-04f, 3.95081151e-04f, 3.56619808e-04f, - 3.15458928e-04f, 2.71954945e-04f, 2.26519129e-04f, 1.79516051e-04f, - 1.31357899e-04f, 8.24572190e-05f, 3.32391965e-05f, -1.58850698e-05f, - -6.45330169e-05f, -1.12264134e-04f, -1.58698628e-04f, -2.03467556e-04f, - -2.46210106e-04f, -2.86570115e-04f, -3.24236629e-04f, -3.58912356e-04f, - -3.90340467e-04f, -4.18264108e-04f, -4.42494467e-04f, -4.62859927e-04f, - -4.79201468e-04f, -4.91408046e-04f, -4.99443631e-04f, -5.03240684e-04f, - -5.02785830e-04f, -4.98157139e-04f, -4.89378704e-04f, -4.76587119e-04f, - -4.59890738e-04f, -4.39477548e-04f, -4.15523795e-04f, -3.88271943e-04f, - -3.57945858e-04f, -3.24835243e-04f, -2.89240231e-04f, -2.51465546e-04f, - -2.11835672e-04f, -1.70699525e-04f, -1.28404932e-04f, -8.52894686e-05f, - -4.17402676e-05f, 1.86488651e-06f, 4.51992598e-05f, 8.79090103e-05f, - 1.29605134e-04f, 1.69965889e-04f, 2.08696862e-04f, 2.45456482e-04f, - 2.79955019e-04f, 3.11933102e-04f, 3.41137711e-04f, 3.67347210e-04f, - 3.90354753e-04f, 4.10008585e-04f, 4.26152950e-04f, 4.38673341e-04f, - 4.47495015e-04f, 4.52569681e-04f, 4.53871341e-04f, 4.51401451e-04f, - 4.45215411e-04f, 4.35379482e-04f, 4.22000265e-04f, 4.05191759e-04f, - 3.85119408e-04f, 3.61974661e-04f, 3.35944547e-04f, 3.07269134e-04f, - 2.76178449e-04f, 2.42964749e-04f, 2.07892199e-04f, 1.71254500e-04f, - 1.33357821e-04f, 9.45229736e-05f, 5.50557762e-05f, 1.52985188e-05f, - -2.44379242e-05f, -6.38226873e-05f, -1.02547166e-04f, -1.40299443e-04f, - -1.76768344e-04f, -2.11670093e-04f, -2.44743896e-04f, -2.75718512e-04f, - -3.04347022e-04f, -3.30418232e-04f, -3.53736841e-04f, -3.74109356e-04f, - -3.91381845e-04f, -4.05451309e-04f, -4.16191977e-04f, -4.23521256e-04f, - -4.27426658e-04f, -4.27869490e-04f, -4.24853386e-04f, -4.18413250e-04f, - -4.08611901e-04f, -3.95543058e-04f, -3.79292301e-04f, -3.60028810e-04f, - -3.37902489e-04f, -3.13061225e-04f, -2.85740223e-04f, -2.56168414e-04f, - -2.24556060e-04f, -1.91176716e-04f, -1.56272512e-04f, -1.20139890e-04f, - -8.30537602e-05f, -4.52873563e-05f, -7.15972468e-06f, 3.10302032e-05f, - 6.90244932e-05f, 1.06496284e-04f, 1.43158198e-04f, 1.78736255e-04f, - 2.12976701e-04f, 2.45591364e-04f, 2.76326944e-04f, 3.04976111e-04f, - 3.31299044e-04f, 3.55114336e-04f, 3.76239459e-04f, 3.94517329e-04f, - 4.09785367e-04f, 4.21936769e-04f, 4.30916344e-04f, 4.36588118e-04f, - 4.38939238e-04f, 4.37964002e-04f, 4.33639999e-04f, 4.26009328e-04f, - 4.15120198e-04f, 4.01062409e-04f, 3.83911295e-04f, 3.63806383e-04f, - 3.40873899e-04f, 3.15295099e-04f, 2.87229071e-04f, 2.56841831e-04f, - 2.24418352e-04f, 1.90132129e-04f, 1.54208573e-04f, 1.16966099e-04f, - 7.85931871e-05f, 3.94169348e-05f, -3.38391999e-07f, -4.03917338e-05f, - -8.04238542e-05f, -1.20226558e-04f, -1.59470944e-04f, -1.97919204e-04f, - -2.35294348e-04f, -2.71326824e-04f, -3.05778818e-04f, -3.38410918e-04f, - -3.69010644e-04f, -3.97338842e-04f, -4.23216844e-04f, -4.46466259e-04f, - -4.66912010e-04f, -4.84405189e-04f, -4.98827952e-04f, -5.10071530e-04f, - -5.18032256e-04f, -5.22640255e-04f, -5.23881412e-04f, -5.21676098e-04f, - -5.16039363e-04f, -5.06992324e-04f, -4.94563784e-04f, -4.78787143e-04f, - -4.59750953e-04f, -4.37555727e-04f, -4.12275557e-04f, -3.84054035e-04f, - -3.53037193e-04f, -3.19340180e-04f, -2.83173899e-04f, -2.44687364e-04f, - -2.04094207e-04f, -1.61583244e-04f, -1.17361228e-04f, -7.16348417e-05f, - -2.46348477e-05f, 2.33922164e-05f, 7.22418000e-05f, 1.21684779e-04f, - 1.71499457e-04f, 2.21438013e-04f, 2.71280140e-04f, 3.20801296e-04f, - 3.69771761e-04f, 4.18013477e-04f, 4.65284057e-04f, 5.11428788e-04f, - 5.56212641e-04f, 5.99478253e-04f, 6.41075472e-04f, 6.80832949e-04f, - 7.18609100e-04f, 7.54279009e-04f, 7.87742278e-04f, 8.18871508e-04f, - 8.47567713e-04f, 8.73805352e-04f, 8.97474148e-04f, 9.18529976e-04f, - 9.36959592e-04f, 9.52736827e-04f, 9.65854080e-04f, 9.76300491e-04f, - 9.84128104e-04f, 9.89353301e-04f, 9.92012052e-04f, 9.92188548e-04f, - 9.89909273e-04f, 9.85280796e-04f, 9.78335267e-04f, 9.69211001e-04f, - 9.58007113e-04f, 9.44804463e-04f, 9.29714666e-04f, 9.12874477e-04f, - 8.94419257e-04f, 8.74438162e-04f, 8.53087607e-04f, 8.30499113e-04f, - 8.06785316e-04f, 7.82049426e-04f, 7.56478510e-04f, 7.30171081e-04f, - 7.03266226e-04f, 6.75883876e-04f, 6.48150302e-04f, 6.20186681e-04f, - 5.92085453e-04f, 5.63985129e-04f, 5.35999572e-04f, 5.08203069e-04f, - 4.80689533e-04f, 4.53557971e-04f, 4.26884566e-04f, 4.00740812e-04f, - 3.75236819e-04f, 3.50353190e-04f, 3.26243777e-04f, 3.02893629e-04f, - 2.80331434e-04f, 2.58652176e-04f, 2.37852072e-04f, 2.17971034e-04f, - 1.99026311e-04f, 1.81011783e-04f, 1.63957631e-04f, 1.47844848e-04f, - 1.32718875e-04f, 1.18546172e-04f, 1.05256179e-04f, 9.29127235e-05f, - 8.14745498e-05f, 7.08726925e-05f, 6.11573299e-05f, 5.22357210e-05f, - 4.41150988e-05f, 3.67197275e-05f, 3.01315396e-05f, 2.41484723e-05f, - 1.88541281e-05f, 1.41686879e-05f, 1.00524473e-05f, 6.50288873e-06f, - 3.38594106e-06f, 7.66607975e-07f, -1.44302379e-06f, -3.26787635e-06f, - -4.78247665e-06f, -5.93452415e-06f, -6.76352831e-06f, -7.34758715e-06f, - -7.79331112e-06f, -7.91311421e-06f, -7.86244933e-06f, -7.86157694e-06f, - -7.64215127e-06f, -7.28290836e-06f, -6.81887853e-06f, -6.49158597e-06f, - -6.02222593e-06f, -5.26738395e-06f, -5.09005650e-06f, -1.73022428e-05f, + 0.00000000e+00f, 2.57738229e-05f, 2.28312635e-05f, 3.29301720e-05f, + 4.62792766e-05f, 6.28392813e-05f, 8.34298526e-05f, 1.09345366e-04f, + 1.40765613e-04f, 1.79085823e-04f, 2.25515927e-04f, 2.80686140e-04f, + 3.46440184e-04f, 4.24213060e-04f, 5.15679758e-04f, 6.22703072e-04f, + 7.47248606e-04f, 8.91648830e-04f, 1.05819097e-03f, 1.24970360e-03f, + 1.46896184e-03f, 1.71909900e-03f, 2.00352112e-03f, 2.32585325e-03f, + 2.69004589e-03f, 3.10009770e-03f, 3.56066709e-03f, 4.07650766e-03f, + 4.65266984e-03f, 5.29443820e-03f, 6.00761218e-03f, 6.79817892e-03f, + 7.67241666e-03f, 8.63705786e-03f, 9.69902462e-03f, 1.08655464e-02f, + 1.21441926e-02f, 1.35431147e-02f, 1.50703831e-02f, 1.67342248e-02f, + 1.85434518e-02f, 2.05073885e-02f, 2.26351995e-02f, 2.49359480e-02f, + 2.74195152e-02f, 3.00957201e-02f, 3.29746797e-02f, 3.60660148e-02f, + 3.93800865e-02f, 4.29269667e-02f, 4.67167355e-02f, 5.07595322e-02f, + 5.50652027e-02f, 5.96435730e-02f, 6.45041914e-02f, 6.96563995e-02f, + 7.51092741e-02f, 8.08713050e-02f, 8.69506086e-02f, 9.33550143e-02f, + 1.00091521e-01f, 1.07166524e-01f, 1.14585856e-01f, 1.22354427e-01f, + 1.30476332e-01f, 1.38954781e-01f, 1.47791858e-01f, 1.56988836e-01f, + 1.66545712e-01f, 1.76461162e-01f, 1.86732857e-01f, 1.97356887e-01f, + 2.08328235e-01f, 2.19640042e-01f, 2.31284315e-01f, 2.43251224e-01f, + 2.55529580e-01f, 2.68106380e-01f, 2.80966931e-01f, 2.94094913e-01f, + 3.07472157e-01f, 3.21078662e-01f, 3.34892936e-01f, 3.48891418e-01f, + 3.63048947e-01f, 3.77338339e-01f, 3.91731016e-01f, 4.06196375e-01f, + 4.20702327e-01f, 4.35214921e-01f, 4.49698829e-01f, 4.64117192e-01f, + 4.78431501e-01f, 4.92602156e-01f, 5.06588058e-01f, 5.20347244e-01f, + 5.33836291e-01f, 5.47011324e-01f, 5.59827304e-01f, 5.72238858e-01f, + 5.84199812e-01f, 5.95663881e-01f, 6.06584530e-01f, 6.16915479e-01f, + 6.26610227e-01f, 6.35623009e-01f, 6.43908415e-01f, 6.51422044e-01f, + 6.58120303e-01f, 6.63960857e-01f, 6.68902787e-01f, 6.72906783e-01f, + 6.75935360e-01f, 6.77953091e-01f, 6.78926764e-01f, 6.78825652e-01f, + 6.77621638e-01f, 6.75289589e-01f, 6.71807256e-01f, 6.67155714e-01f, + 6.61319462e-01f, 6.54286514e-01f, 6.46048652e-01f, 6.36601561e-01f, + 6.25944910e-01f, 6.14082478e-01f, 6.01022343e-01f, 5.86776774e-01f, + 5.71362584e-01f, 5.54800895e-01f, 5.37117377e-01f, 5.18342181e-01f, + 4.98509921e-01f, 4.77659701e-01f, 4.55834999e-01f, 4.33083714e-01f, + 4.09457911e-01f, 3.85013844e-01f, 3.59811788e-01f, 3.33915906e-01f, + 3.07393970e-01f, 2.80317366e-01f, 2.52760583e-01f, 2.24801315e-01f, + 1.96519969e-01f, 1.67999385e-01f, 1.39324761e-01f, 1.10583043e-01f, + 8.18628436e-02f, 5.32540056e-02f, 2.48471803e-02f, -3.26636649e-03f, + -3.09952967e-02f, -5.82485562e-02f, -8.49355930e-02f, -1.10967034e-01f, + -1.36254903e-01f, -1.60712966e-01f, -1.84257387e-01f, -2.06806776e-01f, + -2.28282907e-01f, -2.48610910e-01f, -2.67719669e-01f, -2.85542260e-01f, + -3.02016257e-01f, -3.17084071e-01f, -3.30693194e-01f, -3.42796661e-01f, + -3.53353212e-01f, -3.62327576e-01f, -3.69690653e-01f, -3.75419813e-01f, + -3.79499075e-01f, -3.81919103e-01f, -3.82677491e-01f, -3.81778733e-01f, + -3.79234384e-01f, -3.75063017e-01f, -3.69290152e-01f, -3.61948304e-01f, + -3.53076910e-01f, -3.42722096e-01f, -3.30936620e-01f, -3.17779633e-01f, + -3.03316514e-01f, -2.87618587e-01f, -2.70762756e-01f, -2.52831320e-01f, + -2.33911539e-01f, -2.14095259e-01f, -1.93478520e-01f, -1.72161169e-01f, + -1.50246285e-01f, -1.27839839e-01f, -1.05050082e-01f, -8.19871599e-02f, + -5.87624360e-02f, -3.54880619e-02f, -1.22764049e-02f, 1.07605263e-02f, + 3.35116028e-02f, 5.58671830e-02f, 7.77196474e-02f, 9.89638589e-02f, + 1.19497902e-01f, 1.39223354e-01f, 1.58046039e-01f, 1.75876344e-01f, + 1.92629792e-01f, 2.08227445e-01f, 2.22596376e-01f, 2.35669935e-01f, + 2.47388321e-01f, 2.57698757e-01f, 2.66555789e-01f, 2.73921619e-01f, + 2.79766215e-01f, 2.84067669e-01f, 2.86812042e-01f, 2.87993788e-01f, + 2.87615536e-01f, 2.85688209e-01f, 2.82230951e-01f, 2.77271050e-01f, + 2.70843807e-01f, 2.62992310e-01f, 2.53767274e-01f, 2.43226727e-01f, + 2.31435732e-01f, 2.18465973e-01f, 2.04395453e-01f, 1.89308050e-01f, + 1.73292986e-01f, 1.56444403e-01f, 1.38860793e-01f, 1.20644581e-01f, + 1.01901339e-01f, 8.27393357e-02f, 6.32689072e-02f, 4.36018454e-02f, + 2.38507071e-02f, 4.12822827e-03f, -1.54533490e-02f, -3.47829031e-02f, + -5.37510504e-02f, -7.22508561e-02f, -9.01782817e-02f, -1.07433017e-01f, + -1.23918854e-01f, -1.39544366e-01f, -1.54223358e-01f, -1.67875437e-01f, + -1.80426472e-01f, -1.91808926e-01f, -2.01962423e-01f, -2.10833914e-01f, + -2.18378136e-01f, -2.24557777e-01f, -2.29343716e-01f, -2.32715250e-01f, + -2.34660116e-01f, -2.35174592e-01f, -2.34263552e-01f, -2.31940408e-01f, + -2.28226909e-01f, -2.23153240e-01f, -2.16757610e-01f, -2.09086092e-01f, + -2.00192330e-01f, -1.90137237e-01f, -1.78988601e-01f, -1.66820607e-01f, + -1.53713424e-01f, -1.39752706e-01f, -1.25029052e-01f, -1.09637434e-01f, + -9.36766468e-02f, -7.72485626e-02f, -6.04577178e-02f, -4.34104123e-02f, + -2.62142727e-02f, -8.97736641e-03f, 8.19224333e-03f, 2.51873938e-02f, + 4.19023475e-02f, 5.82335223e-02f, 7.40800352e-02f, 8.93445817e-02f, + 1.03933643e-01f, 1.17758514e-01f, 1.30735488e-01f, 1.42786597e-01f, + 1.53840027e-01f, 1.63830572e-01f, 1.72700012e-01f, 1.80397490e-01f, + 1.86879846e-01f, 1.92111822e-01f, 1.96066362e-01f, 1.98724630e-01f, + 2.00076290e-01f, 2.00119400e-01f, 1.98860492e-01f, 1.96314478e-01f, + 1.92504587e-01f, 1.87462157e-01f, 1.81226343e-01f, 1.73844017e-01f, + 1.65369327e-01f, 1.55863248e-01f, 1.45393459e-01f, 1.34033511e-01f, + 1.21862692e-01f, 1.08965144e-01f, 9.54296309e-02f, 8.13487144e-02f, + 6.68182119e-02f, 5.19365830e-02f, 3.68042391e-02f, 2.15228293e-02f, + 6.19473181e-03f, -9.07786386e-03f, -2.41934219e-02f, -3.90518897e-02f, + -5.35552556e-02f, -6.76082701e-02f, -8.11190181e-02f, -9.39995712e-02f, + -1.06166560e-01f, -1.17541673e-01f, -1.28052194e-01f, -1.37631580e-01f, + -1.46219738e-01f, -1.53763473e-01f, -1.60216884e-01f, -1.65541564e-01f, + -1.69706914e-01f, -1.72690307e-01f, -1.74477196e-01f, -1.75061256e-01f, + -1.74444314e-01f, -1.72636437e-01f, -1.69655694e-01f, -1.65528167e-01f, + -1.60287634e-01f, -1.53975364e-01f, -1.46639831e-01f, -1.38336334e-01f, + -1.29126633e-01f, -1.19078480e-01f, -1.08265109e-01f, -9.67648277e-02f, + -8.46603321e-02f, -7.20381983e-02f, -5.89882902e-02f, -4.56030440e-02f, + -3.19769405e-02f, -1.82057654e-02f, -4.38589399e-03f, 9.38618442e-03f, + 2.30147873e-02f, 3.64055026e-02f, 4.94659847e-02f, 6.21064640e-02f, + 7.42404823e-02f, 8.57854026e-02f, 9.66630881e-02f, 1.06800214e-01f, + 1.16129062e-01f, 1.24587714e-01f, 1.32120704e-01f, 1.38679150e-01f, + 1.44221352e-01f, 1.48712781e-01f, 1.52126551e-01f, 1.54443364e-01f, + 1.55651872e-01f, 1.55748483e-01f, 1.54737594e-01f, 1.52631324e-01f, + 1.49449613e-01f, 1.45219867e-01f, 1.39976951e-01f, 1.33762695e-01f, + 1.26625732e-01f, 1.18621127e-01f, 1.09809835e-01f, 1.00258461e-01f, + 9.00385966e-02f, 7.92263112e-02f, 6.79017514e-02f, 5.61483242e-02f, + 4.40522753e-02f, 3.17019988e-02f, 1.91873411e-02f, 6.59909466e-03f, + -5.97180657e-03f, -1.84349074e-02f, -3.07007132e-02f, -4.26816417e-02f, + -5.42923896e-02f, -6.54506079e-02f, -7.60775609e-02f, -8.60985825e-02f, + -9.54437408e-02f, -1.04048142e-01f, -1.11852553e-01f, -1.18803711e-01f, + -1.24854813e-01f, -1.29965653e-01f, -1.34103041e-01f, -1.37241039e-01f, + -1.39360993e-01f, -1.40451792e-01f, -1.40509793e-01f, -1.39538986e-01f, + -1.37550804e-01f, -1.34564089e-01f, -1.30604905e-01f, -1.25706354e-01f, + -1.19908276e-01f, -1.13257015e-01f, -1.05804921e-01f, -9.76101298e-02f, + -8.87359691e-02f, -7.92505483e-02f, -6.92262319e-02f, -5.87391125e-02f, + -4.78684160e-02f, -3.66959289e-02f, -2.53053541e-02f, -1.37817249e-02f, + -2.21079428e-03f, 9.32167747e-03f, 2.07305160e-02f, 3.19316883e-02f, + 4.28430566e-02f, 5.33848786e-02f, 6.34804441e-02f, 7.30565856e-02f, + 8.20442897e-02f, 9.03790776e-02f, 9.80016231e-02f, 1.04858052e-01f, + 1.10900371e-01f, 1.16086840e-01f, 1.20382198e-01f, 1.23757969e-01f, + 1.26192651e-01f, 1.27671770e-01f, 1.28188137e-01f, 1.27741675e-01f, + 1.26339596e-01f, 1.23996183e-01f, 1.20732705e-01f, 1.16577307e-01f, + 1.11564669e-01f, 1.05735813e-01f, 9.91377187e-02f, 9.18230275e-02f, + 8.38495092e-02f, 7.52797839e-02f, 6.61806838e-02f, 5.66228090e-02f, + 4.66799931e-02f, 3.64286945e-02f, 2.59474847e-02f, 1.53163689e-02f, + 4.61622490e-03f, -6.07182197e-03f, -1.66669799e-02f, -2.70894704e-02f, + -3.72610297e-02f, -4.71056290e-02f, -5.65498931e-02f, -6.55237625e-02f, + -7.39609699e-02f, -8.17995235e-02f, -8.89821548e-02f, -9.54568508e-02f, + -1.01177041e-01f, -1.06102108e-01f, -1.10197566e-01f, -1.13435384e-01f, + -1.15794171e-01f, -1.17259227e-01f, -1.17822757e-01f, -1.17483887e-01f, + -1.16248548e-01f, -1.14129572e-01f, -1.11146430e-01f, -1.07325180e-01f, + -1.02698169e-01f, -9.73037750e-02f, -9.11861728e-02f, -8.43949139e-02f, + -7.69845152e-02f, -6.90140891e-02f, -6.05468325e-02f, -5.16495384e-02f, + -4.23921120e-02f, -3.28470026e-02f, -2.30885659e-02f, -1.31926321e-02f, + -3.23579390e-03f, 6.70506976e-03f, 1.65535306e-02f, 2.62340587e-02f, + 3.56726723e-02f, 4.47975155e-02f, 5.35393359e-02f, 6.18320891e-02f, + 6.96133711e-02f, 7.68249575e-02f, 8.34132047e-02f, 8.93294053e-02f, + 9.45302615e-02f, 9.89780776e-02f, 1.02641187e-01f, 1.05494003e-01f, + 1.07517350e-01f, 1.08698506e-01f, 1.09031319e-01f, 1.08516239e-01f, + 1.07160277e-01f, 1.04976947e-01f, 1.01986161e-01f, 9.82139821e-02f, + 9.36925268e-02f, 8.84595879e-02f, 8.25583806e-02f, 7.60371894e-02f, + 6.89489402e-02f, 6.13508561e-02f, 5.33039225e-02f, 4.48724192e-02f, + 3.61234643e-02f, 2.71263730e-02f, 1.79522593e-02f, 8.67332814e-03f, + -6.37560549e-04f, -9.90757623e-03f, -1.90643762e-02f, -2.80367915e-02f, + -3.67552453e-02f, -4.51524120e-02f, -5.31636754e-02f, -6.07276053e-02f, + -6.77865076e-02f, -7.42868063e-02f, -8.01794199e-02f, -8.54202472e-02f, + -8.99703936e-02f, -9.37965226e-02f, -9.68710320e-02f, -9.91723636e-02f, + -1.00685055e-01f, -1.01399924e-01f, -1.01314027e-01f, -1.00430752e-01f, + -9.87597996e-02f, -9.63169323e-02f, -9.31240228e-02f, -8.92087380e-02f, + -8.46043840e-02f, -7.93495604e-02f, -7.34879129e-02f, -6.70676790e-02f, + -6.01414310e-02f, -5.27655243e-02f, -4.49997042e-02f, -3.69066235e-02f, + -2.85513234e-02f, -2.00007483e-02f, -1.13231172e-02f, -2.58753819e-03f, + 6.13671174e-03f, 1.47805601e-02f, 2.32758739e-02f, 3.15558319e-02f, + 3.95555353e-02f, 4.72125037e-02f, 5.44671546e-02f, 6.12632712e-02f, + 6.75484439e-02f, 7.32744268e-02f, 7.83976489e-02f, 8.28793328e-02f, + 8.66860430e-02f, 8.97896293e-02f, 9.21678120e-02f, 9.38039664e-02f, + 9.46875290e-02f, 9.48139007e-02f, 9.41845125e-02f, 9.28068505e-02f, + 9.06942851e-02f, 8.78660470e-02f, 8.43469508e-02f, 8.01672994e-02f, + 7.53625613e-02f, 6.99730444e-02f, 6.40436219e-02f, 5.76234074e-02f, + 5.07651752e-02f, 4.35251559e-02f, 3.59623612e-02f, 2.81382801e-02f, + 2.01163116e-02f, 1.19611978e-02f, 3.73860852e-03f, -4.48544815e-03f, + -1.26451529e-02f, -2.06753686e-02f, -2.85122123e-02f, -3.60935203e-02f, + -4.33593322e-02f, -5.02524350e-02f, -5.67187066e-02f, -6.27076379e-02f, + -6.81726899e-02f, -7.30716527e-02f, -7.73669645e-02f, -8.10260558e-02f, + -8.40214980e-02f, -8.63313064e-02f, -8.79390729e-02f, -8.88340404e-02f, + -8.90112500e-02f, -8.84714905e-02f, -8.72213317e-02f, -8.52730391e-02f, + -8.26444808e-02f, -7.93588812e-02f, -7.54447661e-02f, -7.09355706e-02f, + -6.58695010e-02f, -6.02890903e-02f, -5.42408964e-02f, -4.77751478e-02f, + -4.09452872e-02f, -3.38075119e-02f, -2.64204001e-02f, -1.88443271e-02f, + -1.11410841e-02f, -3.37322640e-03f, 4.39625224e-03f, 1.21045621e-02f, + 1.96895966e-02f, 2.70903872e-02f, 3.42476479e-02f, 4.11042063e-02f, + 4.76055071e-02f, 5.36999666e-02f, 5.93395286e-02f, 6.44798549e-02f, + 6.90808373e-02f, 7.31067984e-02f, 7.65268692e-02f, 7.93151321e-02f, + 8.14508652e-02f, 8.29187318e-02f, 8.37087874e-02f, 8.38166939e-02f, + 8.32435667e-02f, 8.19961230e-02f, 8.00865044e-02f, 7.75321668e-02f, + 7.43557925e-02f, 7.05850463e-02f, 6.62523340e-02f, 6.13945573e-02f, + 5.60527587e-02f, 5.02718197e-02f, 4.41000820e-02f, 3.75889077e-02f, + 3.07923072e-02f, 2.37664500e-02f, 1.65692144e-02f, 9.25974471e-03f, + 1.89789131e-03f, -5.45616102e-03f, -1.27425262e-02f, -1.99019879e-02f, + -2.68765329e-02f, -3.36098353e-02f, -4.00476211e-02f, -4.61382887e-02f, + -5.18330845e-02f, -5.70866854e-02f, -6.18574768e-02f, -6.61078954e-02f, + -6.98047314e-02f, -7.29193713e-02f, -7.54280733e-02f, -7.73120518e-02f, + -7.85577193e-02f, -7.91567321e-02f, -7.91060672e-02f, -7.84079778e-02f, + -7.70700917e-02f, -7.51051477e-02f, -7.25310474e-02f, -6.93706386e-02f, + -6.56514718e-02f, -6.14056206e-02f, -5.66693718e-02f, -5.14829473e-02f, + -4.58901735e-02f, -3.99380331e-02f, -3.36763702e-02f, -2.71574389e-02f, + -2.04354475e-02f, -1.35661532e-02f, -6.60630727e-03f, 3.86646824e-04f, + 7.35520973e-03f, 1.42422280e-02f, 2.09913641e-02f, 2.75475284e-02f, + 3.38574221e-02f, 3.98698307e-02f, 4.55362181e-02f, 5.08109216e-02f, + 5.56516759e-02f, 6.00198762e-02f, 6.38809158e-02f, 6.72044395e-02f, + 6.99645791e-02f, 7.21402152e-02f, 7.37150597e-02f, 7.46778107e-02f, + 7.50222360e-02f, 7.47472565e-02f, 7.38568363e-02f, 7.23600417e-02f, + 7.02708761e-02f, 6.76082238e-02f, 6.43956576e-02f, 6.06611745e-02f, + 5.64370176e-02f, 5.17594495e-02f, 4.66682739e-02f, 4.12066978e-02f, + 3.54208167e-02f, 2.93593188e-02f, 2.30730217e-02f, 1.66144782e-02f, + 1.00375317e-02f, 3.39684147e-03f, -3.25251124e-03f, -9.85551584e-03f, + -1.63577490e-02f, -2.27056595e-02f, -2.88471998e-02f, -3.47320840e-02f, + -4.03123540e-02f, -4.55426353e-02f, -5.03805924e-02f, -5.47872529e-02f, + -5.87272646e-02f, -6.21693020e-02f, -6.50861771e-02f, -6.74551714e-02f, + -6.92581408e-02f, -7.04816306e-02f, -7.11171304e-02f, -7.11609040e-02f, + -7.06142341e-02f, -6.94832169e-02f, -6.77788399e-02f, -6.55167566e-02f, + -6.27172654e-02f, -5.94050389e-02f, -5.56089420e-02f, -5.13618140e-02f, + -4.67001077e-02f, -4.16636667e-02f, -3.62953408e-02f, -3.06405937e-02f, + -2.47472033e-02f, -1.86647681e-02f, -1.24443758e-02f, -6.13808338e-03f, + 2.01411978e-04f, 6.52133427e-03f, 1.27691320e-02f, 1.88930538e-02f, + 2.48424465e-02f, 3.05682613e-02f, 3.60234752e-02f, 4.11633315e-02f, + 4.59459248e-02f, 5.03323304e-02f, 5.42870835e-02f, 5.77784019e-02f, + 6.07783651e-02f, 6.32633288e-02f, 6.52139121e-02f, 6.66152616e-02f, + 6.74571587e-02f, 6.77340253e-02f, 6.74450634e-02f, 6.65941743e-02f, + 6.51898969e-02f, 6.32454230e-02f, 6.07783949e-02f, 5.78107648e-02f, + 5.43686030e-02f, 5.04819132e-02f, 4.61842717e-02f, 4.15126988e-02f, + 3.65071237e-02f, 3.12102820e-02f, 2.56671547e-02f, 1.99247189e-02f, + 1.40314897e-02f, 8.03710981e-03f, 1.99194113e-03f, -4.05328806e-03f, + -1.00480950e-02f, -1.59424027e-02f, -2.16871978e-02f, -2.72348278e-02f, + -3.25393096e-02f, -3.75569641e-02f, -4.22464745e-02f, -4.65694440e-02f, + -5.04906287e-02f, -5.39782262e-02f, -5.70041442e-02f, -5.95442285e-02f, + -6.15784134e-02f, -6.30910059e-02f, -6.40706329e-02f, -6.45104738e-02f, + -6.44081853e-02f, -6.37660587e-02f, -6.25908310e-02f, -6.08937512e-02f, + -5.86903214e-02f, -5.60003749e-02f, -5.28477193e-02f, -4.92599533e-02f, + -4.52683132e-02f, -4.09073183e-02f, -3.62145264e-02f, -3.12301828e-02f, + -2.59968465e-02f, -2.05591356e-02f, -1.49632303e-02f, -9.25651553e-03f, + -3.48724314e-03f, 2.29598571e-03f, 8.04450724e-03f, 1.37101015e-02f, + 1.92453689e-02f, 2.46040942e-02f, 2.97416606e-02f, 3.46154341e-02f, + 3.91850779e-02f, 4.34129264e-02f, 4.72642978e-02f, 5.07076760e-02f, + 5.37151661e-02f, 5.62625282e-02f, 5.83294584e-02f, 5.98998097e-02f, + 6.09615551e-02f, 6.15070547e-02f, 6.15329990e-02f, 6.10404716e-02f, + 6.00349135e-02f, 5.85260610e-02f, 5.65279401e-02f, 5.40585286e-02f, + 5.11398898e-02f, 4.77976785e-02f, 4.40611761e-02f, 3.99628438e-02f, + 3.55380989e-02f, 3.08251587e-02f, 2.58644088e-02f, 2.06983747e-02f, + 1.53710739e-02f, 9.92792727e-03f, 4.41510532e-03f, -1.12066218e-03f, + -6.63261302e-03f, -1.20742595e-02f, -1.73998395e-02f, -2.25646758e-02f, + -2.75255332e-02f, -3.22410401e-02f, -3.66719333e-02f, -4.07815241e-02f, + -4.45358322e-02f, -4.79040656e-02f, -5.08586430e-02f, -5.33756630e-02f, + -5.54348806e-02f, -5.70200545e-02f, -5.81188903e-02f, -5.87233379e-02f, + -5.88294685e-02f, -5.84376077e-02f, -5.75522891e-02f, -5.61822154e-02f, + -5.43401522e-02f, -5.20428669e-02f, -4.93108603e-02f, -4.61683834e-02f, + -4.26429808e-02f, -3.87654608e-02f, -3.45695151e-02f, -3.00914369e-02f, + -2.53698514e-02f, -2.04453289e-02f, -1.53600900e-02f, -1.01575989e-02f, + -4.88223250e-03f, 4.21140683e-04f, 5.70749346e-03f, 1.09320714e-02f, + 1.60507423e-02f, 2.10203384e-02f, 2.57991258e-02f, 3.03469891e-02f, + 3.46259681e-02f, 3.86003807e-02f, 4.22372656e-02f, 4.55065977e-02f, + 4.83814916e-02f, 5.08385122e-02f, 5.28578492e-02f, 5.44233532e-02f, + 5.55228686e-02f, 5.61481799e-02f, 5.62951181e-02f, 5.59635641e-02f, + 5.51575059e-02f, 5.38848971e-02f, 5.21577067e-02f, 4.99916630e-02f, + 4.74062222e-02f, 4.44243762e-02f, 4.10723813e-02f, 3.73796334e-02f, + 3.33783371e-02f, 2.91031994e-02f, 2.45912699e-02f, 1.98814471e-02f, + 1.50142456e-02f, 1.00314492e-02f, 4.97569860e-03f, -1.09829558e-04f, + -5.18180429e-03f, -1.01971104e-02f, -1.51132009e-02f, -1.98884909e-02f, + -2.44826770e-02f, -2.88570632e-02f, -3.29749774e-02f, -3.68019407e-02f, + -4.03060834e-02f, -4.34583201e-02f, -4.62326394e-02f, -4.86063256e-02f, + -5.05600942e-02f, -5.20783017e-02f, -5.31490320e-02f, -5.37642601e-02f, + -5.39197625e-02f, -5.36153455e-02f, -5.28546569e-02f, -5.16452744e-02f, + -4.99985519e-02f, -4.79295687e-02f, -4.54569365e-02f, -4.26027299e-02f, + -3.93921382e-02f, -3.58534069e-02f, -3.20174780e-02f, -2.79177659e-02f, + -2.35898897e-02f, -1.90712489e-02f, -1.44009108e-02f, -9.61905551e-03f, + -4.76675190e-03f, 1.14429175e-04f, 4.98275197e-03f, 9.79667060e-03f, + 1.45152450e-02f, 1.90984000e-02f, 2.35073004e-02f, 2.77047166e-02f, + 3.16552879e-02f, 3.53258717e-02f, 3.86857329e-02f, 4.17069009e-02f, + 4.43643859e-02f, 4.66362841e-02f, 4.85040977e-02f, 4.99528118e-02f, + 5.09710058e-02f, 5.15509899e-02f, 5.16888143e-02f, 5.13843182e-02f, + 5.06411841e-02f, 4.94667221e-02f, 4.78719944e-02f, 4.58716454e-02f, + 4.34836680e-02f, 4.07294565e-02f, 3.76333478e-02f, 3.42226856e-02f, + 3.05273460e-02f, 2.65796084e-02f, 2.24138588e-02f, 1.80662406e-02f, + 1.35743725e-02f, 8.97708082e-03f, 4.31392223e-03f, -3.74997817e-04f, + -5.04949488e-03f, -9.66954442e-03f, -1.41957488e-02f, -1.85895102e-02f, + -2.28135517e-02f, -2.68320150e-02f, -3.06110148e-02f, -3.41186844e-02f, + -3.73255948e-02f, -4.02049849e-02f, -4.27328870e-02f, -4.48884826e-02f, + -4.66541442e-02f, -4.80155982e-02f, -4.89621376e-02f, -4.94865911e-02f, + -4.95854066e-02f, -4.92587543e-02f, -4.85103568e-02f, -4.73476557e-02f, + -4.57815259e-02f, -4.38263403e-02f, -4.14997758e-02f, -3.88226280e-02f, + -3.58186618e-02f, -3.25144195e-02f, -2.89389208e-02f, -2.51234952e-02f, + -2.11014173e-02f, -1.69076786e-02f, -1.25787051e-02f, -8.15193430e-03f, + -3.66561199e-03f, 8.41612730e-04f, 5.33096998e-03f, 9.76396982e-03f, + 1.41026633e-02f, 1.83099747e-02f, 2.23500907e-02f, 2.61886650e-02f, + 2.97931947e-02f, 3.31332165e-02f, 3.61806686e-02f, 3.89099840e-02f, + 4.12984711e-02f, 4.33263397e-02f, 4.49769909e-02f, 4.62370906e-02f, + 4.70967034e-02f, 4.75493101e-02f, 4.75919973e-02f, 4.72252655e-02f, + 4.64532495e-02f, 4.52834651e-02f, 4.37268913e-02f, 4.17977928e-02f, + 3.95136051e-02f, 3.68947746e-02f, 3.39645897e-02f, 3.07489848e-02f, + 2.72762475e-02f, 2.35768654e-02f, 1.96831666e-02f, 1.56291178e-02f, + 1.14499678e-02f, 7.18199244e-03f, 2.86208797e-03f, -1.47237323e-03f, + -5.78408986e-03f, -1.00359644e-02f, -1.41915102e-02f, -1.82151627e-02f, + -2.20725868e-02f, -2.57308876e-02f, -2.91590343e-02f, -3.23279893e-02f, + -3.52110559e-02f, -3.77840233e-02f, -4.00254088e-02f, -4.19166660e-02f, + -4.34422559e-02f, -4.45898688e-02f, -4.53504693e-02f, -4.57183342e-02f, + -4.56912080e-02f, -4.52701881e-02f, -4.44598082e-02f, -4.32679041e-02f, + -4.17056221e-02f, -3.97872721e-02f, -3.75301757e-02f, -3.49545539e-02f, + -3.20833642e-02f, -2.89420199e-02f, -2.55582198e-02f, -2.19616927e-02f, + -1.81839563e-02f, -1.42580179e-02f, -1.02180663e-02f, -6.09921811e-03f, + -1.93723037e-03f, 2.23190874e-03f, 6.37220013e-03f, 1.04479531e-02f, + 1.44241290e-02f, 1.82666389e-02f, 2.19425802e-02f, 2.54206248e-02f, + 2.86711138e-02f, 3.16665395e-02f, 3.43815887e-02f, 3.67934228e-02f, + 3.88819070e-02f, 4.06296963e-02f, 4.20224632e-02f, 4.30489649e-02f, + 4.37011440e-02f, 4.39741987e-02f, 4.38666105e-02f, 4.33801492e-02f, + 4.25198834e-02f, 4.12940568e-02f, 3.97140893e-02f, 3.77944134e-02f, + 3.55523815e-02f, 3.30080847e-02f, 3.01842181e-02f, 2.71057696e-02f, + 2.37999951e-02f, 2.02958932e-02f, 1.66242890e-02f, 1.28172569e-02f, + 8.90804027e-03f, 4.93068765e-03f, 9.19732115e-04f, -3.09003828e-03f, + -7.06392168e-03f, -1.09676549e-02f, -1.47675919e-02f, -1.84310619e-02f, + -2.19267124e-02f, -2.52246331e-02f, -2.82967223e-02f, -3.11168905e-02f, + -3.36612820e-02f, -3.59084899e-02f, -3.78397204e-02f, -3.94389199e-02f, + -4.06930177e-02f, -4.15918929e-02f, -4.21285586e-02f, -4.22991498e-02f, + -4.21030212e-02f, -4.15426637e-02f, -4.06237373e-02f, -3.93549944e-02f, + -3.77482044e-02f, -3.58180208e-02f, -3.35818746e-02f, -3.10598205e-02f, + -2.82743551e-02f, -2.52501753e-02f, -2.20140228e-02f, -1.85944012e-02f, + -1.50213539e-02f, -1.13261978e-02f, -7.54120713e-03f, -3.69942331e-03f, + 1.65761761e-04f, 4.02077827e-03f, 7.83220364e-03f, 1.15671267e-02f, + 1.51932941e-02f, 1.86795257e-02f, 2.19958878e-02f, 2.51139844e-02f, + 2.80072326e-02f, 3.06510161e-02f, 3.30229521e-02f, 3.51030581e-02f, + 3.68739519e-02f, 3.83209200e-02f, 3.94321479e-02f, 4.01987038e-02f, + 4.06147205e-02f, 4.06773379e-02f, 4.03868010e-02f, 3.97463905e-02f, + 3.87624502e-02f, 3.74442613e-02f, 3.58040217e-02f, 3.38566274e-02f, + 3.16197104e-02f, 2.91132913e-02f, 2.63597535e-02f, 2.33834951e-02f, + 2.02109048e-02f, 1.68699197e-02f, 1.33899369e-02f, 9.80149436e-03f, + 6.13596978e-03f, 2.42540672e-03f, -1.29791566e-03f, -5.00159520e-03f, + -8.65350380e-03f, -1.22220366e-02f, -1.56763640e-02f, -1.89867381e-02f, + -2.21247040e-02f, -2.50633577e-02f, -2.77776392e-02f, -3.02443926e-02f, + -3.24427741e-02f, -3.43542347e-02f, -3.59627985e-02f, -3.72551425e-02f, + -3.82207294e-02f, -3.88518854e-02f, -3.91438407e-02f, -3.90947976e-02f, + -3.87059356e-02f, -3.79813919e-02f, -3.69281514e-02f, -3.55561727e-02f, + -3.38780082e-02f, -3.19089538e-02f, -2.96667755e-02f, -2.71715529e-02f, + -2.44455713e-02f, -2.15130067e-02f, -1.83998712e-02f, -1.51336016e-02f, + -1.17429607e-02f, -8.25773631e-03f, -4.70843717e-03f, -1.12611141e-03f, + 2.45802887e-03f, 6.01275162e-03f, 9.50720826e-03f, 1.29111118e-02f, + 1.61950221e-02f, 1.93306375e-02f, 2.22909758e-02f, 2.50506401e-02f, + 2.75860615e-02f, 2.98756376e-02f, 3.18999914e-02f, 3.36420581e-02f, + 3.50872846e-02f, 3.62237568e-02f, 3.70422138e-02f, 3.75362558e-02f, + 3.77022507e-02f, 3.75395252e-02f, 3.70501413e-02f, 3.62391403e-02f, + 3.51142523e-02f, 3.36859539e-02f, 3.19673834e-02f, 2.99741015e-02f, + 2.77241155e-02f, 2.52375586e-02f, 2.25366390e-02f, 1.96453479e-02f, + 1.65892764e-02f, 1.33954509e-02f, 1.00919807e-02f, 6.70792095e-03f, + 3.27291818e-03f, -1.82973294e-04f, -3.62955472e-03f, -7.03682264e-03f, + -1.03751742e-02f, -1.36156254e-02f, -1.67301419e-02f, -1.96918703e-02f, + -2.24752801e-02f, -2.50565117e-02f, -2.74134928e-02f, -2.95261426e-02f, + -3.13765953e-02f, -3.29492690e-02f, -3.42310321e-02f, -3.52113800e-02f, + -3.58823726e-02f, -3.62388683e-02f, -3.62784198e-02f, -3.60013804e-02f, + -3.54108451e-02f, -3.45126668e-02f, -3.33153418e-02f, -3.18299584e-02f, + -3.00701260e-02f, -2.80517892e-02f, -2.57931374e-02f, -2.33143863e-02f, + -2.06376424e-02f, -1.77867508e-02f, -1.47868963e-02f, -1.16646556e-02f, + -8.44750940e-03f, -5.16378838e-03f, -1.84226820e-03f, 1.48794674e-03f, + 4.79778745e-03f, 8.05836223e-03f, 1.12413416e-02f, 1.43190753e-02f, + 1.72649338e-02f, 2.00534760e-02f, 2.26606759e-02f, 2.50641770e-02f, + 2.72434093e-02f, 2.91798405e-02f, 3.08570869e-02f, 3.22610538e-02f, + 3.33800775e-02f, 3.42050023e-02f, 3.47292410e-02f, 3.49489094e-02f, + 3.48627120e-02f, 3.44720896e-02f, 3.37811019e-02f, 3.27964643e-02f, + 3.15273982e-02f, 2.99856201e-02f, 2.81852138e-02f, 2.61424498e-02f, + 2.38757347e-02f, 2.14053475e-02f, 1.87533476e-02f, 1.59433058e-02f, + 1.30001119e-02f, 9.94981546e-03f, 6.81929403e-03f, 3.63610142e-03f, + 4.28194737e-04f, -2.77634627e-03f, -5.94948890e-03f, -9.06356635e-03f, + -1.20914729e-02f, -1.50068986e-02f, -1.77846186e-02f, -2.04006305e-02f, + -2.28323995e-02f, -2.50590554e-02f, -2.70615857e-02f, -2.88229378e-02f, + -3.03282395e-02f, -3.15648981e-02f, -3.25226545e-02f, -3.31937480e-02f, + -3.35729235e-02f, -3.36575141e-02f, -3.34474213e-02f, -3.29451007e-02f, + -3.21556137e-02f, -3.10864769e-02f, -2.97476700e-02f, -2.81514985e-02f, + -2.63124688e-02f, -2.42472323e-02f, -2.19743519e-02f, -1.95141654e-02f, + -1.68886307e-02f, -1.41210735e-02f, -1.12360633e-02f, -8.25907766e-03f, + -5.21640280e-03f, -2.13480364e-03f, 9.58636625e-04f, 4.03679668e-03f, + 7.07279449e-03f, 1.00401133e-02f, 1.29129091e-02f, 1.56662400e-02f, + 1.82762701e-02f, 2.07204296e-02f, 2.29776718e-02f, 2.50286615e-02f, + 2.68558574e-02f, 2.84437511e-02f, 2.97789600e-02f, 3.08503332e-02f, + 3.16490674e-02f, 3.21687669e-02f, 3.24054635e-02f, 3.23577221e-02f, + 3.20265687e-02f, 3.14155519e-02f, 3.05305894e-02f, 2.93800783e-02f, + 2.79746552e-02f, 2.63272236e-02f, 2.44527049e-02f, 2.23680618e-02f, + 2.00919975e-02f, 1.76449192e-02f, 1.50485988e-02f, 1.23261499e-02f, + 9.50171139e-03f, 6.60026650e-03f, 3.64740367e-03f, 6.69130719e-04f, + -2.30836384e-03f, -5.25901398e-03f, -8.15698453e-03f, -1.09769803e-02f, + -1.36944604e-02f, -1.62858144e-02f, -1.87285884e-02f, -2.10016919e-02f, + -2.30855512e-02f, -2.49623180e-02f, -2.66159983e-02f, -2.80325244e-02f, + -2.92000656e-02f, -3.01088544e-02f, -3.07515454e-02f, -3.11230425e-02f, + -3.12207134e-02f, -3.10442642e-02f, -3.05958739e-02f, -2.98800667e-02f, + -2.89037056e-02f, -2.76759017e-02f, -2.62080378e-02f, -2.45134730e-02f, + -2.26075902e-02f, -2.05075661e-02f, -1.82322785e-02f, -1.58020520e-02f, + -1.32385765e-02f, -1.05646147e-02f, -7.80391393e-03f, -4.98087115e-03f, + -2.12039991e-03f, 7.52293928e-04f, 3.61198449e-03f, 6.43358111e-03f, + 9.19241842e-03f, 1.18644039e-02f, 1.44262640e-02f, 1.68557443e-02f, + 1.91318358e-02f, 2.12348654e-02f, 2.31467521e-02f, 2.48511451e-02f, + 2.63335090e-02f, 2.75813428e-02f, 2.85841744e-02f, 2.93337504e-02f, + 2.98240644e-02f, 3.00513466e-02f, 3.00142022e-02f, 2.97135260e-02f, + 2.91525594e-02f, 2.83367887e-02f, 2.72739123e-02f, 2.59738760e-02f, + 2.44485621e-02f, 2.27119106e-02f, 2.07795919e-02f, 1.86690808e-02f, + 1.63992592e-02f, 1.39904260e-02f, 1.14640690e-02f, 8.84264758e-03f, + 6.14939507e-03f, 3.40812526e-03f, 6.43054198e-04f, -2.12146966e-03f, + -4.86117838e-03f, -7.55201827e-03f, -1.01704721e-02f, -1.26936743e-02f, + -1.50996598e-02f, -1.73675327e-02f, -1.94776873e-02f, -2.14119119e-02f, + -2.31535849e-02f, -2.46878274e-02f, -2.60016123e-02f, -2.70838807e-02f, + -2.79256152e-02f, -2.85199737e-02f, -2.88622578e-02f, -2.89500170e-02f, + -2.87830391e-02f, -2.83633782e-02f, -2.76952574e-02f, -2.67851241e-02f, + -2.56414895e-02f, -2.42749591e-02f, -2.26980211e-02f, -2.09250117e-02f, + -1.89719767e-02f, -1.68564622e-02f, -1.45974790e-02f, -1.22151551e-02f, + -9.73077489e-03f, -7.16636971e-03f, -4.54471089e-03f, -1.88895556e-03f, + 7.77421152e-04f, 3.43097465e-03f, 6.04836823e-03f, 8.60665740e-03f, + 1.10834931e-02f, 1.34572307e-02f, 1.57072091e-02f, 1.78139437e-02f, + 1.97591848e-02f, 2.15261896e-02f, 2.30998051e-02f, 2.44666019e-02f, + 2.56150329e-02f, 2.65354493e-02f, 2.72202868e-02f, 2.76640272e-02f, + 2.78632920e-02f, 2.78168878e-02f, 2.75257711e-02f, 2.69930474e-02f, + 2.62239398e-02f, 2.52257420e-02f, 2.40077502e-02f, 2.25811821e-02f, + 2.09590651e-02f, 1.91560968e-02f, 1.71885831e-02f, 1.50741718e-02f, + 1.28318640e-02f, 1.04816157e-02f, 8.04442313e-03f, 5.54190051e-03f, + 2.99625439e-03f, 4.29964517e-04f, -2.13427934e-03f, -4.67394770e-03f, + -7.16668999e-03f, -9.59070046e-03f, -1.19247527e-02f, -1.41485167e-02f, + -1.62426221e-02f, -1.81889386e-02f, -1.99706517e-02f, -2.15724419e-02f, + -2.29805917e-02f, -2.41831321e-02f, -2.51699460e-02f, -2.59328014e-02f, + -2.64654988e-02f, -2.67638473e-02f, -2.68257457e-02f, -2.66511933e-02f, + -2.62422396e-02f, -2.56030378e-02f, -2.47397026e-02f, -2.36603803e-02f, + -2.23750178e-02f, -2.08954572e-02f, -1.92351291e-02f, -1.74091057e-02f, + -1.54338252e-02f, -1.33270371e-02f, -1.11076012e-02f, -8.79532116e-03f, + -6.41081337e-03f, -3.97522070e-03f, -1.51016205e-03f, 9.62627810e-04f, + 3.42126524e-03f, 5.84416900e-03f, 8.21003607e-03f, 1.04981418e-02f, + 1.26884979e-02f, 1.47620168e-02f, 1.67006743e-02f, 1.84877071e-02f, + 2.01076631e-02f, 2.15466723e-02f, 2.27924559e-02f, 2.38344391e-02f, + 2.46639202e-02f, 2.52740508e-02f, 2.56599603e-02f, 2.58187383e-02f, + 2.57495061e-02f, 2.54533889e-02f, 2.49335063e-02f, 2.41949679e-02f, + 2.32447443e-02f, 2.20917330e-02f, 2.07465369e-02f, 1.92214548e-02f, + 1.75303581e-02f, 1.56885248e-02f, 1.37125582e-02f, 1.16201731e-02f, + 9.43010247e-03f, 7.16186261e-03f, 4.83567361e-03f, 2.47215188e-03f, + 9.22473038e-05f, -2.28300123e-03f, -4.63266587e-03f, -6.93607706e-03f, + -9.17298978e-03f, -1.13238544e-02f, -1.33698760e-02f, -1.52932430e-02f, + -1.70772680e-02f, -1.87065128e-02f, -2.01669821e-02f, -2.14461452e-02f, + -2.25331284e-02f, -2.34188021e-02f, -2.40957864e-02f, -2.45585844e-02f, + -2.48036252e-02f, -2.48292044e-02f, -2.46356329e-02f, -2.42250842e-02f, + -2.36017013e-02f, -2.27714652e-02f, -2.17421665e-02f, -2.05233690e-02f, + -1.91262271e-02f, -1.75635217e-02f, -1.58493869e-02f, -1.39993165e-02f, + -1.20299498e-02f, -9.95894696e-03f, -7.80483551e-03f, -5.58678574e-03f, + -3.32457334e-03f, -1.03821621e-03f, 1.25199524e-03f, 3.52587224e-03f, + 5.76334794e-03f, 7.94474634e-03f, 1.00509704e-02f, 1.20635647e-02f, + 1.39649989e-02f, 1.57386972e-02f, 1.73693364e-02f, 1.88428192e-02f, + 2.01465043e-02f, 2.12692517e-02f, 2.22015935e-02f, 2.29357192e-02f, + 2.34655795e-02f, 2.37870006e-02f, 2.38975984e-02f, 2.37969007e-02f, + 2.34862414e-02f, 2.29689158e-02f, 2.22499000e-02f, 2.13360637e-02f, + 2.02358982e-02f, 1.89595527e-02f, 1.75187187e-02f, 1.59264857e-02f, + 1.41973055e-02f, 1.23467171e-02f, 1.03913879e-02f, 8.34881663e-03f, + 6.23727188e-03f, 4.07554898e-03f, 1.88285221e-03f, -3.21348688e-04f, + -2.51757106e-03f, -4.68641464e-03f, -6.80879709e-03f, -8.86604146e-03f, + -1.08401587e-02f, -1.27138656e-02f, -1.44708646e-02f, -1.60958743e-02f, + -1.75748613e-02f, -1.88950773e-02f, -2.00452523e-02f, -2.10155784e-02f, + -2.17979173e-02f, -2.23857954e-02f, -2.27744512e-02f, -2.29609363e-02f, + -2.29440496e-02f, -2.27244286e-02f, -2.23044963e-02f, -2.16883864e-02f, + -2.08820436e-02f, -1.98930467e-02f, -1.87304962e-02f, -1.74051536e-02f, + -1.59290505e-02f, -1.43156476e-02f, -1.25794710e-02f, -1.07361870e-02f, + -8.80234289e-03f, -6.79523707e-03f, -4.73276287e-03f, -2.63333832e-03f, + -5.15560833e-04f, 1.60177115e-03f, 3.69994169e-03f, 5.76046713e-03f, + 7.76518011e-03f, 9.69651950e-03f, 1.15375588e-02f, 1.32722403e-02f, + 1.48854641e-02f, 1.63632477e-02f, 1.76928345e-02f, 1.88628539e-02f, + 1.98632836e-02f, 2.06857078e-02f, 2.13232370e-02f, 2.17707035e-02f, + 2.20245710e-02f, 2.20830689e-02f, 2.19461035e-02f, 2.16153999e-02f, + 2.10942875e-02f, 2.03878531e-02f, 1.95027602e-02f, 1.84472879e-02f, + 1.72311466e-02f, 1.58654896e-02f, 1.43627402e-02f, 1.27365006e-02f, + 1.10014956e-02f, 9.17326248e-03f, 7.26826777e-03f, 5.30349659e-03f, + 3.29647574e-03f, 1.26509823e-03f, -7.72666021e-04f, -2.79870526e-03f, + -4.79517508e-03f, -6.74446397e-03f, -8.62941284e-03f, -1.04335350e-02f, + -1.21410266e-02f, -1.37370298e-02f, -1.52076630e-02f, -1.65402345e-02f, + -1.77232647e-02f, -1.87466345e-02f, -1.96016877e-02f, -2.02812328e-02f, + -2.07796995e-02f, -2.10930662e-02f, -2.12190342e-02f, -2.11569310e-02f, + -2.09077424e-02f, -2.04741102e-02f, -1.98603315e-02f, -1.90722574e-02f, + -1.81172824e-02f, -1.70042570e-02f, -1.57433882e-02f, -1.43462276e-02f, + -1.28254403e-02f, -1.11947772e-02f, -9.46895912e-03f, -7.66347070e-03f, + -5.79450864e-03f, -3.87873939e-03f, -1.93323810e-03f, 2.46644329e-05f, + 1.97765804e-03f, 3.90841509e-03f, 5.79993335e-03f, 7.63553849e-03f, + 9.39912349e-03f, 1.10752347e-02f, 1.26492655e-02f, 1.41074757e-02f, + 1.54373004e-02f, 1.66272192e-02f, 1.76670438e-02f, 1.85479358e-02f, + 1.92624646e-02f, 1.98047255e-02f, 2.01703008e-02f, 2.03564026e-02f, + 2.03618038e-02f, 2.01868907e-02f, 1.98336400e-02f, 1.93056297e-02f, + 1.86079226e-02f, 1.77471409e-02f, 1.67312987e-02f, 1.55697372e-02f, + 1.42731251e-02f, 1.28532513e-02f, 1.13230121e-02f, 9.69619751e-03f, + 7.98747524e-03f, 6.21217610e-03f, 4.38617553e-03f, 2.52576378e-03f, + 6.47513831e-04f, -1.23187933e-03f, -3.09578918e-03f, -4.92771934e-03f, + -6.71155785e-03f, -8.43156989e-03f, -1.00726980e-02f, -1.16206095e-02f, + -1.30617878e-02f, -1.43837508e-02f, -1.55750228e-02f, -1.66254535e-02f, + -1.75260165e-02f, -1.82691095e-02f, -1.88485262e-02f, -1.92595629e-02f, + -1.94989485e-02f, -1.95649969e-02f, -1.94575478e-02f, -1.91779692e-02f, + -1.87291588e-02f, -1.81155240e-02f, -1.73428622e-02f, -1.64184497e-02f, + -1.53508419e-02f, -1.41498176e-02f, -1.28263924e-02f, -1.13925480e-02f, + -9.86129571e-03f, -8.24642962e-03f, -6.56245985e-03f, -4.82449803e-03f, + -3.04804665e-03f, -1.24899186e-03f, 5.56712448e-04f, 2.35302268e-03f, + 4.12404102e-03f, 5.85413883e-03f, 7.52808495e-03f, 9.13118114e-03f, + 1.06493773e-02f, 1.20694285e-02f, 1.33790131e-02f, 1.45667599e-02f, + 1.56224667e-02f, 1.65370727e-02f, 1.73028687e-02f, 1.79133659e-02f, + 1.83636049e-02f, 1.86499152e-02f, 1.87702262e-02f, 1.87238401e-02f, + 1.85116123e-02f, 1.81358025e-02f, 1.76001712e-02f, 1.69098379e-02f, + 1.60713269e-02f, 1.50924404e-02f, 1.39821625e-02f, 1.27507290e-02f, + 1.14093145e-02f, 9.97008458e-03f, 8.44606386e-03f, 6.85095093e-03f, + 5.19909294e-03f, 3.50524621e-03f, 1.78455190e-03f, 5.23496112e-05f, + -1.67599378e-03f, -3.38516088e-03f, -5.06003030e-03f, -6.68585576e-03f, + -8.24832082e-03f, -9.73374187e-03f, -1.11291228e-02f, -1.24223220e-02f, + -1.36021312e-02f, -1.46583598e-02f, -1.55819659e-02f, -1.63650578e-02f, + -1.70010772e-02f, -1.74847152e-02f, -1.78120952e-02f, -1.79806821e-02f, + -1.79893879e-02f, -1.78385292e-02f, -1.75298522e-02f, -1.70664857e-02f, + -1.64529222e-02f, -1.56949858e-02f, -1.47997517e-02f, -1.37755159e-02f, + -1.26316843e-02f, -1.13787184e-02f, -1.00279612e-02f, -8.59169598e-03f, + -7.08284107e-03f, -5.51497784e-03f, -3.90215899e-03f, -2.25882049e-03f, + -5.99578638e-04f, 1.06075048e-03f, 2.70749024e-03f, 4.32601367e-03f, + 5.90209087e-03f, 7.42179142e-03f, 8.87180147e-03f, 1.02394295e-02f, + 1.15127513e-02f, 1.26806976e-02f, 1.37331768e-02f, 1.46611542e-02f, + 1.54567137e-02f, 1.61130908e-02f, 1.66248644e-02f, 1.69878247e-02f, + 1.71991520e-02f, 1.72573319e-02f, 1.71622504e-02f, 1.69151533e-02f, + 1.65185918e-02f, 1.59764761e-02f, 1.52940069e-02f, 1.44775476e-02f, + 1.35347439e-02f, 1.24742225e-02f, 1.13057588e-02f, 1.00399437e-02f, + 8.68828286e-03f, 7.26299632e-03f, 5.77689293e-03f, 4.24335301e-03f, + 2.67608005e-03f, 1.08909445e-03f, -5.03504096e-04f, -2.08753398e-03f, + -3.64897310e-03f, -5.17400648e-03f, -6.64921573e-03f, -8.06161447e-03f, + -9.39882273e-03f, -1.06491934e-02f, -1.18018231e-02f, -1.28467447e-02f, + -1.37749479e-02f, -1.45785068e-02f, -1.52505941e-02f, -1.57855954e-02f, + -1.61790941e-02f, -1.64279942e-02f, -1.65304126e-02f, -1.64858700e-02f, + -1.62950933e-02f, -1.59601657e-02f, -1.54844596e-02f, -1.48725186e-02f, + -1.41301516e-02f, -1.32642739e-02f, -1.22829116e-02f, -1.11950413e-02f, + -1.00106329e-02f, -8.74042472e-03f, -7.39593243e-03f, -5.98926755e-03f, + -4.53309924e-03f, -3.04046635e-03f, -1.52472960e-03f, 6.00928259e-07f, + 1.52194755e-03f, 3.02583848e-03f, 4.49892816e-03f, 5.92824223e-03f, + 7.30119202e-03f, 8.60573140e-03f, 9.83048696e-03f, 1.09647391e-02f, + 1.19986831e-02f, 1.29234016e-02f, 1.37309351e-02f, 1.44144670e-02f, + 1.49682002e-02f, 1.53875953e-02f, 1.56692643e-02f, 1.58110475e-02f, + 1.58120917e-02f, 1.56726915e-02f, 1.53945244e-02f, 1.49803712e-02f, + 1.44342905e-02f, 1.37614814e-02f, 1.29682165e-02f, 1.20618964e-02f, + 1.10508386e-02f, 9.94431276e-03f, 8.75238815e-03f, 7.48587143e-03f, + 6.15624020e-03f, 4.77541534e-03f, 3.35583886e-03f, 1.91020436e-03f, + 4.51421254e-04f, -1.00751978e-03f, -2.45365701e-03f, -3.87418456e-03f, + -5.25654068e-03f, -6.58854736e-03f, -7.85852969e-03f, -9.05533440e-03f, + -1.01685578e-02f, -1.11885037e-02f, -1.21063745e-02f, -1.29142921e-02f, + -1.36053355e-02f, -1.41737244e-02f, -1.46146963e-02f, -1.49246914e-02f, + -1.51012942e-02f, -1.51432786e-02f, -1.50506566e-02f, -1.48245834e-02f, + -1.44674302e-02f, -1.39827154e-02f, -1.33750864e-02f, -1.26502768e-02f, + -1.18150098e-02f, -1.08770275e-02f, -9.84492957e-03f, -8.72812658e-03f, + -7.53678810e-03f, -6.28165584e-03f, -4.97410178e-03f, -3.62585109e-03f, + -2.24899664e-03f, -8.55861568e-04f, 5.41158727e-04f, 1.92960471e-03f, + 3.29720069e-03f, 4.63182847e-03f, 5.92171512e-03f, 7.15552772e-03f, + 8.32245104e-03f, 9.41229766e-03f, 1.04155873e-02f, 1.13236403e-02f, + 1.21286239e-02f, 1.28237067e-02f, 1.34029530e-02f, 1.38615561e-02f, + 1.41957640e-02f, 1.44029303e-02f, 1.44815754e-02f, 1.44313144e-02f, + 1.42529830e-02f, 1.39484805e-02f, 1.35208513e-02f, 1.29742567e-02f, + 1.23138195e-02f, 1.15457887e-02f, 1.06772196e-02f, 9.71614290e-03f, + 8.67133403e-03f, 7.55228769e-03f, 6.36919353e-03f, 5.13268639e-03f, + 3.85394864e-03f, 2.54443240e-03f, 1.21585789e-03f, -1.19878996e-04f, + -1.45093919e-03f, -2.76547718e-03f, -4.05185149e-03f, -5.29871877e-03f, + -6.49507635e-03f, -7.63046174e-03f, -8.69492807e-03f, -9.67921631e-03f, + -1.05747852e-02f, -1.13739326e-02f, -1.20697971e-02f, -1.26564969e-02f, + -1.31290964e-02f, -1.34837229e-02f, -1.37175190e-02f, -1.38287732e-02f, + -1.38167986e-02f, -1.36820574e-02f, -1.34260714e-02f, -1.30514744e-02f, + -1.25618820e-02f, -1.19619814e-02f, -1.12574303e-02f, -1.04547257e-02f, + -9.56133206e-03f, -8.58538654e-03f, -7.53583412e-03f, -6.42217872e-03f, + -5.25452181e-03f, -4.04336901e-03f, -2.79962633e-03f, -1.53443002e-03f, + -2.59098789e-04f, 1.01502741e-03f, 2.27659752e-03f, 3.51443317e-03f, + 4.71761329e-03f, 5.87551504e-03f, 6.97798012e-03f, 8.01534309e-03f, + 8.97859709e-03f, 9.85934513e-03f, 1.06500123e-02f, 1.13437857e-02f, + 1.19348028e-02f, 1.24180671e-02f, 1.27895845e-02f, 1.30463879e-02f, + 1.31864572e-02f, 1.32089360e-02f, 1.31138884e-02f, 1.29025483e-02f, + 1.25770694e-02f, 1.21406815e-02f, 1.15975855e-02f, 1.09528847e-02f, + 1.02125997e-02f, 9.38359994e-03f, 8.47347906e-03f, 7.49058151e-03f, + 6.44381432e-03f, 5.34271678e-03f, 4.19719189e-03f, 3.01755365e-03f, + 1.81439519e-03f, 5.98487206e-04f, -6.19328778e-04f, -1.82821924e-03f, + -3.01745871e-03f, -4.17650106e-03f, -5.29517520e-03f, -6.36359283e-03f, + -7.37244199e-03f, -8.31286411e-03f, -9.17674098e-03f, -9.95657112e-03f, + -1.06456657e-02f, -1.12381503e-02f, -1.17290432e-02f, -1.21142223e-02f, + -1.23906200e-02f, -1.25560393e-02f, -1.26093357e-02f, -1.25503481e-02f, + -1.23799279e-02f, -1.20999009e-02f, -1.17130697e-02f, -1.12231760e-02f, + -1.06348586e-02f, -9.95364282e-03f, -9.18584158e-03f, -8.33854107e-03f, + -7.41947999e-03f, -6.43706907e-03f, -5.40019213e-03f, -4.31826103e-03f, + -3.20099965e-03f, -2.05845707e-03f, -9.00865349e-04f, 2.61441292e-04f, + 1.41809108e-03f, 2.55883321e-03f, 3.67354889e-03f, 4.75241753e-03f, + 5.78592427e-03f, 6.76501988e-03f, 7.68115315e-03f, 8.52633033e-03f, + 9.29324902e-03f, 9.97532856e-03f, 1.05667189e-02f, 1.10624375e-02f, + 1.14583325e-02f, 1.17511845e-02f, 1.19386891e-02f, 1.20194307e-02f, + 1.19930645e-02f, 1.18600946e-02f, 1.16220073e-02f, 1.12812470e-02f, + 1.08411159e-02f, 1.03058419e-02f, 9.68042693e-03f, 8.97072807e-03f, + 8.18325916e-03f, 7.32531818e-03f, 6.40466057e-03f, 5.42973409e-03f, + 4.40930059e-03f, 3.35261692e-03f, 2.26916087e-03f, 1.16867964e-03f, + 6.09764122e-05f, -1.04405158e-03f, -2.13657269e-03f, -3.20691908e-03f, + -4.24563108e-03f, -5.24355860e-03f, -6.19193381e-03f, -7.08246398e-03f, + -7.90741664e-03f, -8.65959818e-03f, -9.33257195e-03f, -9.92055171e-03f, + -1.04185689e-02f, -1.08224536e-02f, -1.11288669e-02f, -1.13354045e-02f, + -1.14404744e-02f, -1.14434825e-02f, -1.13446735e-02f, -1.11452105e-02f, + -1.08472074e-02f, -1.04535649e-02f, -9.96807850e-03f, -9.39533832e-03f, + -8.74069245e-03f, -8.01020524e-03f, -7.21061702e-03f, -6.34920826e-03f, + -5.43385500e-03f, -4.47285238e-03f, -3.47490761e-03f, -2.44893717e-03f, + -1.40424120e-03f, -3.50117524e-04f, 7.03996367e-04f, 1.74874948e-03f, + 2.77486316e-03f, 3.77323529e-03f, 4.73510696e-03f, 5.65199516e-03f, + 6.51589535e-03f, 7.31925060e-03f, 8.05510111e-03f, 8.71711178e-03f, + 9.29960810e-03f, 9.79762661e-03f, 1.02069851e-02f, 1.05243267e-02f, + 1.07470734e-02f, 1.08735440e-02f, 1.09028797e-02f, 1.08351360e-02f, + 1.06711793e-02f, 1.04127447e-02f, 1.00624520e-02f, 9.62365700e-03f, + 9.10054672e-03f, 8.49802459e-03f, 7.82168998e-03f, 7.07776632e-03f, + 6.27310545e-03f, 5.41501721e-03f, 4.51131894e-03f, 3.57017926e-03f, + 2.60009355e-03f, 1.60979968e-03f, 6.08111937e-04f, -3.95951416e-04f, + -1.39349410e-03f, -2.37563261e-03f, -3.33368651e-03f, -4.25918973e-03f, + -5.14402447e-03f, -5.98039909e-03f, -6.76106610e-03f, -7.47919350e-03f, + -8.12860686e-03f, -8.70374621e-03f, -9.19968280e-03f, -9.61226566e-03f, + -9.93805155e-03f, -1.01744272e-02f, -1.03195556e-02f, -1.03723935e-02f, + -1.03327824e-02f, -1.02013418e-02f, -9.97951235e-03f, -9.66955161e-03f, + -9.27446962e-03f, -8.79806255e-03f, -8.24481164e-03f, -7.61986353e-03f, + -6.92898907e-03f, -6.17857782e-03f, -5.37544863e-03f, -4.52693679e-03f, + -3.64070939e-03f, -2.72478903e-03f, -1.78741480e-03f, -8.36980429e-04f, + 1.17989664e-04f, 1.06900538e-03f, 2.00758934e-03f, 2.92546046e-03f, + 3.81446851e-03f, 4.66681294e-03f, 5.47503707e-03f, 6.23202642e-03f, + 6.93126676e-03f, 7.56665434e-03f, 8.13275794e-03f, 8.62476397e-03f, + 9.03846441e-03f, 9.37048808e-03f, 9.61806678e-03f, 9.77929762e-03f, + 9.85300343e-03f, 9.83878137e-03f, 9.73705126e-03f, 9.54895686e-03f, + 9.27646789e-03f, 8.92224731e-03f, 8.48973525e-03f, 7.98296668e-03f, + 7.40673818e-03f, 6.76635688e-03f, 6.06774523e-03f, 5.31727151e-03f, + 4.52181520e-03f, 3.68856414e-03f, 2.82508906e-03f, 1.93911771e-03f, + 1.03864165e-03f, 1.31724000e-04f, -7.73548104e-04f, -1.66912498e-03f, + -2.54705639e-03f, -3.39960239e-03f, -4.21921926e-03f, -4.99874677e-03f, + -5.73135156e-03f, -6.41064787e-03f, -7.03078893e-03f, -7.58638515e-03f, + -8.07274239e-03f, -8.48573382e-03f, -8.82189842e-03f, -9.07847965e-03f, + -9.25348106e-03f, -9.34553774e-03f, -9.35415328e-03f, -9.27944192e-03f, + -9.12241341e-03f, -8.88466411e-03f, -8.56856089e-03f, -8.17721225e-03f, + -7.71429404e-03f, -7.18416104e-03f, -6.59175983e-03f, -5.94253368e-03f, + -5.24247041e-03f, -4.49794039e-03f, -3.71571847e-03f, -2.90290602e-03f, + -2.06680152e-03f, -1.21494821e-03f, -3.54960536e-04f, 5.05456753e-04f, + 1.35866818e-03f, 2.19707251e-03f, 3.01328571e-03f, 3.80007591e-03f, + 4.55055509e-03f, 5.25814438e-03f, 5.91669192e-03f, 6.52047200e-03f, + 7.06430640e-03f, 7.54353794e-03f, 7.95408283e-03f, 8.29252663e-03f, + 8.55606304e-03f, 8.74262824e-03f, 8.85075236e-03f, 8.87975906e-03f, + 8.82962202e-03f, 8.70105451e-03f, 8.49545293e-03f, 8.21486139e-03f, + 7.86207328e-03f, 7.44038974e-03f, 6.95385180e-03f, 6.40695971e-03f, + 5.80479502e-03f, 5.15286870e-03f, 4.45718246e-03f, 3.72404945e-03f, + 2.96010221e-03f, 2.17226938e-03f, 1.36762733e-03f, 5.53405381e-04f, + -2.63146484e-04f, -1.07470419e-03f, -1.87409443e-03f, -2.65421924e-03f, + -3.40822243e-03f, -4.12944508e-03f, -4.81156866e-03f, -5.44867726e-03f, + -6.03520323e-03f, -6.56613683e-03f, -7.03688002e-03f, -7.44347988e-03f, + -7.78249135e-03f, -8.05112205e-03f, -8.24721385e-03f, -8.36925002e-03f, + -8.41640683e-03f, -8.38846392e-03f, -8.28595912e-03f, -8.11001020e-03f, + -7.86244307e-03f, -7.54569095e-03f, -7.16278595e-03f, -6.71738300e-03f, + -6.21361469e-03f, -5.65619404e-03f, -5.05022152e-03f, -4.40127236e-03f, + -3.71526815e-03f, -2.99841577e-03f, -2.25722112e-03f, -1.49836763e-03f, + -7.28642071e-04f, 4.50434477e-05f, 8.15794780e-04f, 1.57675983e-03f, + 2.32119303e-03f, 3.04253373e-03f, 3.73440374e-03f, 4.39076109e-03f, + 5.00588458e-03f, 5.57441293e-03f, 6.09144353e-03f, 6.55256260e-03f, + 6.95381103e-03f, 7.29184996e-03f, 7.56385409e-03f, 7.76763312e-03f, + 7.90157987e-03f, 7.96474929e-03f, 7.95679715e-03f, 7.87804369e-03f, + 7.72942509e-03f, 7.51247337e-03f, 7.22938137e-03f, 6.88285636e-03f, + 6.47620933e-03f, 6.01325566e-03f, 5.49830484e-03f, 4.93612206e-03f, + 4.33185515e-03f, 3.69104049e-03f, 3.01948602e-03f, 2.32330330e-03f, + 1.60874544e-03f, 8.82231714e-04f, 1.50308827e-04f, -5.80528317e-04f, + -1.30375322e-03f, -2.01296135e-03f, -2.70188199e-03f, -3.36444701e-03f, + -3.99483762e-03f, -4.58758013e-03f, -5.13746453e-03f, -5.63980390e-03f, + -6.09023428e-03f, -6.48495361e-03f, -6.82059867e-03f, -7.09440613e-03f, + -7.30414154e-03f, -7.44814383e-03f, -7.52537074e-03f, -7.53532231e-03f, + -7.47817928e-03f, -7.35462729e-03f, -7.16604653e-03f, -6.91428861e-03f, + -6.60184485e-03f, -6.23166749e-03f, -5.80727376e-03f, -5.33260451e-03f, + -4.81206874e-03f, -4.25046030e-03f, -3.65290532e-03f, -3.02486716e-03f, + -2.37201464e-03f, -1.70026266e-03f, -1.01565670e-03f, -3.24326012e-04f, + 3.67549442e-04f, 1.05380850e-03f, 1.72833677e-03f, 2.38519657e-03f, + 3.01857597e-03f, 3.62294858e-03f, 4.19299252e-03f, 4.72378606e-03f, + 5.21069555e-03f, 5.64957232e-03f, 6.03664967e-03f, 6.36867744e-03f, + 6.64284990e-03f, 6.85698418e-03f, 7.00931443e-03f, 7.09874441e-03f, + 7.12465840e-03f, 7.08706888e-03f, 6.98651094e-03f, 6.82410178e-03f, + 6.60150306e-03f, 6.32090521e-03f, 5.98500306e-03f, 5.59697420e-03f, + 5.16044998e-03f, 4.67947995e-03f, 4.15852195e-03f, 3.60234011e-03f, + 3.01599436e-03f, 2.40483676e-03f, 1.77436911e-03f, 1.13028995e-03f, + 4.78371791e-04f, -1.75557439e-04f, -8.25650621e-04f, -1.46616696e-03f, + -2.09139965e-03f, -2.69583389e-03f, -3.27419516e-03f, -3.82140068e-03f, + -4.33268241e-03f, -4.80363658e-03f, -5.23017010e-03f, -5.60867917e-03f, + -5.93592714e-03f, -6.20921825e-03f, -6.42625503e-03f, -6.58536384e-03f, + -6.68526899e-03f, -6.72533025e-03f, -6.70537034e-03f, -6.62579421e-03f, + -6.48752153e-03f, -6.29196633e-03f, -6.04110074e-03f, -5.73732501e-03f, + -5.38355191e-03f, -4.98307960e-03f, -4.53965639e-03f, -4.05738219e-03f, + -3.54068617e-03f, -2.99427320e-03f, -2.42313171e-03f, -1.83242705e-03f, + -1.22748267e-03f, -6.13742401e-04f, 3.29459964e-06f, 6.18128643e-04f, + 1.22530705e-03f, 1.81942982e-03f, 2.39525993e-03f, 2.94773242e-03f, + 3.47202200e-03f, 3.96353949e-03f, 4.41805557e-03f, 4.83162874e-03f, + 5.20072081e-03f, 5.52222100e-03f, 5.79342983e-03f, 6.01209834e-03f, + 6.17646715e-03f, 6.28528285e-03f, 6.33778155e-03f, 6.33364321e-03f, + 6.27317175e-03f, 6.15705477e-03f, 5.98657179e-03f, 5.76336894e-03f, + 5.48968624e-03f, 5.16811657e-03f, 4.80167923e-03f, 4.39381474e-03f, + 3.94828981e-03f, 3.46922951e-03f, 2.96098404e-03f, 2.42819955e-03f, + 1.87572404e-03f, 1.30851770e-03f, 7.31708692e-04f, 1.50445075e-04f, + -4.30070196e-04f, -1.00466524e-03f, -1.56827393e-03f, -2.11588940e-03f, + -2.64269440e-03f, -3.14408074e-03f, -3.61568802e-03f, -4.05340575e-03f, + -4.45347457e-03f, -4.81245326e-03f, -5.12730726e-03f, -5.39539697e-03f, + -5.61447256e-03f, -5.78280084e-03f, -5.89903878e-03f, -5.96236515e-03f, + -5.97237627e-03f, -5.92920270e-03f, -5.83340844e-03f, -5.68605666e-03f, + -5.48859720e-03f, -5.24302551e-03f, -4.95168147e-03f, -4.61731774e-03f, + -4.24306545e-03f, -3.83242787e-03f, -3.38915992e-03f, -2.91735676e-03f, + -2.42128273e-03f, -1.90549963e-03f, -1.37460030e-03f, -8.33438466e-04f, + -2.86823004e-04f, 2.60354880e-04f, 8.03207784e-04f, 1.33692447e-03f, + 1.85678689e-03f, 2.35821923e-03f, 2.83683539e-03f, 3.28844174e-03f, + 3.70911927e-03f, 4.09525832e-03f, 4.44351520e-03f, 4.75094096e-03f, + 5.01496724e-03f, 5.23338423e-03f, 5.40441842e-03f, 5.52671520e-03f, + 5.59940305e-03f, 5.62196231e-03f, 5.59441894e-03f, 5.51717260e-03f, + 5.39113480e-03f, 5.21756593e-03f, 4.99821309e-03f, 4.73517171e-03f, + 4.43098991e-03f, 4.08848316e-03f, 3.71086536e-03f, 3.30161941e-03f, + 2.86452112e-03f, 2.40355058e-03f, 1.92289472e-03f, 1.42691829e-03f, + 9.20082667e-04f, 4.06947402e-04f, -1.07889236e-04f, -6.19866009e-04f, + -1.12440714e-03f, -1.61705134e-03f, -2.09347995e-03f, -2.54950186e-03f, + -2.98112018e-03f, -3.38459298e-03f, -3.75646023e-03f, -4.09349417e-03f, + -4.39283025e-03f, -4.65196264e-03f, -4.86872185e-03f, -5.04135627e-03f, + -5.16846121e-03f, -5.24911038e-03f, -5.28273546e-03f, -5.26923483e-03f, + -5.20889346e-03f, -5.10241936e-03f, -4.95095616e-03f, -4.75600604e-03f, + -4.51946213e-03f, -4.24360018e-03f, -3.93100678e-03f, -3.58463172e-03f, + -3.20766423e-03f, -2.80356963e-03f, -2.37606413e-03f, -1.92903332e-03f, + -1.46654246e-03f, -9.92737520e-04f, -5.11899001e-04f, -2.83386680e-05f, + 4.53646722e-04f, 9.29762308e-04f, 1.39578833e-03f, 1.84762733e-03f, + 2.28131825e-03f, 2.69304631e-03f, 3.07924546e-03f, 3.43657876e-03f, + 3.76196637e-03f, 4.05264982e-03f, 4.30615625e-03f, 4.52037923e-03f, + 4.69357270e-03f, 4.82432688e-03f, 4.91167463e-03f, 4.95498330e-03f, + 4.95403020e-03f, 4.90901290e-03f, 4.82048355e-03f, 4.68941823e-03f, + 4.51712335e-03f, 4.30530103e-03f, 4.05599901e-03f, 3.77155674e-03f, + 3.45466234e-03f, 3.10824592e-03f, 2.73552763e-03f, 2.33988646e-03f, + 1.92496688e-03f, 1.49452387e-03f, 1.05244323e-03f, 6.02711670e-04f, + 1.49351638e-04f, -3.03587769e-04f, -7.52061949e-04f, -1.19211664e-03f, + -1.61985030e-03f, -2.03151752e-03f, -2.42352388e-03f, -2.79243508e-03f, + -3.13508754e-03f, -3.44849890e-03f, -3.73002846e-03f, -3.97727137e-03f, + -4.18818648e-03f, -4.36099952e-03f, -4.49435996e-03f, -4.58719167e-03f, + -4.63889710e-03f, -4.64910091e-03f, -4.61791828e-03f, -4.54579073e-03f, + -4.43349692e-03f, -4.28220742e-03f, -4.09340522e-03f, -3.86894313e-03f, + -3.61094458e-03f, -3.32182985e-03f, -3.00430811e-03f, -2.66130866e-03f, + -2.29599013e-03f, -1.91166816e-03f, -1.51187551e-03f, -1.10018574e-03f, + -6.80346354e-04f, -2.56113849e-04f, 1.68739284e-04f, 5.90407275e-04f, + 1.00515855e-03f, 1.40933774e-03f, 1.79936566e-03f, 2.17186782e-03f, + 2.52357035e-03f, 2.85142336e-03f, 3.15262986e-03f, 3.42461005e-03f, + 3.66503089e-03f, 3.87190507e-03f, 4.04353460e-03f, 4.17850434e-03f, + 4.27578294e-03f, 4.33462985e-03f, 4.35470557e-03f, 4.33597602e-03f, + 4.27874072e-03f, 4.18369422e-03f, 4.05179951e-03f, 3.88441838e-03f, + 3.68314007e-03f, 3.44992044e-03f, 3.18696299e-03f, 2.89668435e-03f, + 2.58184280e-03f, 2.24526719e-03f, 1.89009368e-03f, 1.51951951e-03f, + 1.13694082e-03f, 7.45769400e-04f, 3.49541752e-04f, -4.81936756e-05f, + -4.43913653e-04f, -8.34083461e-04f, -1.21527427e-03f, -1.58411419e-03f, + -1.93737804e-03f, -2.27200055e-03f, -2.58505953e-03f, -2.87386789e-03f, + -3.13596261e-03f, -3.36909158e-03f, -3.57133916e-03f, -3.74099847e-03f, + -3.87671687e-03f, -3.97740006e-03f, -4.04231029e-03f, -4.07103135e-03f, + -4.06340767e-03f, -4.01970877e-03f, -3.94042889e-03f, -3.82644559e-03f, + -3.67889403e-03f, -3.49922796e-03f, -3.28921214e-03f, -3.05076130e-03f, + -2.78618777e-03f, -2.49791205e-03f, -2.18859644e-03f, -1.86109553e-03f, + -1.51839073e-03f, -1.16358358e-03f, -7.99877062e-04f, -4.30553251e-04f, + -5.89097879e-05f, 3.11723769e-04f, 6.78086247e-04f, 1.03688551e-03f, + 1.38501625e-03f, 1.71939231e-03f, 2.03710542e-03f, 2.33541156e-03f, + 2.61171872e-03f, 2.86367519e-03f, 3.08912751e-03f, 3.28618123e-03f, + 3.45323225e-03f, 3.58887288e-03f, 3.69206164e-03f, 3.76199260e-03f, + 3.79818934e-03f, 3.80048902e-03f, 3.76896561e-03f, 3.70409972e-03f, + 3.60655769e-03f, 3.47737266e-03f, 3.31778682e-03f, 3.12940296e-03f, + 2.91396911e-03f, 2.67353140e-03f, 2.41032316e-03f, 2.12679252e-03f, + 1.82553429e-03f, 1.50931293e-03f, 1.18097998e-03f, 8.43534445e-04f, + 4.99989066e-04f, 1.53447611e-04f, -1.93022636e-04f, -5.36321933e-04f, + -8.73416474e-04f, -1.20133088e-03f, -1.51720912e-03f, -1.81826798e-03f, + -2.10190341e-03f, -2.36567388e-03f, -2.60729607e-03f, -2.82474796e-03f, + -3.01616460e-03f, -3.17997100e-03f, -3.31484550e-03f, -3.41966897e-03f, + -3.49367342e-03f, -3.53630851e-03f, -3.54733556e-03f, -3.52679670e-03f, + -3.47499990e-03f, -3.39254703e-03f, -3.28028198e-03f, -3.13934064e-03f, + -2.97110336e-03f, -2.77718173e-03f, -2.55939692e-03f, -2.31979112e-03f, + -2.06058943e-03f, -1.78418388e-03f, -1.49308601e-03f, -1.18996918e-03f, + -8.77562261e-04f, -5.58694884e-04f, -2.36201878e-04f, 8.69917292e-05f, + 4.08040872e-04f, 7.24100044e-04f, 1.03237391e-03f, 1.33015630e-03f, + 1.61484341e-03f, 1.88395774e-03f, 2.13520669e-03f, 2.36638885e-03f, + 2.57554947e-03f, 2.76095026e-03f, 2.92101166e-03f, 3.05442850e-03f, + 3.16014757e-03f, 3.23732893e-03f, 3.28541223e-03f, 3.30412034e-03f, + 3.29335572e-03f, 3.25342718e-03f, 3.18472643e-03f, 3.08805315e-03f, + 2.96434808e-03f, 2.81484617e-03f, 2.64098428e-03f, 2.44441964e-03f, + 2.22698580e-03f, 1.99071671e-03f, 1.73777144e-03f, 1.47049147e-03f, + 1.19131101e-03f, 9.02757052e-04f, 6.07440484e-04f, 3.08007319e-04f, + 7.13829517e-06f, -2.92474511e-04f, -5.88197665e-04f, -8.77399733e-04f, + -1.15755209e-03f, -1.42622267e-03f, -1.68101236e-03f, -1.91978505e-03f, + -2.14045006e-03f, -2.34115054e-03f, -2.52017578e-03f, -2.67602742e-03f, + -2.80743844e-03f, -2.91334720e-03f, -2.99289427e-03f, -3.04551982e-03f, + -3.07087625e-03f, -3.06883997e-03f, -3.03955162e-03f, -2.98340720e-03f, + -2.90101796e-03f, -2.79322818e-03f, -2.66108957e-03f, -2.50591158e-03f, + -2.32917402e-03f, -2.13252284e-03f, -1.91779817e-03f, -1.68700449e-03f, + -1.44223748e-03f, -1.18575491e-03f, -9.19861684e-04f, -6.47001914e-04f, + -3.69593810e-04f, -9.01276712e-05f, 1.88886030e-04f, 4.64997876e-04f, + 7.35743579e-04f, 9.98752535e-04f, 1.25173547e-03f, 1.49246727e-03f, + 1.71888893e-03f, 1.92901822e-03f, 2.12110128e-03f, 2.29349620e-03f, + 2.44475112e-03f, 2.57362114e-03f, 2.67908845e-03f, 2.76026445e-03f, + 2.81660118e-03f, 2.84766807e-03f, 2.85331403e-03f, 2.83360095e-03f, + 2.78882988e-03f, 2.71949307e-03f, 2.62633897e-03f, 2.51027194e-03f, + 2.37244667e-03f, 2.21418592e-03f, 2.03698052e-03f, 1.84248796e-03f, + 1.63251333e-03f, 1.40901166e-03f, 1.17398423e-03f, 9.29629511e-04f, + 6.78093274e-04f, 4.21697134e-04f, 1.62708629e-04f, -9.65532846e-05f, + -3.53786150e-04f, -6.06698280e-04f, -8.53097342e-04f, -1.09078811e-03f, + -1.31773933e-03f, -1.53194961e-03f, -1.73160040e-03f, -1.91496699e-03f, + -2.08051810e-03f, -2.22683099e-03f, -2.35272161e-03f, -2.45716809e-03f, + -2.53932263e-03f, -2.59857031e-03f, -2.63448385e-03f, -2.64686900e-03f, + -2.63568789e-03f, -2.60117512e-03f, -2.54374911e-03f, -2.46399878e-03f, + -2.36276068e-03f, -2.24101080e-03f, -2.09994110e-03f, -1.94088932e-03f, + -1.76533620e-03f, -1.57492545e-03f, -1.37143402e-03f, -1.15668344e-03f, + -9.32686687e-04f, -7.01423675e-04f, -4.65019034e-04f, -2.25575437e-04f, + 1.47791969e-05f, 2.53869081e-04f, 4.89608281e-04f, 7.19930839e-04f, + 9.42775487e-04f, 1.15624415e-03f, 1.35846050e-03f, 1.54767351e-03f, + 1.72231203e-03f, 1.88082576e-03f, 2.02194509e-03f, 2.14444684e-03f, + 2.24738140e-03f, 2.32988384e-03f, 2.39134751e-03f, 2.43130004e-03f, + 2.44952080e-03f, 2.44590293e-03f, 2.42061909e-03f, 2.37398935e-03f, + 2.30650850e-03f, 2.21889198e-03f, 2.11201559e-03f, 1.98687753e-03f, + 1.84474091e-03f, 1.68687111e-03f, 1.51481261e-03f, 1.33010545e-03f, + 1.13447001e-03f, 9.29723262e-04f, 7.17657881e-04f, 5.00235175e-04f, + 2.79406846e-04f, 5.71322995e-05f, -1.64594545e-04f, -3.83823629e-04f, + -5.98614360e-04f, -8.07065822e-04f, -1.00739680e-03f, -1.19784818e-03f, + -1.37675697e-03f, -1.54263099e-03f, -1.69403924e-03f, -1.82971049e-03f, + -1.94850212e-03f, -2.04945536e-03f, -2.13177374e-03f, -2.19478544e-03f, + -2.23804061e-03f, -2.26125546e-03f, -2.26431492e-03f, -2.24728584e-03f, + -2.21038704e-03f, -2.15409950e-03f, -2.07896974e-03f, -1.98576777e-03f, + -1.87541310e-03f, -1.74895112e-03f, -1.60761319e-03f, -1.45267246e-03f, + -1.28561050e-03f, -1.10794701e-03f, -9.21319188e-04f, -7.27412747e-04f, + -5.28001974e-04f, -3.24849174e-04f, -1.19822475e-04f, 8.53071910e-05f, + 2.88686843e-04f, 4.88528049e-04f, 6.83070989e-04f, 8.70634797e-04f, + 1.04957014e-03f, 1.21833395e-03f, 1.37549860e-03f, 1.51969890e-03f, + 1.64975621e-03f, 1.76454677e-03f, 1.86314685e-03f, 1.94476478e-03f, + 2.00874558e-03f, 2.05462014e-03f, 2.08206459e-03f, 2.09092884e-03f, + 2.08122572e-03f, 2.05311829e-03f, 2.00696285e-03f, 1.94325570e-03f, + 1.86261554e-03f, 1.76589405e-03f, 1.65398613e-03f, 1.52796111e-03f, + 1.38902998e-03f, 1.23845862e-03f, 1.07765357e-03f, 9.08101811e-04f, + 7.31312372e-04f, 5.48925579e-04f, 3.62559457e-04f, 1.73908753e-04f, + -1.53640653e-05f, -2.03576240e-04f, -3.89073746e-04f, -5.70178373e-04f, + -7.45367890e-04f, -9.13091163e-04f, -1.07188249e-03f, -1.22041361e-03f, + -1.35740687e-03f, -1.48168528e-03f, -1.59225154e-03f, -1.68814122e-03f, + -1.76862655e-03f, -1.83303385e-03f, -1.88089694e-03f, -1.91186980e-03f, + -1.92575160e-03f, -1.92251421e-03f, -1.90226422e-03f, -1.86526958e-03f, + -1.81193893e-03f, -1.74283351e-03f, -1.65863573e-03f, -1.56017209e-03f, + -1.44838006e-03f, -1.32431143e-03f, -1.18916080e-03f, -1.04412159e-03f, + -8.90592600e-04f, -7.29926142e-04f, -5.63607604e-04f, -3.93123732e-04f, + -2.20015398e-04f, -4.58265535e-05f, 1.27888591e-04f, 2.99622014e-04f, + 4.67812693e-04f, 6.31045212e-04f, 7.87850352e-04f, 9.36918188e-04f, + 1.07691014e-03f, 1.20669763e-03f, 1.32512406e-03f, 1.43124633e-03f, + 1.52414726e-03f, 1.60310171e-03f, 1.66746035e-03f, 1.71674068e-03f, + 1.75057975e-03f, 1.76874501e-03f, 1.77117438e-03f, 1.75790455e-03f, + 1.72916148e-03f, 1.68523050e-03f, 1.62665041e-03f, 1.55393961e-03f, + 1.46788028e-03f, 1.36921441e-03f, 1.25897234e-03f, 1.13815311e-03f, + 1.00785213e-03f, 8.69331636e-04f, 7.23787860e-04f, 5.72621147e-04f, + 4.17123806e-04f, 2.58743469e-04f, 9.88837891e-05f, -6.10350755e-05f, + -2.19596167e-04f, -3.75409622e-04f, -5.27082337e-04f, -6.73344125e-04f, + -8.12882243e-04f, -9.44509077e-04f, -1.06712919e-03f, -1.17964492e-03f, + -1.28116892e-03f, -1.37084083e-03f, -1.44791745e-03f, -1.51179651e-03f, + -1.56196842e-03f, -1.59806342e-03f, -1.61984890e-03f, -1.62719129e-03f, + -1.62009091e-03f, -1.59871731e-03f, -1.56330213e-03f, -1.51425912e-03f, + -1.45207397e-03f, -1.37735224e-03f, -1.29085967e-03f, -1.19339166e-03f, + -1.08587214e-03f, -9.69313282e-04f, -8.44784217e-04f, -7.13452738e-04f, + -5.76480508e-04f, -4.35102909e-04f, -2.90654344e-04f, -1.44369163e-04f, + 2.42120883e-06f, 1.48447430e-04f, 2.92370866e-04f, 4.32973514e-04f, + 5.68999279e-04f, 6.99308513e-04f, 8.22732435e-04f, 9.38238883e-04f, + 1.04484914e-03f, 1.14166499e-03f, 1.22788622e-03f, 1.30279910e-03f, + 1.36578274e-03f, 1.41640462e-03f, 1.45420013e-03f, 1.47896611e-03f, + 1.49053972e-03f, 1.48884571e-03f, 1.47404145e-03f, 1.44625672e-03f, + 1.40585004e-03f, 1.35323924e-03f, 1.28894075e-03f, 1.21358722e-03f, + 1.12792237e-03f, 1.03272450e-03f, 9.28938538e-04f, 8.17461092e-04f, + 6.99372886e-04f, 5.75717626e-04f, 4.47641236e-04f, 3.16292734e-04f, + 1.82841969e-04f, 4.84802779e-05f, -8.55739910e-05f, -2.18169080e-04f, + -3.48142521e-04f, -4.74327437e-04f, -5.95661172e-04f, -7.11080885e-04f, + -8.19593186e-04f, -9.20314282e-04f, -1.01233129e-03f, -1.09493574e-03f, + -1.16743147e-03f, -1.22920752e-03f, -1.27981998e-03f, -1.31882788e-03f, + -1.34600511e-03f, -1.36114508e-03f, -1.36417035e-03f, -1.35514680e-03f, + -1.33419233e-03f, -1.30158504e-03f, -1.25764422e-03f, -1.20284876e-03f, + -1.13771466e-03f, -1.06289654e-03f, -9.79088444e-04f, -8.87105266e-04f, + -7.87774055e-04f, -6.82024906e-04f, -5.70838905e-04f, -4.55202296e-04f, + -3.36181145e-04f, -2.14839486e-04f, -9.22503227e-05f, 3.04698838e-05f, + 1.52270414e-04f, 2.72050892e-04f, 3.88799677e-04f, 5.01453830e-04f, + 6.09100748e-04f, 7.10751675e-04f, 8.05583159e-04f, 8.92801542e-04f, + 9.71649359e-04f, 1.04151118e-03f, 1.10177598e-03f, 1.15200217e-03f, + 1.19178629e-03f, 1.22083114e-03f, 1.23895814e-03f, 1.24603381e-03f, + 1.24209314e-03f, 1.22718978e-03f, 1.20157123e-03f, 1.16547661e-03f, + 1.11929301e-03f, 1.06350264e-03f, 9.98630850e-04f, 9.25308342e-04f, + 8.44227193e-04f, 7.56141297e-04f, 6.61877038e-04f, 5.62284265e-04f, + 4.58306144e-04f, 3.50831771e-04f, 2.40873321e-04f, 1.29410208e-04f, + 1.74046420e-05f, -9.41372015e-05f, -2.04212769e-04f, -3.11884379e-04f, + -4.16198692e-04f, -5.16283854e-04f, -6.11247752e-04f, -7.00285651e-04f, + -7.82656708e-04f, -8.57682384e-04f, -9.24710993e-04f, -9.83216422e-04f, + -1.03273072e-03f, -1.07286281e-03f, -1.10331228e-03f, -1.12385081e-03f, + -1.13439398e-03f, -1.13485267e-03f, -1.12532108e-03f, -1.10590886e-03f, + -1.07686330e-03f, -1.03849711e-03f, -9.91189076e-04f, -9.35388727e-04f, + -8.71690018e-04f, -8.00651347e-04f, -7.22956626e-04f, -6.39322842e-04f, + -5.50528037e-04f, -4.57398663e-04f, -3.60742819e-04f, -2.61456827e-04f, + -1.60433252e-04f, -5.85672438e-05f, 4.32388885e-05f, 1.44099976e-04f, + 2.43118717e-04f, 3.39409985e-04f, 4.32194068e-04f, 5.20625265e-04f, + 6.03978397e-04f, 6.81539342e-04f, 7.52654738e-04f, 8.16724875e-04f, + 8.73252328e-04f, 9.21758980e-04f, 9.61871797e-04f, 9.93285392e-04f, + 1.01577077e-03f, 1.02918084e-03f, 1.03343231e-03f, 1.02859389e-03f, + 1.01469668e-03f, 9.91956413e-04f, 9.60609395e-04f, 9.20977714e-04f, + 8.73477570e-04f, 8.18534765e-04f, 7.56717188e-04f, 6.88582017e-04f, + 6.14781378e-04f, 5.35992969e-04f, 4.52932280e-04f, 3.66367367e-04f, + 2.77067953e-04f, 1.85846068e-04f, 9.35202666e-05f, 8.95800378e-07f, + -9.12013687e-05f, -1.81968879e-04f, -2.70612753e-04f, -3.56346876e-04f, + -4.38458579e-04f, -5.16248792e-04f, -5.89021803e-04f, -6.56215711e-04f, + -7.17226849e-04f, -7.71587683e-04f, -8.18834525e-04f, -8.58598909e-04f, + -8.90593018e-04f, -9.14550231e-04f, -9.30330737e-04f, -9.37818621e-04f, + -9.37042342e-04f, -9.28000423e-04f, -9.10856704e-04f, -8.85795548e-04f, + -8.53094084e-04f, -8.13072428e-04f, -7.66143829e-04f, -7.12744419e-04f, + -6.53375169e-04f, -5.88633731e-04f, -5.19070071e-04f, -4.45359640e-04f, + -3.68152539e-04f, -2.88173584e-04f, -2.06121828e-04f, -1.22733383e-04f, + -3.87658539e-05f, 4.50501871e-05f, 1.27995593e-04f, 2.09307286e-04f, + 2.88316517e-04f, 3.64302877e-04f, 4.36661583e-04f, 5.04755088e-04f, + 5.68007944e-04f, 6.25887844e-04f, 6.77933133e-04f, 7.23703420e-04f, + 7.62852083e-04f, 7.95064070e-04f, 8.20080681e-04f, 8.37764175e-04f, + 8.47969128e-04f, 8.50664693e-04f, 8.45873351e-04f, 8.33674258e-04f, + 8.14207332e-04f, 7.87719338e-04f, 7.54456412e-04f, 7.14767940e-04f, + 6.69041313e-04f, 6.17697028e-04f, 5.61235426e-04f, 5.00173623e-04f, + 4.35079832e-04f, 3.66567870e-04f, 2.95227094e-04f, 2.21715344e-04f, + 1.46718993e-04f, 7.08530739e-05f, -5.15577364e-06f, -8.07004736e-05f, + -1.55066524e-04f, -2.27613536e-04f, -2.97740553e-04f, -3.64841340e-04f, + -4.28317869e-04f, -4.87670380e-04f, -5.42375883e-04f, -5.92004229e-04f, + -6.36123948e-04f, -6.74394124e-04f, -7.06529242e-04f, -7.32237235e-04f, + -7.51412988e-04f, -7.63820757e-04f, -7.69492035e-04f, -7.68344591e-04f, + -7.60492235e-04f, -7.45985791e-04f, -7.25024598e-04f, -6.97830116e-04f, + -6.64676731e-04f, -6.25892548e-04f, -5.81842478e-04f, -5.32951282e-04f, + -4.79696107e-04f, -4.22525795e-04f, -3.62014271e-04f, -2.98672472e-04f, + -2.33120510e-04f, -1.65900121e-04f, -9.76362200e-05f, -2.89275243e-05f, + 3.96176674e-05f, 1.07395474e-04f, 1.73835271e-04f, 2.38325404e-04f, + 3.00347969e-04f, 3.59354345e-04f, 4.14862825e-04f, 4.66386521e-04f, + 5.13515409e-04f, 5.55829029e-04f, 5.93042831e-04f, 6.24819812e-04f, + 6.50905442e-04f, 6.71146467e-04f, 6.85375692e-04f, 6.93516598e-04f, + 6.95509528e-04f, 6.91405413e-04f, 6.81262048e-04f, 6.65220278e-04f, + 6.43420145e-04f, 6.16125905e-04f, 5.83606917e-04f, 5.46156953e-04f, + 5.04157409e-04f, 4.58002510e-04f, 4.08099631e-04f, 3.54942755e-04f, + 2.98984828e-04f, 2.40759234e-04f, 1.80768051e-04f, 1.19575974e-04f, + 5.76957438e-05f, -4.28880367e-06f, -6.58721960e-05f, -1.26493174e-04f, + -1.85639859e-04f, -2.42807383e-04f, -2.97481187e-04f, -3.49221122e-04f, + -3.97578847e-04f, -4.42159263e-04f, -4.82605142e-04f, -5.18557036e-04f, + -5.49760019e-04f, -5.75954068e-04f, -5.96944106e-04f, -6.12574988e-04f, + -6.22745551e-04f, -6.27396115e-04f, -6.26534322e-04f, -6.20176804e-04f, + -6.08460174e-04f, -5.91464522e-04f, -5.69410030e-04f, -5.42484457e-04f, + -5.10992484e-04f, -4.75225134e-04f, -4.35505918e-04f, -3.92225972e-04f, + -3.45771246e-04f, -2.96593648e-04f, -2.45116844e-04f, -1.91808621e-04f, + -1.37165970e-04f, -8.16356339e-05f, -2.57445926e-05f, 3.00322984e-05f, + 8.51783200e-05f, 1.39260137e-04f, 1.91796337e-04f, 2.42317102e-04f, + 2.90405255e-04f, 3.35659286e-04f, 3.77691701e-04f, 4.16174018e-04f, + 4.50785161e-04f, 4.81219250e-04f, 5.07259738e-04f, 5.28714262e-04f, + 5.45399813e-04f, 5.57228370e-04f, 5.64092051e-04f, 5.66000580e-04f, + 5.62944333e-04f, 5.54966288e-04f, 5.42209838e-04f, 5.24790563e-04f, + 5.02873830e-04f, 4.76708610e-04f, 4.46521001e-04f, 4.12650276e-04f, + 3.75358170e-04f, 3.35013907e-04f, 2.91997251e-04f, 2.46695035e-04f, + 1.99507857e-04f, 1.50889553e-04f, 1.01231956e-04f, 5.10047602e-05f, + 6.33587302e-07f, -4.94259002e-05f, -9.87418814e-05f, -1.46889490e-04f, + -1.93470217e-04f, -2.38065458e-04f, -2.80305060e-04f, -3.19836003e-04f, + -3.56327452e-04f, -3.89502317e-04f, -4.19041782e-04f, -4.44759447e-04f, + -4.66423158e-04f, -4.83886447e-04f, -4.97011387e-04f, -5.05730787e-04f, + -5.09963720e-04f, -5.09717728e-04f, -5.05043322e-04f, -4.95963142e-04f, + -4.82639446e-04f, -4.65152875e-04f, -4.43733866e-04f, -4.18567153e-04f, + -3.89890304e-04f, -3.57996097e-04f, -3.23170997e-04f, -2.85736681e-04f, + -2.46055002e-04f, -2.04445682e-04f, -1.61320963e-04f, -1.17042529e-04f, + -7.20188676e-05f, -2.66396241e-05f, 1.86912290e-05f, 6.36017948e-05f, + 1.07665176e-04f, 1.50514890e-04f, 1.91812013e-04f, 2.31174729e-04f, + 2.68275281e-04f, 3.02820173e-04f, 3.34517215e-04f, 3.63107193e-04f, + 3.88350390e-04f, 4.10051248e-04f, 4.28044624e-04f, 4.42214263e-04f, + 4.52417167e-04f, 4.58637799e-04f, 4.60811960e-04f, 4.58959019e-04f, + 4.53110813e-04f, 4.43353615e-04f, 4.29788076e-04f, 4.12573604e-04f, + 3.91837458e-04f, 3.67827680e-04f, 3.40742776e-04f, 3.10878990e-04f, + 2.78464465e-04f, 2.43800376e-04f, 2.07253019e-04f, 1.69099894e-04f, + 1.29702525e-04f, 8.93909104e-05f, 4.85743353e-05f, 7.55415142e-06f, + -3.32831786e-05f, -7.36072164e-05f, -1.13043176e-04f, -1.51234384e-04f, + -1.87933888e-04f, -2.22751408e-04f, -2.55424048e-04f, -2.85707115e-04f, + -3.13301151e-04f, -3.38018882e-04f, -3.59650405e-04f, -3.78009094e-04f, + -3.93005971e-04f, -4.04467563e-04f, -4.12352296e-04f, -4.16625213e-04f, + -4.17213487e-04f, -4.14188932e-04f, -4.07570766e-04f, -3.97428285e-04f, + -3.83928030e-04f, -3.67135876e-04f, -3.47267227e-04f, -3.24463119e-04f, + -2.98994961e-04f, -2.71043298e-04f, -2.40926487e-04f, -2.08843709e-04f, + -1.75152575e-04f, -1.40107049e-04f, -1.04080472e-04f, -6.73135829e-05f, + -3.01948541e-05f, 7.00871665e-06f, 4.39240599e-05f, 8.02943349e-05f, + 1.15726815e-04f, 1.49966848e-04f, 1.82710879e-04f, 2.13703334e-04f, + 2.42653106e-04f, 2.69351388e-04f, 2.93549873e-04f, 3.15102924e-04f, + 3.33768751e-04f, 3.49447907e-04f, 3.62019988e-04f, 3.71363176e-04f, + 3.77445082e-04f, 3.80209433e-04f, 3.79656986e-04f, 3.75829456e-04f, + 3.68740564e-04f, 3.58494796e-04f, 3.45203622e-04f, 3.28976671e-04f, + 3.09977680e-04f, 2.88380075e-04f, 2.64390378e-04f, 2.38244599e-04f, + 2.10143730e-04f, 1.80382945e-04f, 1.49191334e-04f, 1.16874433e-04f, + 8.36939641e-05f, 4.99545687e-05f, 1.59419682e-05f, -1.80487956e-05f, + -5.17116932e-05f, -8.47731419e-05f, -1.16937183e-04f, -1.47911960e-04f, + -1.77490349e-04f, -2.05400357e-04f, -2.31387402e-04f, -2.55252058e-04f, + -2.76804796e-04f, -2.95856747e-04f, -3.12270197e-04f, -3.25902725e-04f, + -3.36672987e-04f, -3.44466047e-04f, -3.49280604e-04f, -3.51026811e-04f, + -3.49738846e-04f, -3.45444097e-04f, -3.38175670e-04f, -3.28025304e-04f, + -3.15107780e-04f, -2.99483932e-04f, -2.81389775e-04f, -2.60907140e-04f, + -2.38309091e-04f, -2.13697965e-04f, -1.87369701e-04f, -1.59547138e-04f, + -1.30435290e-04f, -1.00336342e-04f, -6.95075165e-05f, -3.81891347e-05f, + -6.66316342e-06f, 2.48005967e-05f, 5.59093608e-05f, 8.64381970e-05f, + 1.16084478e-04f, 1.44629782e-04f, 1.71817136e-04f, 1.97430379e-04f, + 2.21249248e-04f, 2.43067483e-04f, 2.62714805e-04f, 2.80037512e-04f, + 2.94912411e-04f, 3.07154450e-04f, 3.16770812e-04f, 3.23565757e-04f, + 3.27598190e-04f, 3.28781884e-04f, 3.27137017e-04f, 3.22671504e-04f, + 3.15478482e-04f, 3.05561179e-04f, 2.93071243e-04f, 2.78072214e-04f, + 2.60752421e-04f, 2.41204026e-04f, 2.19657587e-04f, 1.96274763e-04f, + 1.71259618e-04f, 1.44829587e-04f, 1.17207736e-04f, 8.86396900e-05f, + 5.93848595e-05f, 2.96756971e-05f, -2.55974213e-07f, -3.01364040e-05f, + -5.97095986e-05f, -8.87016361e-05f, -1.16938981e-04f, -1.44121097e-04f, + -1.70042060e-04f, -1.94504049e-04f, -2.17285275e-04f, -2.38190653e-04f, + -2.57074514e-04f, -2.73769854e-04f, -2.88131929e-04f, -3.00067778e-04f, + -3.09445066e-04f, -3.16238251e-04f, -3.20350572e-04f, -3.21779258e-04f, + -3.20521292e-04f, -3.16575228e-04f, -3.09991670e-04f, -3.00802605e-04f, + -2.89147820e-04f, -2.75068336e-04f, -2.58722607e-04f, -2.40234254e-04f, + -2.19762234e-04f, -1.97484144e-04f, -1.73586378e-04f, -1.48283187e-04f, + -1.21767035e-04f, -9.42546438e-05f, -6.59849449e-05f, -3.72382910e-05f, + -8.18708029e-06f, 2.09018928e-05f, 4.97856427e-05f, 7.82043260e-05f, + 1.05945996e-04f, 1.32796055e-04f, 1.58498048e-04f, 1.82852505e-04f, + 2.05652703e-04f, 2.26725402e-04f, 2.45875363e-04f, 2.62957031e-04f, + 2.77828957e-04f, 2.90361412e-04f, 3.00450626e-04f, 3.07979930e-04f, + 3.12961709e-04f, 3.15260735e-04f, 3.14925301e-04f, 3.11915939e-04f, + 3.06269493e-04f, 2.97998031e-04f, 2.87169055e-04f, 2.73915388e-04f, + 2.58278031e-04f, 2.40399328e-04f, 2.20409753e-04f, 1.98478187e-04f, + 1.74739784e-04f, 1.49434544e-04f, 1.22685308e-04f, 9.47715675e-05f, + 6.58537103e-05f, 3.61749072e-05f, 5.97746474e-06f, -2.45162183e-05f, + -5.50707960e-05f, -8.54546076e-05f, -1.15437977e-04f, -1.44752181e-04f, + -1.73217230e-04f, -2.00586450e-04f, -2.26666528e-04f, -2.51224767e-04f, + -2.74099533e-04f, -2.95109231e-04f, -3.14079824e-04f, -3.30869553e-04f, + -3.45369337e-04f, -3.57422987e-04f, -3.66953155e-04f, -3.73911775e-04f, + -3.78220230e-04f, -3.79850587e-04f, -3.78754989e-04f, -3.74987358e-04f, + -3.68568872e-04f, -3.59511330e-04f, -3.47888205e-04f, -3.33821666e-04f, + -3.17386918e-04f, -2.98721551e-04f, -2.77916847e-04f, -2.55178573e-04f, + -2.30688519e-04f, -2.04596855e-04f, -1.77101686e-04f, -1.48416144e-04f, + -1.18791903e-04f, -8.83978597e-05f, -5.74821966e-05f, -2.62868833e-05f, + 4.92815189e-06f, 3.59504591e-05f, 6.65434569e-05f, 9.64155022e-05f, + 1.25420450e-04f, 1.53244448e-04f, 1.79726500e-04f, 2.04644719e-04f, + 2.27732573e-04f, 2.48876343e-04f, 2.67856021e-04f, 2.84480504e-04f, + 2.98654254e-04f, 3.10195638e-04f, 3.18997467e-04f, 3.24986682e-04f, + 3.28010014e-04f, 3.28075276e-04f, 3.25081644e-04f, 3.18989006e-04f, + 3.09862065e-04f, 2.97618732e-04f, 2.82358452e-04f, 2.64078432e-04f, + 2.42867446e-04f, 2.18818442e-04f, 1.92028457e-04f, 1.62575887e-04f, + 1.30632731e-04f, 9.63241083e-05f, 5.98270577e-05f, 2.13018198e-05f, + -1.90650636e-05f, -6.10476572e-05f, -1.04504009e-04f, -1.49141212e-04f, + -1.94818380e-04f, -2.41269356e-04f, -2.88284124e-04f, -3.35636339e-04f, + -3.83078353e-04f, -4.30375313e-04f, -4.77325331e-04f, -5.23684425e-04f, + -5.69213416e-04f, -6.13776473e-04f, -6.57085679e-04f, -6.98973293e-04f, + -7.39240301e-04f, -7.77721569e-04f, -8.14237506e-04f, -8.48630685e-04f, + -8.80760950e-04f, -9.10481345e-04f, -9.37672148e-04f, -9.62299433e-04f, + -9.84157343e-04f, -1.00327325e-03f, -1.01946138e-03f, -1.03289356e-03f, + -1.04338135e-03f, -1.05093085e-03f, -1.05547810e-03f, -1.05718776e-03f, + -1.05596199e-03f, -1.05199493e-03f, -1.04512596e-03f, -1.03564457e-03f, + -1.02347745e-03f, -1.00881749e-03f, -9.91651851e-04f, -9.72214477e-04f, + -9.50589764e-04f, -9.26823063e-04f, -9.01169667e-04f, -8.73693053e-04f, + -8.44651495e-04f, -8.14015623e-04f, -7.82096657e-04f, -7.48978970e-04f, + -7.14918806e-04f, -6.79915120e-04f, -6.44197160e-04f, -6.07973052e-04f, + -5.71340571e-04f, -5.34494985e-04f, -4.97625899e-04f, -4.60763150e-04f, + -4.24154408e-04f, -3.87781959e-04f, -3.51977273e-04f, -3.16708720e-04f, + -2.82209173e-04f, -2.48453876e-04f, -2.15649909e-04f, -1.83919068e-04f, + -1.53211006e-04f, -1.23682813e-04f, -9.53777996e-05f, -6.83598308e-05f, + -4.27405102e-05f, -1.84644678e-05f, 4.40560628e-06f, 2.57526124e-05f, + 4.57070844e-05f, 6.42470547e-05f, 8.12438667e-05f, 9.67784543e-05f, + 1.10883532e-04f, 1.23557473e-04f, 1.34866254e-04f, 1.44751012e-04f, + 1.53323607e-04f, 1.60602540e-04f, 1.66572422e-04f, 1.71555319e-04f, + 1.75228695e-04f, 1.77785942e-04f, 1.79287868e-04f, 1.80066421e-04f, + 1.79856554e-04f, 1.78600745e-04f, 1.76640402e-04f, 1.74149693e-04f, + 1.70998956e-04f, 1.67101361e-04f, 1.62736605e-04f, 1.57955365e-04f, + 1.52783832e-04f, 1.47235562e-04f, 1.41440247e-04f, 1.35411748e-04f, + 1.29174003e-04f, 1.22883471e-04f, 1.16507352e-04f, 1.10062224e-04f, + 1.03554135e-04f, 9.72109292e-05f, 9.09057438e-05f, 8.47069282e-05f, + 7.86275439e-05f, 7.27295443e-05f, 6.70460602e-05f, 6.14870567e-05f, + 5.62358268e-05f, 5.11680423e-05f, 4.63971100e-05f, 4.18604282e-05f, + 3.75581730e-05f, 3.34956971e-05f, 2.95613583e-05f, 2.62367667e-05f, + 2.30173784e-05f, 1.99216834e-05f, 1.73717570e-05f, 1.47187235e-05f, + 1.23944987e-05f, 1.06041654e-05f, 8.75464375e-06f, 1.94324625e-05f, }; // Minimum-phase equiripple FIR lowpass diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index b2a494230b..b25df633c0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -33,6 +33,10 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : { // SkeletonModels, and by extention Avatars, use Dual Quaternion skinning. _useDualQuaternionSkinning = true; + + // Avatars all cast shadow + _canCastShadow = true; + assert(_owningAvatar); } diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index a765e66bbc..0407c8508c 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -33,65 +33,8 @@ #include "FBXBaker.h" -#ifdef _WIN32 -#pragma warning( push ) -#pragma warning( disable : 4267 ) -#endif - -#include -#include - -#ifdef _WIN32 -#pragma warning( pop ) -#endif - - -FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, - const QString& bakedOutputDir, const QString& originalOutputDir) : - _fbxURL(fbxURL), - _bakedOutputDir(bakedOutputDir), - _originalOutputDir(originalOutputDir), - _textureThreadGetter(textureThreadGetter) -{ - -} - -FBXBaker::~FBXBaker() { - if (_tempDir.exists()) { - if (!_tempDir.remove(_originalFBXFilePath)) { - qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalFBXFilePath; - } - if (!_tempDir.rmdir(".")) { - qCWarning(model_baking) << "Failed to remove temporary directory:" << _tempDir; - } - } -} - -void FBXBaker::abort() { - Baker::abort(); - - // tell our underlying TextureBaker instances to abort - // the FBXBaker will wait until all are aborted before emitting its own abort signal - for (auto& textureBaker : _bakingTextures) { - textureBaker->abort(); - } -} - -void FBXBaker::bake() { - qDebug() << "FBXBaker" << _fbxURL << "bake starting"; - - auto tempDir = PathUtils::generateTemporaryDir(); - - if (tempDir.isEmpty()) { - handleError("Failed to create a temporary directory."); - return; - } - - _tempDir = tempDir; - - _originalFBXFilePath = _tempDir.filePath(_fbxURL.fileName()); - qDebug() << "Made temporary dir " << _tempDir; - qDebug() << "Origin file path: " << _originalFBXFilePath; +void FBXBaker::bake() { + qDebug() << "FBXBaker" << _modelURL << "bake starting"; // setup the output folder for the results of this bake setupOutputFolder(); @@ -152,7 +95,7 @@ void FBXBaker::setupOutputFolder() { } // attempt to make the output folder if (!QDir().mkpath(_originalOutputDir)) { - handleError("Failed to create FBX output folder " + _bakedOutputDir); + handleError("Failed to create FBX output folder " + _originalOutputDir); return; } } @@ -160,25 +103,25 @@ void FBXBaker::setupOutputFolder() { void FBXBaker::loadSourceFBX() { // check if the FBX is local or first needs to be downloaded - if (_fbxURL.isLocalFile()) { + if (_modelURL.isLocalFile()) { // load up the local file - QFile localFBX { _fbxURL.toLocalFile() }; + QFile localFBX { _modelURL.toLocalFile() }; - qDebug() << "Local file url: " << _fbxURL << _fbxURL.toString() << _fbxURL.toLocalFile() << ", copying to: " << _originalFBXFilePath; + qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath; if (!localFBX.exists()) { //QMessageBox::warning(this, "Could not find " + _fbxURL.toString(), ""); - handleError("Could not find " + _fbxURL.toString()); + handleError("Could not find " + _modelURL.toString()); return; } // make a copy in the output folder if (!_originalOutputDir.isEmpty()) { - qDebug() << "Copying to: " << _originalOutputDir << "/" << _fbxURL.fileName(); - localFBX.copy(_originalOutputDir + "/" + _fbxURL.fileName()); + qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName(); + localFBX.copy(_originalOutputDir + "/" + _modelURL.fileName()); } - localFBX.copy(_originalFBXFilePath); + localFBX.copy(_originalModelFilePath); // emit our signal to start the import of the FBX source copy emit sourceCopyReadyToLoad(); @@ -193,9 +136,9 @@ void FBXBaker::loadSourceFBX() { networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - networkRequest.setUrl(_fbxURL); + networkRequest.setUrl(_modelURL); - qCDebug(model_baking) << "Downloading" << _fbxURL; + qCDebug(model_baking) << "Downloading" << _modelURL; auto networkReply = networkAccessManager.get(networkRequest); connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); @@ -206,20 +149,20 @@ void FBXBaker::handleFBXNetworkReply() { auto requestReply = qobject_cast(sender()); if (requestReply->error() == QNetworkReply::NoError) { - qCDebug(model_baking) << "Downloaded" << _fbxURL; + qCDebug(model_baking) << "Downloaded" << _modelURL; // grab the contents of the reply and make a copy in the output folder - QFile copyOfOriginal(_originalFBXFilePath); + QFile copyOfOriginal(_originalModelFilePath); - qDebug(model_baking) << "Writing copy of original FBX to" << _originalFBXFilePath << copyOfOriginal.fileName(); + qDebug(model_baking) << "Writing copy of original FBX to" << _originalModelFilePath << copyOfOriginal.fileName(); if (!copyOfOriginal.open(QIODevice::WriteOnly)) { // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made - handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to open " + _originalFBXFilePath + ")"); + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")"); return; } if (copyOfOriginal.write(requestReply->readAll()) == -1) { - handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to write)"); + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)"); return; } @@ -227,98 +170,32 @@ void FBXBaker::handleFBXNetworkReply() { copyOfOriginal.close(); if (!_originalOutputDir.isEmpty()) { - copyOfOriginal.copy(_originalOutputDir + "/" + _fbxURL.fileName()); + copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName()); } // emit our signal to start the import of the FBX source copy emit sourceCopyReadyToLoad(); } else { // add an error to our list stating that the FBX could not be downloaded - handleError("Failed to download " + _fbxURL.toString()); + handleError("Failed to download " + _modelURL.toString()); } } void FBXBaker::importScene() { - qDebug() << "file path: " << _originalFBXFilePath.toLocal8Bit().data() << QDir(_originalFBXFilePath).exists(); + qDebug() << "file path: " << _originalModelFilePath.toLocal8Bit().data() << QDir(_originalModelFilePath).exists(); - QFile fbxFile(_originalFBXFilePath); + QFile fbxFile(_originalModelFilePath); if (!fbxFile.open(QIODevice::ReadOnly)) { - handleError("Error opening " + _originalFBXFilePath + " for reading"); + handleError("Error opening " + _originalModelFilePath + " for reading"); return; } FBXReader reader; - qCDebug(model_baking) << "Parsing" << _fbxURL; + qCDebug(model_baking) << "Parsing" << _modelURL; _rootNode = reader._rootNode = reader.parseFBX(&fbxFile); - _geometry = reader.extractFBXGeometry({}, _fbxURL.toString()); - _textureContent = reader._textureContent; -} - -QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { - auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - - if (texturePath.startsWith(fbxPath)) { - // texture path is a child of the FBX path, return the texture path without the fbx path - return texturePath.mid(fbxPath.length()); - } else { - // the texture path was not a child of the FBX path, return the empty string - return ""; - } -} - -QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { - // first make sure we have a unique base name for this texture - // in case another texture referenced by this model has the same base name - auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - - QString bakedTextureFileName { textureFileInfo.completeBaseName() }; - - if (nameMatches > 0) { - // there are already nameMatches texture with this name - // append - and that number to our baked texture file name so that it is unique - bakedTextureFileName += "-" + QString::number(nameMatches); - } - - bakedTextureFileName += BAKED_TEXTURE_EXT; - - // increment the number of name matches - ++nameMatches; - - return bakedTextureFileName; -} - -QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) { - - QUrl urlToTexture; - - auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); - - if (isEmbedded) { - urlToTexture = _fbxURL.toString() + "/" + apparentRelativePath.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 FBX - if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { - // the absolute path we ran into for the texture in the FBX exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the FBX to match the behaviour of interface - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); - } - } - } - - return urlToTexture; + _geometry = reader.extractFBXGeometry({}, _modelURL.toString()); + _textureContentMap = reader._textureContent; } void FBXBaker::rewriteAndBakeSceneModels() { @@ -344,176 +221,25 @@ void FBXBaker::rewriteAndBakeSceneModels() { // TODO Pull this out of _geometry instead so we don't have to reprocess it auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false); - auto& mesh = extractedMesh.mesh; - - if (mesh.wasCompressed) { - handleError("Cannot re-bake a file that contains compressed mesh"); - return; - } - - Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); - Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); - Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); - - int64_t numTriangles { 0 }; - for (auto& part : mesh.parts) { - if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { - handleWarning("Found a mesh part with invalid index data, skipping"); + + // Callback to get MaterialID + GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) { + return extractedMesh.partMaterialTextures[partIndex].first; + }; + + // Compress mesh information and store in dracoMeshNode + FBXNode dracoMeshNode; + bool success = compressMesh(extractedMesh.mesh, hasDeformers, dracoMeshNode, materialIDcallback); + + // if bake fails - return, if there were errors and continue, if there were warnings. + if (!success) { + if (hasErrors()) { + return; + } else if (hasWarnings()) { continue; } - numTriangles += part.quadTrianglesIndices.size() / 3; - numTriangles += part.triangleIndices.size() / 3; } - - if (numTriangles == 0) { - continue; - } - - draco::TriangleSoupMeshBuilder meshBuilder; - - meshBuilder.Start(numTriangles); - - bool hasNormals { mesh.normals.size() > 0 }; - bool hasColors { mesh.colors.size() > 0 }; - bool hasTexCoords { mesh.texCoords.size() > 0 }; - bool hasTexCoords1 { mesh.texCoords1.size() > 0 }; - bool hasPerFaceMaterials { mesh.parts.size() > 1 - || extractedMesh.partMaterialTextures[0].first != 0 }; - bool needsOriginalIndices { hasDeformers }; - - int normalsAttributeID { -1 }; - int colorsAttributeID { -1 }; - int texCoordsAttributeID { -1 }; - int texCoords1AttributeID { -1 }; - int faceMaterialAttributeID { -1 }; - int originalIndexAttributeID { -1 }; - - const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, - 3, draco::DT_FLOAT32); - if (needsOriginalIndices) { - originalIndexAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, - 1, draco::DT_INT32); - } - - if (hasNormals) { - normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, - 3, draco::DT_FLOAT32); - } - if (hasColors) { - colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, - 3, draco::DT_FLOAT32); - } - if (hasTexCoords) { - texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, - 2, draco::DT_FLOAT32); - } - if (hasTexCoords1) { - texCoords1AttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, - 2, draco::DT_FLOAT32); - } - if (hasPerFaceMaterials) { - faceMaterialAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, - 1, draco::DT_UINT16); - } - - - auto partIndex = 0; - draco::FaceIndex face; - for (auto& part : mesh.parts) { - const auto& matTex = extractedMesh.partMaterialTextures[partIndex]; - uint16_t materialID = matTex.first; - - auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { - int32_t idx0 = indices[index]; - int32_t idx1 = indices[index + 1]; - int32_t idx2 = indices[index + 2]; - - if (hasPerFaceMaterials) { - meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); - } - - meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, - &mesh.vertices[idx0], &mesh.vertices[idx1], - &mesh.vertices[idx2]); - - if (needsOriginalIndices) { - meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, - &mesh.originalIndices[idx0], - &mesh.originalIndices[idx1], - &mesh.originalIndices[idx2]); - } - if (hasNormals) { - meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, - &mesh.normals[idx0], &mesh.normals[idx1], - &mesh.normals[idx2]); - } - if (hasColors) { - meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, - &mesh.colors[idx0], &mesh.colors[idx1], - &mesh.colors[idx2]); - } - if (hasTexCoords) { - meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, - &mesh.texCoords[idx0], &mesh.texCoords[idx1], - &mesh.texCoords[idx2]); - } - if (hasTexCoords1) { - meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, - &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], - &mesh.texCoords1[idx2]); - } - }; - - for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { - addFace(part.quadTrianglesIndices, i, face++); - } - - for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { - addFace(part.triangleIndices, i, face++); - } - - partIndex++; - } - - auto dracoMesh = meshBuilder.Finalize(); - - if (!dracoMesh) { - handleWarning("Failed to finalize the baking of a draco Geometry node"); - continue; - } - - // we need to modify unique attribute IDs for custom attributes - // so the attributes are easily retrievable on the other side - if (hasPerFaceMaterials) { - dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); - } - - if (hasTexCoords1) { - dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); - } - - if (needsOriginalIndices) { - dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); - } - - draco::Encoder encoder; - - encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); - encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); - encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); - encoder.SetSpeedOptions(0, 5); - - draco::EncoderBuffer buffer; - encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); - - FBXNode dracoMeshNode; - dracoMeshNode.name = "DracoMesh"; - auto value = QVariant::fromValue(QByteArray(buffer.data(), (int) buffer.size())); - dracoMeshNode.properties.append(value); - + objectChild.children.push_back(dracoMeshNode); static const std::vector nodeNamesToDelete { @@ -590,69 +316,25 @@ void FBXBaker::rewriteAndBakeSceneTextures() { for (FBXNode& textureChild : object->children) { if (textureChild.name == "RelativeFilename") { + QString fbxTextureFileName { 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]; - // use QFileInfo to easily split up the existing texture filename into its components - QString fbxTextureFileName { textureChild.properties.at(0).toByteArray() }; - QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; + // Compress the texture information and return the new filename to be added into the FBX scene + auto bakedTextureFile = compressTexture(fbxTextureFileName, textureType); - if (hasErrors()) { - return; - } - - if (textureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) { - // re-baking an FBX that already references baked textures is a fail - // so we add an error and return from here - handleError("Cannot re-bake a file that references compressed textures"); - - return; - } - - if (!image::getSupportedFormats().contains(textureFileInfo.suffix())) { - // this is a texture format we don't bake, skip it - handleWarning(fbxTextureFileName + " is not a bakeable texture format"); - continue; - } - - // make sure this texture points to something and isn't one we've already re-mapped - if (!textureFileInfo.filePath().isEmpty()) { - // check if this was an embedded texture we have already have in-memory content for - auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); - - // figure out the URL to this texture, embedded or external - auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName, - !textureContent.isNull()); - - QString bakedTextureFileName; - if (_remappedTexturePaths.contains(urlToTexture)) { - bakedTextureFileName = _remappedTexturePaths[urlToTexture]; - } else { - // 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 - bakedTextureFileName = createBakedTextureFileName(textureFileInfo); - _remappedTexturePaths[urlToTexture] = bakedTextureFileName; - } - - qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName - << "to" << bakedTextureFileName; - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + bakedTextureFileName - }; - - // write the new filename into the FBX scene - textureChild.properties[0] = bakedTextureFileName.toLocal8Bit(); - - if (!_bakingTextures.contains(urlToTexture)) { - _outputFiles.push_back(bakedTextureFilePath); - - // grab the ID for this texture so we can figure out the - // texture type from the loaded materials - QString textureID { object->properties[0].toByteArray() }; - auto textureType = textureTypes[textureID]; - - // bake this texture asynchronously - bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); + // 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; } } } @@ -671,172 +353,26 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } -void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, - 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(textureURL, textureType, outputDir, bakedFilename, textureContent), - &TextureBaker::deleteLater - }; - - // make sure we hear when the baking texture is done or aborted - connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); - connect(bakingTexture.data(), &TextureBaker::aborted, this, &FBXBaker::handleAbortedTexture); - - // keep a shared pointer to the baking texture - _bakingTextures.insert(textureURL, bakingTexture); - - // start baking the texture on one of our available worker threads - bakingTexture->moveToThread(_textureThreadGetter()); - QMetaObject::invokeMethod(bakingTexture.data(), "bake"); -} - -void FBXBaker::handleBakedTexture() { - TextureBaker* bakedTexture = qobject_cast(sender()); - - // 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 FBX - // since that is the fake URL we provide when baking external textures - - if (!_fbxURL.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original FBX - - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, 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" << _fbxURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _fbxURL.toString()); - return; - } - } - } - - - // now that this texture has been baked and handled, we can remove that TextureBaker from our hash - _bakingTextures.remove(bakedTexture->getTextureURL()); - - 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()); - - // 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()); - - checkIfTexturesFinished(); - } - } -} - -void FBXBaker::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()); - - if (bakedTexture) { - _bakingTextures.remove(bakedTexture->getTextureURL()); - } - - // 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(); -} - void FBXBaker::exportScene() { // save the relative path to this FBX inside our passed output folder - auto fileName = _fbxURL.fileName(); + auto fileName = _modelURL.fileName(); auto baseName = fileName.left(fileName.lastIndexOf('.')); auto bakedFilename = baseName + BAKED_FBX_EXTENSION; - _bakedFBXFilePath = _bakedOutputDir + "/" + bakedFilename; + _bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename; auto fbxData = FBXWriter::encodeFBX(_rootNode); - QFile bakedFile(_bakedFBXFilePath); + QFile bakedFile(_bakedModelFilePath); if (!bakedFile.open(QIODevice::WriteOnly)) { - handleError("Error opening " + _bakedFBXFilePath + " for writing"); + handleError("Error opening " + _bakedModelFilePath + " for writing"); return; } bakedFile.write(fbxData); - _outputFiles.push_back(_bakedFBXFilePath); + _outputFiles.push_back(_bakedModelFilePath); - qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << _bakedFBXFilePath; -} - -void FBXBaker::checkIfTexturesFinished() { - // check if we're done everything we need to do for this FBX - // 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 finsihed" << _fbxURL; - - setIsFinished(true); - } - } -} - -void FBXBaker::setWasAborted(bool wasAborted) { - if (wasAborted != _wasAborted.load()) { - Baker::setWasAborted(wasAborted); - - if (wasAborted) { - qCDebug(model_baking) << "Aborted baking" << _fbxURL; - } - } + qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath; } diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index a6034ee2b7..2888a60f73 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -19,7 +19,7 @@ #include "Baker.h" #include "TextureBaker.h" - +#include "ModelBaker.h" #include "ModelBakingLoggingCategory.h" #include @@ -30,21 +30,13 @@ static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; using TextureBakerThreadGetter = std::function; -class FBXBaker : public Baker { +class FBXBaker : public ModelBaker { Q_OBJECT public: - FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, - const QString& bakedOutputDir, const QString& originalOutputDir = ""); - ~FBXBaker() override; - - QUrl getFBXUrl() const { return _fbxURL; } - QString getBakedFBXFilePath() const { return _bakedFBXFilePath; } - - virtual void setWasAborted(bool wasAborted) override; + using ModelBaker::ModelBaker; public slots: virtual void bake() override; - virtual void abort() override; signals: void sourceCopyReadyToLoad(); @@ -52,8 +44,6 @@ signals: private slots: void bakeSourceCopy(); void handleFBXNetworkReply(); - void handleBakedTexture(); - void handleAbortedTexture(); private: void setupOutputFolder(); @@ -64,38 +54,12 @@ private: void rewriteAndBakeSceneModels(); void rewriteAndBakeSceneTextures(); void exportScene(); - void removeEmbeddedMediaFolder(); - - void checkIfTexturesFinished(); - - QString createBakedTextureFileName(const QFileInfo& textureFileInfo); - QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false); - - void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir, - const QString& bakedFilename, const QByteArray& textureContent = QByteArray()); - - QUrl _fbxURL; FBXNode _rootNode; FBXGeometry* _geometry; - QHash _textureContent; - - QString _bakedFBXFilePath; - - QString _bakedOutputDir; - - // If set, the original FBX and textures will also be copied here - QString _originalOutputDir; - - QDir _tempDir; - QString _originalFBXFilePath; - - QMultiHash> _bakingTextures; QHash _textureNameMatchCount; QHash _remappedTexturePaths; - TextureBakerThreadGetter _textureThreadGetter; - bool _pendingErrorEmission { false }; }; diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp new file mode 100644 index 0000000000..16a0c89c7f --- /dev/null +++ b/libraries/baking/src/ModelBaker.cpp @@ -0,0 +1,521 @@ +// +// ModelBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelBaker.h" + +#include + +#include +#include + +#ifdef _WIN32 +#pragma warning( push ) +#pragma warning( disable : 4267 ) +#endif + +#include +#include + +#ifdef _WIN32 +#pragma warning( pop ) +#endif + +ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, + const QString& bakedOutputDirectory, const QString& originalOutputDirectory) : + _modelURL(inputModelURL), + _bakedOutputDir(bakedOutputDirectory), + _originalOutputDir(originalOutputDirectory), + _textureThreadGetter(inputTextureThreadGetter) +{ + auto tempDir = PathUtils::generateTemporaryDir(); + + if (tempDir.isEmpty()) { + handleError("Failed to create a temporary directory."); + return; + } + + _modelTempDir = tempDir; + + _originalModelFilePath = _modelTempDir.filePath(_modelURL.fileName()); + qDebug() << "Made temporary dir " << _modelTempDir; + qDebug() << "Origin file path: " << _originalModelFilePath; + +} + +ModelBaker::~ModelBaker() { + if (_modelTempDir.exists()) { + if (!_modelTempDir.remove(_originalModelFilePath)) { + qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalModelFilePath; + } + if (!_modelTempDir.rmdir(".")) { + qCWarning(model_baking) << "Failed to remove temporary directory:" << _modelTempDir; + } + } +} + +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(); + } +} + +bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { + if (mesh.wasCompressed) { + handleError("Cannot re-bake a file that contains compressed mesh"); + return false; + } + + Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); + Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); + Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); + + int64_t numTriangles{ 0 }; + for (auto& part : mesh.parts) { + if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { + handleWarning("Found a mesh part with invalid index data, skipping"); + continue; + } + numTriangles += part.quadTrianglesIndices.size() / 3; + numTriangles += part.triangleIndices.size() / 3; + } + + if (numTriangles == 0) { + return false; + } + + draco::TriangleSoupMeshBuilder meshBuilder; + + meshBuilder.Start(numTriangles); + + bool hasNormals{ mesh.normals.size() > 0 }; + bool hasColors{ mesh.colors.size() > 0 }; + bool hasTexCoords{ mesh.texCoords.size() > 0 }; + bool hasTexCoords1{ mesh.texCoords1.size() > 0 }; + bool hasPerFaceMaterials = (materialIDCallback) ? (mesh.parts.size() > 1 || materialIDCallback(0) != 0 ) : true; + bool needsOriginalIndices{ hasDeformers }; + + int normalsAttributeID { -1 }; + int colorsAttributeID { -1 }; + int texCoordsAttributeID { -1 }; + int texCoords1AttributeID { -1 }; + int faceMaterialAttributeID { -1 }; + int originalIndexAttributeID { -1 }; + + const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, + 3, draco::DT_FLOAT32); + if (needsOriginalIndices) { + originalIndexAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, + 1, draco::DT_INT32); + } + + if (hasNormals) { + normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, + 3, draco::DT_FLOAT32); + } + if (hasColors) { + colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, + 3, draco::DT_FLOAT32); + } + if (hasTexCoords) { + texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, + 2, draco::DT_FLOAT32); + } + if (hasTexCoords1) { + texCoords1AttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, + 2, draco::DT_FLOAT32); + } + if (hasPerFaceMaterials) { + faceMaterialAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, + 1, draco::DT_UINT16); + } + + auto partIndex = 0; + draco::FaceIndex face; + uint16_t materialID; + + for (auto& part : mesh.parts) { + materialID = (materialIDCallback) ? materialIDCallback(partIndex) : partIndex; + + auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { + int32_t idx0 = indices[index]; + int32_t idx1 = indices[index + 1]; + int32_t idx2 = indices[index + 2]; + + if (hasPerFaceMaterials) { + meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); + } + + meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, + &mesh.vertices[idx0], &mesh.vertices[idx1], + &mesh.vertices[idx2]); + + if (needsOriginalIndices) { + meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, + &mesh.originalIndices[idx0], + &mesh.originalIndices[idx1], + &mesh.originalIndices[idx2]); + } + if (hasNormals) { + meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, + &mesh.normals[idx0], &mesh.normals[idx1], + &mesh.normals[idx2]); + } + if (hasColors) { + meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, + &mesh.colors[idx0], &mesh.colors[idx1], + &mesh.colors[idx2]); + } + if (hasTexCoords) { + meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, + &mesh.texCoords[idx0], &mesh.texCoords[idx1], + &mesh.texCoords[idx2]); + } + if (hasTexCoords1) { + meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, + &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], + &mesh.texCoords1[idx2]); + } + }; + + for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { + addFace(part.quadTrianglesIndices, i, face++); + } + + for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { + addFace(part.triangleIndices, i, face++); + } + + partIndex++; + } + + auto dracoMesh = meshBuilder.Finalize(); + + if (!dracoMesh) { + handleWarning("Failed to finalize the baking of a draco Geometry node"); + return false; + } + + // we need to modify unique attribute IDs for custom attributes + // so the attributes are easily retrievable on the other side + if (hasPerFaceMaterials) { + dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); + } + + if (hasTexCoords1) { + dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); + } + + if (needsOriginalIndices) { + dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); + } + + draco::Encoder encoder; + + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); + encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); + encoder.SetSpeedOptions(0, 5); + + draco::EncoderBuffer buffer; + encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); + + FBXNode dracoNode; + dracoNode.name = "DracoMesh"; + auto value = QVariant::fromValue(QByteArray(buffer.data(), (int)buffer.size())); + dracoNode.properties.append(value); + + dracoMeshNode = dracoNode; + // Mesh compression successful return true + return true; +} + +QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) { + + QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") }; + + if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_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, modelTextureFileName, !textureContent.isNull()); + + QString bakedTextureFileName; + if (_remappedTexturePaths.contains(urlToTexture)) { + bakedTextureFileName = _remappedTexturePaths[urlToTexture]; + } else { + // 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 + bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo); + _remappedTexturePaths[urlToTexture] = bakedTextureFileName; + } + + qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName + << "to" << bakedTextureFileName; + + QString bakedTextureFilePath{ + _bakedOutputDir + "/" + bakedTextureFileName + }; + + textureChild = bakedTextureFileName; + + if (!_bakingTextures.contains(urlToTexture)) { + _outputFiles.push_back(bakedTextureFilePath); + + // bake this texture asynchronously + bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); + } + } + + return textureChild; +} + +void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, + 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(textureURL, textureType, 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(textureURL, 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()); + + 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()); + + // 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()); + + 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()); + } + + // 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, QString relativeFileName, bool isEmbedded) { + QUrl urlToTexture; + + // use QFileInfo to easily split up the existing texture filename into its components + auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); + + if (isEmbedded) { + urlToTexture = _modelURL.toString() + "/" + apparentRelativePath.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() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the model exists on this machine + // so use that file + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.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(apparentRelativePath.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; + + setIsFinished(true); + } + } +} + +QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { + // first make sure we have a unique base name for this texture + // in case another texture referenced by this model has the same base name + auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + + QString bakedTextureFileName{ textureFileInfo.completeBaseName() }; + + if (nameMatches > 0) { + // there are already nameMatches texture with this name + // append - and that number to our baked texture file name so that it is unique + bakedTextureFileName += "-" + QString::number(nameMatches); + } + + bakedTextureFileName += BAKED_TEXTURE_EXT; + + // increment the number of name matches + ++nameMatches; + + return bakedTextureFileName; +} + +void ModelBaker::setWasAborted(bool wasAborted) { + if (wasAborted != _wasAborted.load()) { + Baker::setWasAborted(wasAborted); + + if (wasAborted) { + qCDebug(model_baking) << "Aborted baking" << _modelURL; + } + } +} diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h new file mode 100644 index 0000000000..6fd529af92 --- /dev/null +++ b/libraries/baking/src/ModelBaker.h @@ -0,0 +1,79 @@ +// +// ModelBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelBaker_h +#define hifi_ModelBaker_h + +#include +#include +#include +#include + +#include "Baker.h" +#include "TextureBaker.h" + +#include "ModelBakingLoggingCategory.h" + +#include + +#include + +using TextureBakerThreadGetter = std::function; +using GetMaterialIDCallback = std::function ; + +class ModelBaker : public Baker { + Q_OBJECT + +public: + ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, + const QString& bakedOutputDirectory, const QString& originalOutputDirectory = ""); + virtual ~ModelBaker(); + + bool compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); + QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); + virtual void setWasAborted(bool wasAborted) override; + + QUrl getModelURL() const { return _modelURL; } + QString getBakedModelFilePath() const { return _bakedModelFilePath; } + +public slots: + virtual void abort() override; + +protected: + void checkIfTexturesFinished(); + + QHash _textureContentMap; + QUrl _modelURL; + QString _bakedOutputDir; + QString _originalOutputDir; + QString _bakedModelFilePath; + QDir _modelTempDir; + QString _originalModelFilePath; + +private slots: + void handleBakedTexture(); + void handleAbortedTexture(); + +private: + QString createBakedTextureFileName(const QFileInfo & textureFileInfo); + QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false); + void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir, + const QString & bakedFilename, const QByteArray & textureContent); + QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL); + + TextureBakerThreadGetter _textureThreadGetter; + QMultiHash> _bakingTextures; + QHash _textureNameMatchCount; + QHash _remappedTexturePaths; + bool _pendingErrorEmission{ false }; +}; + +#endif // hifi_ModelBaker_h diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp new file mode 100644 index 0000000000..85771ff2e3 --- /dev/null +++ b/libraries/baking/src/OBJBaker.cpp @@ -0,0 +1,404 @@ +// +// OBJBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "OBJBaker.h" +#include "OBJReader.h" +#include "FBXWriter.h" + +const double UNIT_SCALE_FACTOR = 100.0; +const QByteArray PROPERTIES70_NODE_NAME = "Properties70"; +const QByteArray P_NODE_NAME = "P"; +const QByteArray C_NODE_NAME = "C"; +const QByteArray FBX_HEADER_EXTENSION = "FBXHeaderExtension"; +const QByteArray GLOBAL_SETTINGS_NODE_NAME = "GlobalSettings"; +const QByteArray OBJECTS_NODE_NAME = "Objects"; +const QByteArray GEOMETRY_NODE_NAME = "Geometry"; +const QByteArray MODEL_NODE_NAME = "Model"; +const QByteArray MATERIAL_NODE_NAME = "Material"; +const QByteArray TEXTURE_NODE_NAME = "Texture"; +const QByteArray TEXTURENAME_NODE_NAME = "TextureName"; +const QByteArray RELATIVEFILENAME_NODE_NAME = "RelativeFilename"; +const QByteArray CONNECTIONS_NODE_NAME = "Connections"; +const QByteArray CONNECTIONS_NODE_PROPERTY = "OO"; +const QByteArray CONNECTIONS_NODE_PROPERTY_1 = "OP"; +const QByteArray MESH = "Mesh"; + +void OBJBaker::bake() { + qDebug() << "OBJBaker" << _modelURL << "bake starting"; + + // trigger bakeOBJ once OBJ is loaded + connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::bakeOBJ); + + // make a local copy of the OBJ + loadOBJ(); +} + +void OBJBaker::loadOBJ() { + if (!QDir().mkpath(_bakedOutputDir)) { + handleError("Failed to create baked OBJ output folder " + _bakedOutputDir); + return; + } + + if (!QDir().mkpath(_originalOutputDir)) { + handleError("Failed to create original OBJ output folder " + _originalOutputDir); + return; + } + + // check if the OBJ is local or it needs to be downloaded + if (_modelURL.isLocalFile()) { + // loading the local OBJ + QFile localOBJ { _modelURL.toLocalFile() }; + + qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath; + + if (!localOBJ.exists()) { + handleError("Could not find " + _modelURL.toString()); + return; + } + + // make a copy in the output folder + if (!_originalOutputDir.isEmpty()) { + qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName(); + localOBJ.copy(_originalOutputDir + "/" + _modelURL.fileName()); + } + + localOBJ.copy(_originalModelFilePath); + + // local OBJ is loaded emit signal to trigger its baking + emit OBJLoaded(); + } else { + // OBJ is remote, start download + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + + // setup the request to follow re-directs and always hit the network + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + networkRequest.setUrl(_modelURL); + + qCDebug(model_baking) << "Downloading" << _modelURL; + auto networkReply = networkAccessManager.get(networkRequest); + + connect(networkReply, &QNetworkReply::finished, this, &OBJBaker::handleOBJNetworkReply); + } +} + +void OBJBaker::handleOBJNetworkReply() { + auto requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + qCDebug(model_baking) << "Downloaded" << _modelURL; + + // grab the contents of the reply and make a copy in the output folder + QFile copyOfOriginal(_originalModelFilePath); + + qDebug(model_baking) << "Writing copy of original obj to" << _originalModelFilePath << copyOfOriginal.fileName(); + + if (!copyOfOriginal.open(QIODevice::WriteOnly)) { + // add an error to the error list for this obj stating that a duplicate of the original obj could not be made + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")"); + return; + } + if (copyOfOriginal.write(requestReply->readAll()) == -1) { + handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)"); + return; + } + + // close that file now that we are done writing to it + copyOfOriginal.close(); + + if (!_originalOutputDir.isEmpty()) { + copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName()); + } + + // remote OBJ is loaded emit signal to trigger its baking + emit OBJLoaded(); + } else { + // add an error to our list stating that the OBJ could not be downloaded + handleError("Failed to download " + _modelURL.toString()); + } +} + +void OBJBaker::bakeOBJ() { + // Read the OBJ file + QFile objFile(_originalModelFilePath); + if (!objFile.open(QIODevice::ReadOnly)) { + handleError("Error opening " + _originalModelFilePath + " for reading"); + return; + } + + QByteArray objData = objFile.readAll(); + + bool combineParts = true; // set true so that OBJReader reads material info from material library + OBJReader reader; + auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL); + + // Write OBJ Data as FBX tree nodes + FBXNode rootNode; + createFBXNodeTree(rootNode, *geometry); + + // Serialize the resultant FBX tree + auto encodedFBX = FBXWriter::encodeFBX(rootNode); + + // Export as baked FBX + auto fileName = _modelURL.fileName(); + auto baseName = fileName.left(fileName.lastIndexOf('.')); + auto bakedFilename = baseName + ".baked.fbx"; + + _bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename; + + QFile bakedFile; + bakedFile.setFileName(_bakedModelFilePath); + if (!bakedFile.open(QIODevice::WriteOnly)) { + handleError("Error opening " + _bakedModelFilePath + " for writing"); + return; + } + + bakedFile.write(encodedFBX); + + // Export successful + _outputFiles.push_back(_bakedModelFilePath); + qCDebug(model_baking) << "Exported" << _modelURL << "to" << _bakedModelFilePath; + + checkIfTexturesFinished(); +} + +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { + // Generating FBX Header Node + FBXNode headerNode; + headerNode.name = FBX_HEADER_EXTENSION; + + // Generating global settings node + // Required for Unit Scale Factor + FBXNode globalSettingsNode; + globalSettingsNode.name = GLOBAL_SETTINGS_NODE_NAME; + + // Setting the tree hierarchy: GlobalSettings -> Properties70 -> P -> Properties + FBXNode properties70Node; + properties70Node.name = PROPERTIES70_NODE_NAME; + + FBXNode pNode; + { + pNode.name = P_NODE_NAME; + pNode.properties.append({ + "UnitScaleFactor", "double", "Number", "", + UNIT_SCALE_FACTOR + }); + } + + properties70Node.children = { pNode }; + globalSettingsNode.children = { properties70Node }; + + // Generating Object node + _objectNode.name = OBJECTS_NODE_NAME; + + // Generating Object node's child - Geometry node + FBXNode geometryNode; + geometryNode.name = GEOMETRY_NODE_NAME; + { + _geometryID = nextNodeID(); + geometryNode.properties = { + _geometryID, + GEOMETRY_NODE_NAME, + MESH + }; + } + + // Compress the mesh information and store in dracoNode + bool hasDeformers = false; // No concept of deformers for an OBJ + FBXNode dracoNode; + compressMesh(geometry.meshes[0], hasDeformers, dracoNode); + geometryNode.children.append(dracoNode); + + // Generating Object node's child - Model node + FBXNode modelNode; + modelNode.name = MODEL_NODE_NAME; + { + _modelID = nextNodeID(); + modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH }; + } + + _objectNode.children = { geometryNode, modelNode }; + + // Generating Objects node's child - Material node + auto& meshParts = geometry.meshes[0].parts; + for (auto& meshPart : meshParts) { + FBXNode materialNode; + materialNode.name = MATERIAL_NODE_NAME; + if (geometry.materials.size() == 1) { + // case when no material information is provided, OBJReader considers it as a single default material + for (auto& materialID : geometry.materials.keys()) { + setMaterialNodeProperties(materialNode, materialID, geometry); + } + } else { + setMaterialNodeProperties(materialNode, meshPart.materialID, geometry); + } + + _objectNode.children.append(materialNode); + } + + // 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; + FBXMaterial currentMaterial = geometry.materials[material]; + if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { + _textureID = nextNodeID(); + _mapTextureMaterial.emplace_back(_textureID, i); + + FBXNode textureNode; + { + textureNode.name = TEXTURE_NODE_NAME; + textureNode.properties = { _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 + FBXNode connectionsNode; + connectionsNode.name = CONNECTIONS_NODE_NAME; + + // connect Geometry to Model + FBXNode cNode; + cNode.name = C_NODE_NAME; + cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID }; + connectionsNode.children = { cNode }; + + // connect all materials to model + for (auto& materialID : _materialIDs) { + FBXNode cNode; + cNode.name = C_NODE_NAME; + 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); + } + + // Make all generated nodes children of rootNode + rootNode.children = { globalSettingsNode, _objectNode, connectionsNode }; +} + +// Set properties for material nodes +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry) { + auto materialID = nextNodeID(); + _materialIDs.push_back(materialID); + materialNode.properties = { materialID, material, MESH }; + + FBXMaterial currentMaterial = geometry.materials[material]; + + // Setting the hierarchy: Material -> Properties70 -> P -> Properties + FBXNode properties70Node; + properties70Node.name = PROPERTIES70_NODE_NAME; + + // Set diffuseColor + FBXNode pNodeDiffuseColor; + { + pNodeDiffuseColor.name = P_NODE_NAME; + pNodeDiffuseColor.properties.append({ + "DiffuseColor", "Color", "", "A", + currentMaterial.diffuseColor[0], currentMaterial.diffuseColor[1], currentMaterial.diffuseColor[2] + }); + } + properties70Node.children.append(pNodeDiffuseColor); + + // Set specularColor + FBXNode pNodeSpecularColor; + { + pNodeSpecularColor.name = P_NODE_NAME; + pNodeSpecularColor.properties.append({ + "SpecularColor", "Color", "", "A", + currentMaterial.specularColor[0], currentMaterial.specularColor[1], currentMaterial.specularColor[2] + }); + } + properties70Node.children.append(pNodeSpecularColor); + + // Set Shininess + FBXNode pNodeShininess; + { + pNodeShininess.name = P_NODE_NAME; + pNodeShininess.properties.append({ + "Shininess", "Number", "", "A", + currentMaterial.shininess + }); + } + properties70Node.children.append(pNodeShininess); + + // Set Opacity + FBXNode pNodeOpacity; + { + pNodeOpacity.name = P_NODE_NAME; + pNodeOpacity.properties.append({ + "Opacity", "Number", "", "A", + currentMaterial.opacity + }); + } + properties70Node.children.append(pNodeOpacity); + + materialNode.children.append(properties70Node); +} diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h new file mode 100644 index 0000000000..e888c7b1d8 --- /dev/null +++ b/libraries/baking/src/OBJBaker.h @@ -0,0 +1,54 @@ +// +// OBJBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OBJBaker_h +#define hifi_OBJBaker_h + +#include "Baker.h" +#include "TextureBaker.h" +#include "ModelBaker.h" + +#include "ModelBakingLoggingCategory.h" + +using TextureBakerThreadGetter = std::function; + +using NodeID = qlonglong; + +class OBJBaker : public ModelBaker { + Q_OBJECT +public: + using ModelBaker::ModelBaker; + +public slots: + virtual void bake() override; + +signals: + void OBJLoaded(); + +private slots: + void bakeOBJ(); + void handleOBJNetworkReply(); + +private: + void loadOBJ(); + void createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry); + void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry); + NodeID nextNodeID() { return _nodeID++; } + + NodeID _nodeID { 0 }; + NodeID _geometryID; + NodeID _modelID; + std::vector _materialIDs; + NodeID _textureID; + std::vector> _mapTextureMaterial; + FBXNode _objectNode; +}; +#endif // hifi_OBJBaker_h diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 56cbcdadf8..97f74fa24e 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -28,7 +28,7 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() { auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png"; auto image = QImage(iconPath); qreal dpi = getFullscreenTarget()->physicalDotsPerInch(); - _virtualPadPixelSize = dpi * 512 / 534; // 534 dpi for Pixel XL and Mate 9 Pro + _virtualPadPixelSize = dpi * VirtualPad::Manager::PIXEL_SIZE / VirtualPad::Manager::DPI; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 234818c740..de115b0554 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -90,7 +90,7 @@ public: glm::vec2 getReticleMaximumPosition() const; glm::mat4 getReticleTransform(const glm::mat4& eyePose = glm::mat4(), const glm::vec3& headPosition = glm::vec3()) const; - glm::mat4 getPoint2DTransform(const glm::vec2& point = glm::vec2(), float sizeX = 512.0f, float sizeY = 512.0f) const; + glm::mat4 getPoint2DTransform(const glm::vec2& point, float sizeX , float sizeY) const; ReticleInterface* getReticleInterface() { return _reticleInterface; } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 9bd7d89634..db9b86b9dd 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -361,7 +361,7 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - getGLBackend()->setCameraCorrection(mat4()); + getGLBackend()->setCameraCorrection(mat4(), mat4(), true); for (auto& cursorValue : _cursorsData) { auto& cursorData = cursorValue.second; @@ -692,6 +692,9 @@ void OpenGLDisplayPlugin::present() { incrementPresentCount(); if (_currentFrame) { + auto correction = getViewCorrection(); + getGLBackend()->setCameraCorrection(correction, _prevRenderView); + _prevRenderView = correction * _currentFrame->view; { withPresentThreadLock([&] { _renderRate.increment(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index bf06486095..bde7984ec0 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -118,6 +118,7 @@ protected: void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo); void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); virtual void updateFrameData(); + virtual glm::mat4 getViewCorrection() { return glm::mat4(); } void withOtherThreadContext(std::function f) const; @@ -137,6 +138,7 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; + mat4 _prevRenderView; gpu::FramebufferPointer _compositeFramebuffer; gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _mirrorHUDPipeline; diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index 6e397efbe5..40063652c8 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -7,6 +7,8 @@ // #include "DebugHmdDisplayPlugin.h" +#include + #include #include @@ -41,7 +43,15 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } bool DebugHmdDisplayPlugin::internalActivate() { + _isAutoRotateEnabled = _container->getBoolSetting("autoRotate", true); + _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), tr("Auto Rotate"), + [this](bool clicked) { + _isAutoRotateEnabled = clicked; + _container->setBoolSetting("autoRotate", _isAutoRotateEnabled); + }, true, _isAutoRotateEnabled); + _ipd = 0.0327499993f * 2.0f; + // Would be nice to know why the left and right projection matrices are slightly dissymetrical _eyeProjections[0][0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; _eyeProjections[0][1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; _eyeProjections[0][2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; @@ -50,10 +60,15 @@ bool DebugHmdDisplayPlugin::internalActivate() { _eyeProjections[1][1] = vec4{ 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; _eyeProjections[1][2] = vec4{ 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; _eyeProjections[1][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; - _eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); - _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); + // No need to do so here as this will done in Parent::internalActivate + //_eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); + //_eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, 0.0149999997, 1.0 }; _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, 0.0149999997, 1.0 }; + _eyeInverseProjections[0] = glm::inverse(_eyeProjections[0]); + _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); + _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, -0.0149999997, 1.0 }; + _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, -0.0149999997, 1.0 }; _renderTargetSize = { 3024, 1680 }; _cullingProjection = _eyeProjections[0]; // This must come after the initialization, so that the values calculated @@ -63,10 +78,13 @@ bool DebugHmdDisplayPlugin::internalActivate() { } void DebugHmdDisplayPlugin::updatePresentPose() { - float yaw = sinf(secTimestampNow()) * 0.25f; - float pitch = cosf(secTimestampNow()) * 0.25f; - // Simulates head pose latency correction - _currentPresentFrameInfo.presentPose = - glm::mat4_cast(glm::angleAxis(yaw, Vectors::UP)) * - glm::mat4_cast(glm::angleAxis(pitch, Vectors::RIGHT)); + Parent::updatePresentPose(); + if (_isAutoRotateEnabled) { + float yaw = sinf(secTimestampNow()) * 0.25f; + float pitch = cosf(secTimestampNow()) * 0.25f; + // Simulates head pose latency correction + _currentPresentFrameInfo.presentPose = + glm::mat4_cast(glm::angleAxis(yaw, Vectors::UP)) * + glm::mat4_cast(glm::angleAxis(pitch, Vectors::RIGHT)) ; + } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index cd6fdd44b9..f2b1f36419 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -28,5 +28,7 @@ protected: bool isHmdMounted() const override { return true; } bool internalActivate() override; private: + static const QString NAME; + bool _isAutoRotateEnabled{ true }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 8b255d9974..2a32a7d5ce 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -58,6 +58,18 @@ QRect HmdDisplayPlugin::getRecommendedHUDRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } +glm::mat4 HmdDisplayPlugin::getEyeToHeadTransform(Eye eye) const { + return _eyeOffsets[eye]; +} + +glm::mat4 HmdDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +glm::mat4 HmdDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { + return _cullingProjection; +} + #define DISABLE_PREVIEW_MENU_ITEM_DELAY_MS 500 bool HmdDisplayPlugin::internalActivate() { @@ -324,12 +336,14 @@ void HmdDisplayPlugin::updateFrameData() { } updatePresentPose(); +} +glm::mat4 HmdDisplayPlugin::getViewCorrection() { if (_currentFrame) { auto batchPose = _currentFrame->pose; - auto currentPose = _currentPresentFrameInfo.presentPose; - auto correction = glm::inverse(batchPose) * currentPose; - getGLBackend()->setCameraCorrection(correction); + return glm::inverse(_currentPresentFrameInfo.presentPose) * batchPose; + } else { + return glm::mat4(); } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index d2d30df093..3639952524 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -26,9 +26,9 @@ public: ~HmdDisplayPlugin(); bool isHmd() const override final { return true; } float getIPD() const override final { return _ipd; } - glm::mat4 getEyeToHeadTransform(Eye eye) const override final { return _eyeOffsets[eye]; } - glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override { return _eyeProjections[eye]; } - glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override { return _cullingProjection; } + glm::mat4 getEyeToHeadTransform(Eye eye) const override final; + glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; + glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; glm::uvec2 getRecommendedUiSize() const override final; glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; } bool isDisplayVisible() const override { return isHmdMounted(); } @@ -59,6 +59,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; + glm::mat4 getViewCorrection() override; std::array _eyeOffsets; std::array _eyeProjections; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index ae8f9ec039..cfdfb1fc21 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -101,3 +101,4 @@ void StereoDisplayPlugin::internalDeactivate() { float StereoDisplayPlugin::getRecommendedAspectRatio() const { return aspect(Parent::getRecommendedRenderSize()); } + diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h index 3b481dce97..c4205ea1db 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -26,7 +26,7 @@ public: // the IPD at the Application level, the way we now allow with HMDs. // If that becomes an issue then we'll need to break up the functionality similar // to the HMD plugins. - // virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; + //virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override; protected: virtual bool internalActivate() override; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 968c181940..e58eb540e8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -111,7 +111,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { if (!_geometryID) { _geometryID = geometryCache->allocateID(); } - geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false, false); + geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); float scale = _lineHeight / _textRenderer->getFontSize(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f7aaa43b7d..52b9364e98 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -46,6 +46,19 @@ static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { + if (urlString.isEmpty()) { + return ContentType::NoContent; + } + + const QUrl url(urlString); + if (url.scheme() == "http" || url.scheme() == "https" || + urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { + return ContentType::HtmlContent; + } + return ContentType::QmlContent; +} + WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { static std::once_flag once; std::call_once(once, [&]{ @@ -123,13 +136,45 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - withWriteLock([&] { - // This work must be done on the main thread - if (!hasWebSurface()) { - // If we couldn't create a new web surface, exit - if (!buildWebSurface(entity)) { - return; + // If the content type has changed, or the old content type was QML, we need to + // destroy the existing surface (because surfaces don't support changing the root + // object, so subsequent loads of content just overlap the existing content + bool urlChanged = false; + { + auto newSourceUrl = entity->getSourceUrl(); + auto newContentType = getContentType(newSourceUrl); + auto currentContentType = ContentType::NoContent; + withReadLock([&] { + urlChanged = _lastSourceUrl != newSourceUrl; + currentContentType = _contentType; + }); + + if (urlChanged) { + if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { + destroyWebSurface(); } + + withWriteLock([&] { + _lastSourceUrl = newSourceUrl; + _contentType = newContentType; + }); + } + } + + + withWriteLock([&] { + if (_contentType == ContentType::NoContent) { + return; + } + + // This work must be done on the main thread + // If we couldn't create a new web surface, exit + if (!hasWebSurface() && !buildWebSurface(entity)) { + return; + } + + if (urlChanged) { + _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -138,11 +183,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); } - if (_lastSourceUrl != entity->getSourceUrl()) { - _lastSourceUrl = entity->getSourceUrl(); - loadSourceURL(); - } - _lastDPI = entity->getDPI(); _lastLocked = entity->getLocked(); @@ -232,9 +272,6 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { // Let us interact with the keyboard surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); }); - _fadeStartTime = usecTimestampNow(); - loadSourceURL(); - _webSurface->resume(); // forward web events to EntityScriptingInterface auto entities = DependencyManager::get(); @@ -243,6 +280,29 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { emit entities->webEventReceived(entityItemID, message); }); + if (_contentType == ContentType::HtmlContent) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the + // web entity + if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(DEFAULT_MAX_FPS); + } + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty("url", _lastSourceUrl); + }); + } else if (_contentType == ContentType::QmlContent) { + _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { + if (item && item->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); + } + }); + } + _fadeStartTime = usecTimestampNow(); + _webSurface->resume(); + return true; } @@ -289,32 +349,6 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con return dims; } -void WebEntityRenderer::loadSourceURL() { - const QUrl sourceUrl(_lastSourceUrl); - if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || - _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { - _contentType = htmlContent; - - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - - _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); - }); - } else { - _contentType = qmlContent; - _webSurface->load(_lastSourceUrl); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - } -} - void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (!_lastLocked && _webSurface) { PointerEvent webEvent = event; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 309e750f53..3100014e9b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -47,15 +47,19 @@ private: bool buildWebSurface(const TypedEntityPointer& entity); void destroyWebSurface(); bool hasWebSurface(); - void loadSourceURL(); glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; + int _geometryId{ 0 }; - enum contentType { - htmlContent, - qmlContent + enum class ContentType { + NoContent, + HtmlContent, + QmlContent }; - contentType _contentType; + + static ContentType getContentType(const QString& urlString); + + ContentType _contentType{ ContentType::NoContent }; QSharedPointer _webSurface; glm::vec3 _contextPosition; gpu::TexturePointer _texture; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 322c91e3d3..5a3caa55fe 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -433,7 +433,7 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { _ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE); // keep whatever is assigned on the ambient map/sphere until texture is loaded -} + } } void ZoneEntityRenderer::updateAmbientMap() { diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index 769b87f2a9..0cf9596cce 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -30,7 +30,7 @@ void main(void) { varTexcoord = inTexCoord0.st; // pass along the diffuse color - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform diff --git a/libraries/entities-renderer/src/paintStroke_fade.slv b/libraries/entities-renderer/src/paintStroke_fade.slv index 9f10fa5d91..b6075caaf8 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slv +++ b/libraries/entities-renderer/src/paintStroke_fade.slv @@ -31,7 +31,7 @@ void main(void) { varTexcoord = inTexCoord0.st; // pass along the diffuse color - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform diff --git a/libraries/entities/src/AmbientLightPropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h index fbbc7c9900..591ea6a6fa 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -27,6 +27,14 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * Ambient light is defined by the following properties. + * @typedef {object} Entities.AmbientLight + * @property {number} ambientIntensity=0.5 - The intensity of the light. + * @property {string} ambientURL="" - A cube map image that defines the color of the light coming from each direction. If + * "" then the entity's {@link Entities.Skybox|Skybox} url property value is used, unless that also is "" in which + * case the entity's ambientLightMode property is set to "inherit". + */ class AmbientLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 2af56fb6b2..82af60ed1a 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -44,6 +44,19 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b } +/**jsdoc + * The AnimationProperties are used to configure an animation. + * @typedef Entities.AnimationProperties + * @property {string} url="" - The URL of the FBX file that has the animation. + * @property {number} fps=30 - The speed in frames/s that the animation is played at. + * @property {number} firstFrame=0 - The first frame to play in the animation. + * @property {number} lastFrame=100000 - The last frame to play in the animation. + * @property {number} currentFrame=0 - The current frame being played in the animation. + * @property {boolean} running=false - If true then the animation should play. + * @property {boolean} loop=true - If true then the animation should be continuously repeated in a loop. + * @property {boolean} hold=false - If true then the rotations and translations of the last frame played should be + * maintained when the animation stops playing. + */ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, Animation, animation, URL, url); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index f424c02e6e..d43bdd7b51 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -94,6 +94,49 @@ variables. These argument variables are used by the code which is run when bull #include "EntityDynamicInterface.h" +/**jsdoc +*

An entity action may be one of the following types:

+* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +* +*
ValueTypeDescriptionArguments
"far-grab"Avatar actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity. Collisions +* between the entity and the user's avatar are disabled during the far-grab.{@link Entities.ActionArguments-FarGrab}
"hold"Avatar actionPositions and rotates an entity relative to an avatar's hand. Collisions between the entity and the user's avatar +* are disabled during the hold.{@link Entities.ActionArguments-Hold}
"offset"Object actionMoves an entity so that it is a set distance away from a target point.{@link Entities.ActionArguments-Offset}
"tractor"Object actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity.{@link Entities.ActionArguments-Tractor}
"travel-oriented"Object actionOrients an entity to align with its direction of travel.{@link Entities.ActionArguments-TravelOriented}
"hinge"Object constraintLets an entity pivot about an axis or connects two entities with a hinge joint.{@link Entities.ActionArguments-Hinge}
"slider"Object constraintLets an entity slide and rotate along an axis, or connects two entities that slide and rotate along a shared +* axis.{@link Entities.ActionArguments-Slider|ActionArguments-Slider}
"cone-twist"Object constraintConnects two entities with a joint that can move through a cone and can twist.{@link Entities.ActionArguments-ConeTwist}
"ball-socket"Object constraintConnects two entities with a ball and socket joint.{@link Entities.ActionArguments-BallSocket}
"spring"Synonym for "tractor". Legacy value.
+* @typedef {string} Entities.ActionType +*/ +// Note: The "none" action type is not listed because it's an internal "uninitialized" value and not useful for scripts. EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) { QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_'); if (normalizedDynamicTypeString == "none") { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2eca612fc2..d348101b66 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -440,6 +440,707 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { return changedProperties; } +/**jsdoc + * Different entity types have different properties: some common to all entities (listed below) and some specific to each + * {@link Entities.EntityType|EntityType} (linked to below). The properties are accessed as an object of property names and + * values. + * + * @typedef {object} Entities.EntityProperties + * @property {Uuid} id - The ID of the entity. Read-only. + * @property {string} name="" - A name for the entity. Need not be unique. + * @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though + * its value may switch among "Box", "Shape", and "Sphere" depending on changes to + * the shape property set for entities of these types.) Read-only. + * @property {boolean} clientOnly=false - If true then the entity is an avatar entity, otherwise it is a server + * entity. Read-only. + * @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if clientOnly is + * true, otherwise {@link Uuid|Uuid.NULL}. Read-only. + * + * @property {string} created - The UTC date and time that the entity was created, in ISO 8601 format as + * yyyy-MM-ddTHH:mm:ssZ. Read-only. + * @property {number} age - The age of the entity in seconds since it was created. Read-only. + * @property {string} ageAsText - The age of the entity since it was created, formatted as h hours m minutes s + * seconds. + * @property {number} lifetime=-1 - How long an entity lives for, in seconds, before being automatically deleted. A value of + * -1 means that the entity lives for ever. + * @property {number} lastEdited - When the entity was last edited, expressed as the number of microseconds since + * 1970-01-01T00:00:00 UTC. Read-only. + * @property {Uuid} lastEditedBy - The session ID of the avatar or agent that most recently created or edited the entity. + * Read-only. + * + * @property {boolean} locked=false - Whether or not the entity can be edited or deleted. If true then the + * entity's properties other than locked cannot be changed, and the entity cannot be deleted. + * @property {boolean} visible=true - Whether or not the entity is rendered. If true then the entity is rendered. + * @property {boolean} canCastShadows=true - Whether or not the entity casts shadows. Currently applicable only to + * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a + * {@link Entities.EntityType|Zone} entity with castShadows enabled in its {@link Entities.EntityProperties-Zone|keyLight} property. + * + * @property {Vec3} position=0,0,0 - The position of the entity. + * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. + * @property {Vec3} registrationPoint=0.5,0.5,0.5 - The point in the entity that is set to the entity's position and is rotated + * about, {@link Vec3|Vec3.ZERO} – {@link Vec3|Vec3.ONE}. A value of {@link Vec3|Vec3.ZERO} is the entity's + * minimum x, y, z corner; a value of {@link Vec3|Vec3.ONE} is the entity's maximum x, y, z corner. + * + * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise + * {@link Vec3|Vec3.ZERO}. Read-only. + * @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model if it has one, otherwise + * {@link Vec3|Vec3.ONE}. Read-only. + * + * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. + * @property {number} damping=0.39347 - How much to slow down the linear velocity of an entity over time, 0.0 + * – 1.0. A higher damping value slows down the entity more quickly. The default value is for an + * exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 of its + * initial value. + * @property {Vec3} angularVelocity=0,0,0 - The angular velocity of the entity in rad/s with respect to its axes, about its + * registration point. + * @property {number} angularDamping=0.39347 - How much to slow down the angular velocity of an entity over time, + * 0.01.0. A higher damping value slows down the entity more quickly. The default value + * is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 + * of its initial value. + * + * @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s2 that the entity should move with, in + * world coordinates. Set to { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied to an + * entity's motion only if its dynamic property is true. If changing an entity's + * gravity from {@link Vec3|Vec3.ZERO}, you need to give it a small velocity in order to kick off + * physics simulation. + * The gravity value is applied in addition to the acceleration value. + * @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s2 that the entity should move with, in world + * coordinates. The acceleration is applied to an entity's motion only if its dynamic property is + * true. If changing an entity's acceleration from {@link Vec3|Vec3.ZERO}, you need to give it a + * small velocity in order to kick off physics simulation. + * The acceleration value is applied in addition to the gravity value. + * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, 0.0 – + * 0.99. The higher the value, the more bouncy. + * @property {number} friction=0.5 - How much to slow down an entity when it's moving against another, 0.0 – + * 10.0. The higher the value, the more quickly it slows down. Examples: 0.1 for ice, + * 0.9 for sandpaper. + * @property {number} density=1000 - The density of the entity in kg/m3, 100 for balsa wood – + * 10000 for silver. The density is used in conjunction with the entity's bounding box volume to work out its + * mass in the application of physics. + * + * @property {boolean} collisionless=false - Whether or not the entity should collide with items per its + * collisionMask property. If true then the entity does not collide. + * @property {boolean} ignoreForCollisions=false - Synonym for collisionless. + * @property {Entities.CollisionMask} collisionMask=31 - What types of items the entity should collide with. + * @property {string} collidesWith="static,dynamic,kinematic,myAvatar,otherAvatar," - Synonym for collisionMask, + * in text format. + * @property {string} collisionSoundURL="" - The sound to play when the entity experiences a collision. Valid file formats are + * as per the {@link SoundCache} object. + * @property {boolean} dynamic=false - Whether or not the entity should be affected by collisions. If true then + * the entity's movement is affected by collisions. + * @property {boolean} collisionsWillMove=false - Synonym for dynamic. + * + * @property {string} href="" - A "hifi://" metaverse address that a user is taken to when they click on the entity. + * @property {string} description="" - A description of the href property value. + * + * @property {string} userData="" - Used to store extra data about the entity in JSON format. WARNING: Other apps such as the + * Create app can also use this property, so make sure you handle data stored by other apps — edit only your bit and + * leave the rest of the data intact. You can use JSON.parse() to parse the string into a JavaScript object + * which you can manipulate the properties of, and use JSON.stringify() to convert the object into a string to + * put in the property. + * + * @property {string} script="" - The URL of the client entity script, if any, that is attached to the entity. + * @property {number} scriptTimestamp=0 - Intended to be used to indicate when the client entity script was loaded. Should be + * an integer number of milliseconds since midnight GMT on January 1, 1970 (e.g., as supplied by Date.now(). + * If you update the property's value, the script is re-downloaded and reloaded. This is how the "reload" + * button beside the "script URL" field in properties tab of the Create app works. + * @property {string} serverScripts="" - The URL of the server entity script, if any, that is attached to the entity. + * + * @property {Uuid} parentID=Uuid.NULL - The ID of the entity or avatar that this entity is parented to. {@link Uuid|Uuid.NULL} + * if the entity is not parented. + * @property {number} parentJointIndex=65535 - The joint of the entity or avatar that this entity is parented to. Use + * 65535 or -1 to parent to the entity or avatar's position and orientation rather than a joint. + * @property {Vec3} localPosition=0,0,0 - The position of the entity relative to its parent if the entity is parented, + * otherwise the same value as position. If the entity is parented to an avatar and is clientOnly + * so that it scales with the avatar, this value remains the original local position value while the avatar scale changes. + * @property {Quat} localRotation=0,0,0,1 - The rotation of the entity relative to its parent if the entity is parented, + * otherwise the same value as rotation. + * @property {Vec3} localVelocity=0,0,0 - The velocity of the entity relative to its parent if the entity is parented, + * otherwise the same value as velocity. + * @property {Vec3} localAngularVelocity=0,0,0 - The angular velocity of the entity relative to its parent if the entity is + * parented, otherwise the same value as position. + * @property {Vec3} localDimensions - The dimensions of the entity. If the entity is parented to an avatar and is + * clientOnly so that it scales with the avatar, this value remains the original dimensions value while the + * avatar scale changes. + * + * @property {Entities.BoundingBox} boundingBox - The axis-aligned bounding box that tightly encloses the entity. + * Read-only. + * @property {AACube} queryAACube - The axis-aligned cube that determines where the entity lives in the entity server's octree. + * The cube may be considerably larger than the entity in some situations, e.g., when the entity is grabbed by an avatar: + * the position of the entity is determined through avatar mixer updates and so the AA cube is expanded in order to reduce + * unnecessary entity server updates. Scripts should not change this property's value. + * + * @property {string} actionData="" - Base-64 encoded compressed dump of the actions associated with the entity. This property + * is typically not used in scripts directly; rather, functions that manipulate an entity's actions update it. + * The size of this property increases with the number of actions. Because this property value has to fit within a High + * Fidelity datagram packet there is a limit to the number of actions that an entity can have, and edits which would result + * in overflow are rejected. + * Read-only. + * @property {Entities.RenderInfo} renderInfo - Information on the cost of rendering the entity. Currently information is only + * provided for Model entities. Read-only. + * + * @property {string} itemName="" - Certifiable name of the Marketplace item. + * @property {string} itemDescription="" - Certifiable description of the Marketplace item. + * @property {string} itemCategories="" - Certifiable category of the Marketplace item. + * @property {string} itemArtist="" - Certifiable artist that created the Marketplace item. + * @property {string} itemLicense="" - Certifiable license URL for the Marketplace item. + * @property {number} limitedRun=4294967295 - Certifiable maximum integer number of editions (copies) of the Marketplace item + * allowed to be sold. + * @property {number} editionNumber=0 - Certifiable integer edition (copy) number or the Marketplace item. Each copy sold in + * the Marketplace is numbered sequentially, starting at 1. + * @property {number} entityInstanceNumber=0 - Certifiable integer instance number for identical entities in a Marketplace + * item. A Marketplace item may have identical parts. If so, then each is numbered sequentially with an instance number. + * @property {string} marketplaceID="" - Certifiable UUID for the Marketplace item, as used in the URL of the item's download + * and its Marketplace Web page. + * @property {string} certificateID="" - Hash of the entity's static certificate JSON, signed by the artist's private key. + * @property {number} staticCertificateVersion=0 - The version of the method used to generate the certificateID. + * + * @see The different entity types have additional properties as follows: + * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} + * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} + * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} + * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} + * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} + * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} + * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} + * @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone} + */ + +/**jsdoc + * The "Box" {@link Entities.EntityType|EntityType} is the same as the "Shape" + * {@link Entities.EntityType|EntityType} except that its shape value is always set to "Cube" + * when the entity is created. If its shape property value is subsequently changed then the entity's + * type will be reported as "Sphere" if the shape is set to "Sphere", + * otherwise it will be reported as "Shape". + * @typedef {object} Entities.EntityProperties-Box + */ + +/**jsdoc + * The "Light" {@link Entities.EntityType|EntityType} adds local lighting effects. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Light + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Entity surface outside these dimensions are not lit + * by the light. + * @property {Color} color=255,255,255 - The color of the light emitted. + * @property {number} intensity=1 - The brightness of the light. + * @property {number} falloffRadius=0.1 - The distance from the light's center at which intensity is reduced by 25%. + * @property {boolean} isSpotlight=false - If true then the light is directional, emitting along the entity's + * local negative z-axis; otherwise the light is a point light which emanates in all directions. + * @property {number} exponent=0 - Affects the softness of the spotlight beam: the higher the value the softer the beam. + * @property {number} cutoff=1.57 - Affects the size of the spotlight beam: the higher the value the larger the beam. + * @example Create a spotlight pointing at the ground. + * Entities.addEntity({ + * type: "Light", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -4 })), + * rotation: Quat.fromPitchYawRollDegrees(-75, 0, 0), + * dimensions: { x: 5, y: 5, z: 5 }, + * intensity: 100, + * falloffRadius: 0.3, + * isSpotlight: true, + * exponent: 20, + * cutoff: 30, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Line" {@link Entities.EntityType|EntityType} draws thin, straight lines between a sequence of two or more + * points. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Line + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Must be sufficient to contain all the + * linePoints. + * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * position. A maximum of 70 points can be specified. The property's value is set only if all the linePoints + * lie within the entity's dimensions. + * @property {number} lineWidth=2 - Currently not used. + * @property {Color} color=255,255,255 - The color of the line. + * @example Draw lines in a "V". + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 }, + * { x: 1, y: 1, z: 0 }, + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Material" {@link Entities.EntityType|EntityType} modifies the existing materials on + * {@link Entities.EntityType|Model} entities, {@link Entities.EntityType|Shape} entities (albedo only), + * {@link Overlays.OverlayType|model overlays}, and avatars. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
+ * To apply a material to an entity or overlay, set the material entity's parentID property to the entity or + * overlay's ID. + * To apply a material to an avatar, set the material entity's parentID property to the avatar's session UUID. + * To apply a material to your avatar such that it persists across domains and log-ins, create the material as an avatar entity + * by setting the clientOnly parameter in {@link Entities.addEntity} to true. + * 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 + * material with that name in the {@link MaterialResource} will be applied to the entity.
+ * Alternatively, set the property value to "userData" to use the {@link Entities.EntityProperties|userData} + * entity property to live edit the material resource values. + * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is + * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of + * 0. + * @property {string|number} parentMaterialName="0" - Selects the submesh or submeshes within the parent to apply the material + * to. If in the format "mat::string", all submeshes with material name "string" are replaced. + * Otherwise the property value is parsed as an unsigned integer, specifying the mesh index to modify. Invalid values are + * parsed to 0. + * @property {string} materialMappingMode="uv" - How the material is mapped to the entity. Either "uv" or + * "projected". Currently, only "uv" is supported. + * @property {Vec2} materialMappingPos=0,0 - Offset position in UV-space of the top left of the material, range + * { x: 0, y: 0 }{ x: 1, y: 1 }. + * @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space. + * @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees. + * @example Color a sphere using a Material entity. + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 1, y: 1, z: 1 }, + * color: { red: 128, green: 128, blue: 128 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var materialID = Entities.addEntity({ + * type: "Material", + * parentID: entityID, + * materialURL: "userData", + * priority: 1, + * userData: JSON.stringify({ + * materials: { + * // Can only set albedo on a Shape entity. + * // Value overrides entity's "color" property. + * albedo: [1.0, 0, 0] + * } + * }), + * }); + */ + +/**jsdoc + * The "Model" {@link Entities.EntityType|EntityType} displays an FBX or OBJ model. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Model + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. When adding an entity, if no dimensions + * value is specified then the model is automatically sized to its + * {@link Entities.EntityProperties|naturalDimensions}. + * @property {Color} color=255,255,255 - Currently not used. + * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
+ * Note: If the name ends with "default-image-model.fbx" then the entity is considered to be an "Image" + * entity, in which case the textures property should be set per the example. + * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the + * model's original textures. Use a texture name from the originalTextures property to override that texture. + * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no + * overrides. You can use JSON.stringify() to convert a JavaScript object of name, URL pairs into a JSON + * string. + * @property {string} originalTextures="{}" - A JSON string of texture name, URL pairs used in the model. The property value is + * filled in after the entity has finished rezzing (i.e., textures have loaded). You can use JSON.parse() to + * parse the JSON string into a JavaScript object of name, URL pairs. Read-only. + * + * @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled. + * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * "compound". + * + * @property {Entities.AnimationProperties} animation - An animation to play on the model. + * + * @property {Quat[]} jointRotations=[]] - Joint rotations applied to the model; [] if none are applied or the + * model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative to + * each joint's parent.
+ * Joint rotations can be set by {@link Entities.setLocalJointRotation|setLocalJointRotation} and similar functions, or by + * setting the value of this property. If you set a joint rotation using this property you also need to set the + * corresponding jointRotationsSet value to true. + * @property {boolean[]} jointRotationsSet=[]] - true values for joints that have had rotations applied, + * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per + * {@link Entities.getJointIndex|getJointIndex}. + * @property {Vec3[]} jointTranslations=[]] - Joint translations applied to the model; [] if none are applied or + * the model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative + * to each joint's parent.
+ * Joint translations can be set by {@link Entities.setLocalJointTranslation|setLocalJointTranslation} and similar + * functions, or by setting the value of this property. If you set a joint translation using this property you also need to + * set the corresponding jointTranslationsSet value to true. + * @property {boolean[]} jointTranslationsSet=[]] - true values for joints that have had translations applied, + * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per + * {@link Entities.getJointIndex|getJointIndex}. + * @property {boolean} relayParentJoints=false - If true and the entity is parented to an avatar, then the + * avatar's joint rotations are applied to the entity's joints. + * + * @example Rez a Vive tracker puck. + * var entity = Entities.addEntity({ + * type: "Model", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -2 })), + * rotation: MyAvatar.orientation, + * modelURL: "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj", + * dimensions: { x: 0.0945, y: 0.0921, z: 0.0423 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * @example Create an "Image" entity like you can in the Create app. + * var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; + * var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + * var entity = Entities.addEntity({ + * type: "Model", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * dimensions: { + * x: 0.5385, + * y: 0.2819, + * z: 0.0092 + * }, + * shapeType: "box", + * collisionless: true, + * modelURL: IMAGE_MODEL, + * textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }), + * lifetime: 300 // Delete after 5 minutes + * }); + */ + +/**jsdoc + * The "ParticleEffect" {@link Entities.EntityType|EntityType} displays a particle system that can be used to + * simulate things such as fire, smoke, snow, magic spells, etc. The particles emanate from an ellipsoid or part thereof. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-ParticleEffect + + * @property {boolean} isEmitting=true - If true then particles are emitted. + * @property {number} maxParticles=1000 - The maximum number of particles to render at one time. Older particles are deleted if + * necessary when new ones are created. + * @property {number} lifespan=3s - How long, in seconds, each particle lives. + * @property {number} emitRate=15 - The number of particles per second to emit. + * @property {number} emitSpeed=5 - The speed, in m/s, that each particle is emitted at. + * @property {number} speedSpread=1 - The spread in speeds at which particles are emitted at. If emitSpeed == 5 + * and speedSpread == 1, particles will be emitted with speeds in the range 4m/s – 6m/s. + * @property {vec3} emitAcceleration=0,-9.8,0 - The acceleration that is applied to each particle during its lifetime. The + * default is Earth's gravity value. + * @property {vec3} accelerationSpread=0,0,0 - The spread in accelerations that each particle is given. If + * emitAccelerations == {x: 0, y: -9.8, z: 0} and accelerationSpread == + * {x: 0, y: 1, z: 0}, each particle will have an acceleration in the range, {x: 0, y: -10.8, z: 0} + * – {x: 0, y: -8.8, z: 0}. + * @property {Vec3} dimensions - The dimensions of the particle effect, i.e., a bounding box containing all the particles + * during their lifetimes, assuming that emitterShouldTrail is false. Read-only. + * @property {boolean} emitterShouldTrail=false - If true then particles are "left behind" as the emitter moves, + * otherwise they stay with the entity's dimensions. + * + * @property {Quat} emitOrientation=-0.707,0,0,0.707 - The orientation of particle emission relative to the entity's axes. By + * 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 {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. + * @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. + * @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. + * @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. + * + * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, + * use PNG format. + * @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. + * @property {number} radiusStart=0.025 - The radius of each particle at the start of its life. If not explicitly set, the + * particleRadius value is used. + * @property {number} radiusFinish=0.025 - The radius of each particle at the end of its life. If not explicitly set, the + * particleRadius value is used. + * @property {number} radiusSpread=0 - Currently not used. + * @property {Color} color=255,255,255 - The color of each particle at the middle of its life. + * @property {Color} colorStart=255,255,255 - The color of each particle at the start of its life. If not explicitly set, the + * color value is used. + * @property {Color} colorFinish=255,255,255 - The color of each particle at the end of its life. If not explicitly set, the + * color value is used. + * @property {Color} colorSpread=0,0,0 - Currently not used. + * @property {number} alpha=1 - The alpha of each particle at the middle of its life. + * @property {number} alphaStart=1 - The alpha of each particle at the start of its life. If not explicitly set, the + * alpha value is used. + * @property {number} alphaFinish=1 - The alpha of each particle at the end of its life. If not explicitly set, the + * alpha value is used. + * @property {number} alphaSpread=0 - Currently not used. + * + * @property {ShapeType} shapeType="none" - Currently not used. Read-only. + * + * @example Create a ball of green smoke. + * particles = Entities.addEntity({ + * type: "ParticleEffect", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -4 })), + * lifespan: 5, + * emitRate: 10, + * emitSpeed: 0.02, + * speedSpread: 0.01, + * emitAcceleration: { x: 0, y: 0.02, z: 0 }, + * polarFinish: Math.PI, + * textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + * particleRadius: 0.1, + * color: { red: 0, green: 255, blue: 0 }, + * alphaFinish: 0, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "PolyLine" {@link Entities.EntityType|EntityType} draws textured, straight lines between a sequence of + * points. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-PolyLine + * @property {Vec3} dimensions=1,1,1 - The dimensions of the entity, i.e., the size of the bounding box that contains the + * lines drawn. + * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * position. A maximum of 70 points can be specified. Must be specified in order for the entity to render. + * @property {Vec3[]} normals=[]] - The normal vectors for the line's surface at the linePoints. The values are + * relative to the entity's orientation. Must be specified in order for the entity to render. + * @property {number[]} strokeWidths=[]] - The widths, in m, of the line at the linePoints. Must be specified in + * order for the entity to render. + * @property {number} lineWidth=2 - Currently not used. + * @property {Vec3[]} strokeColors=[]] - Currently not used. + * @property {Color} color=255,255,255 - The base color of the line, which is multiplied with the color of the texture for + * rendering. + * @property {string} textures="" - The URL of a JPG or PNG texture to use for the lines. If you want transparency, use PNG + * format. + * @property {boolean} isUVModeStretch=true - If true, the texture is stretched to fill the whole line, otherwise + * the texture repeats along the line. + * @example Draw a textured "V". + * var entity = Entities.addEntity({ + * type: "PolyLine", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * linePoints: [ + * { x: -1, y: 0.5, z: 0 }, + * { x: 0, y: 0, z: 0 }, + * { x: 1, y: 0.5, z: 0 } + * ], + * normals: [ + * { x: 0, y: 0, z: 1 }, + * { x: 0, y: 0, z: 1 }, + * { x: 0, y: 0, z: 1 } + * ], + * strokeWidths: [ 0.1, 0.1, 0.1 ], + * color: { red: 255, green: 0, blue: 0 }, // Use just the red channel from the image. + * textures: "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/flowArts/trails.png", + * isUVModeStretch: true, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "PolyVox" {@link Entities.EntityType|EntityType} displays a set of textured voxels. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * If you have two or more neighboring PolyVox entities of the same size abutting each other, you can display them as joined by + * configuring their voxelSurfaceStyle and neighbor ID properties.
+ * PolyVox entities uses a library from Volumes of Fun. Their + * library documentation may be useful to read. + * @typedef {object} Entities.EntityProperties-PolyVox + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. + * @property {Vec3} voxelVolumeSize=32,32,32 - Integer number of voxels along each axis of the entity, in the range + * 1,1,1 to 128,128,128. The dimensions of each voxel is + * dimensions / voxelVolumesize. + * @property {string} voxelData="ABAAEAAQAAAAHgAAEAB42u3BAQ0AAADCoPdPbQ8HFAAAAPBuEAAAAQ==" - Base-64 encoded compressed dump of + * the PolyVox data. This property is typically not used in scripts directly; rather, functions that manipulate a PolyVox + * entity update it.
+ * The size of this property increases with the size and complexity of the PolyVox entity, with the size depending on how + * the particular entity's voxels compress. Because this property value has to fit within a High Fidelity datagram packet + * there is a limit to the size and complexity of a PolyVox entity, and edits which would result in an overflow are + * rejected. + * @property {Entities.PolyVoxSurfaceStyle} voxelSurfaceStyle=2 - The style of rendering the voxels' surface and how + * neighboring PolyVox entities are joined. + * @property {string} xTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local x-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {string} yTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local y-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {string} zTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local z-axis. JPG or + * PNG format. If no texture is specified the surfaces display white. + * @property {Uuid} xNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local x-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local y-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local z-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} xPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local x-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local y-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local z-axis direction, + * if you want them joined. Set to {@link Uuid|Uuid.NULL} if there is none or you don't want to join them. + * @example Create a textured PolyVox sphere. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var texture = "http://public.highfidelity.com/cozza13/tuscany/Concrete2.jpg"; + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * xTextureURL: texture, + * yTextureURL: texture, + * zTextureURL: texture, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setVoxelSphere(polyVox, position, 0.8, 255); + */ + +/**jsdoc + * The "Shape" {@link Entities.EntityType|EntityType} displays an entity of a specified shape. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Shape + * @property {Entities.Shape} shape="Sphere" - The shape of the entity. + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. + * @property {Color} color=255,255,255 - The color of the entity. + * @example Create a cylinder. + * var shape = Entities.addEntity({ + * type: "Shape", + * shape: "Cylinder", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.4, y: 0.6, z: 0.4 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Sphere" {@link Entities.EntityType|EntityType} is the same as the "Shape" + * {@link Entities.EntityType|EntityType} except that its shape value is always set to "Sphere" + * when the entity is created. If its shape property value is subsequently changed then the entity's + * type will be reported as "Box" if the shape is set to "Cube", + * otherwise it will be reported as "Shape". + * @typedef {object} Entities.EntityProperties-Sphere + */ + +/**jsdoc + * The "Text" {@link Entities.EntityType|EntityType} displays a 2D rectangle of text in the domain. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Text + * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {string} text="" - The text to display on the face of the entity. Text wraps if necessary to fit. New lines can be + * created using \n. Overflowing lines are not displayed. + * @property {number} lineHeight=0.1 - The height of each line of text (thus determining the font size). + * @property {Color} textColor=255,255,255 - The color of the text. + * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. + * @property {boolean} faceCamera=false - If true, the entity is oriented to face each user's camera (i.e., it + * differs for each user present). + * @example Create a text entity. + * var text = Entities.addEntity({ + * type: "Text", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, + * lineHeight: 0.12, + * text: "Hello\nthere!", + * faceCamera: true, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Web" {@link Entities.EntityType|EntityType} displays a browsable Web page. Each user views their own copy + * of the Web page: if one user navigates to another page on the entity, other users do not see the change; if a video is being + * played, users don't see it in sync. + * The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Web + * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate + * on the Web entity. + * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter + * (multiply by 1 / 0.0254 = 39.3701) then multiply dimensions.x and dimensions.y by that value + * you get the resolution in pixels. + * @example Create a Web entity displaying at 1920 x 1080 resolution. + * var METERS_TO_INCHES = 39.3701; + * var entity = Entities.addEntity({ + * type: "Web", + * sourceUrl: "https://highfidelity.com/", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -4 })), + * rotation: MyAvatar.orientation, + * dimensions: { + * x: 3, + * y: 3 * 1080 / 1920, + * z: 0.01 + * }, + * dpi: 1920 / (3 * METERS_TO_INCHES), + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The "Zone" {@link Entities.EntityType|EntityType} is a volume of lighting effects and avatar permissions. + * Avatar interaction events such as {@link Entities.enterEntity} are also often used with a Zone entity. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Zone + * @property {Vec3} dimensions=0.1,0.1,0.1 - The size of the volume in which the zone's lighting effects and avatar permissions + * have effect. + * + * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar + * permissions have effect. Reverts to the default value if set to "none", or set to "compound" + * and compoundShapeURL is "". + * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * "compound". + * + * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:
+ * "inherit": The key light from any enclosing zone continues into this zone.
+ * "disabled": The key light from any enclosing zone and the key light of this zone are disabled in this + * zone.
+ * "enabled": The key light properties of this zone are enabled, overriding the key light of from any + * enclosing zone. + * @property {Entities.KeyLight} keyLight - The key light properties of the zone. + * + * @property {string} ambientLightMode="inherit" - Configures the ambient light in the zone. Possible values:
+ * "inherit": The ambient light from any enclosing zone continues into this zone.
+ * "disabled": The ambient light from any enclosing zone and the ambient light of this zone are disabled in + * this zone.
+ * "enabled": The ambient light properties of this zone are enabled, overriding the ambient light from any + * enclosing zone. + * @property {Entities.AmbientLight} ambientLight - The ambient light properties of the zone. + * + * @property {string} skyboxMode="inherit" - Configures the skybox displayed in the zone. Possible values:
+ * "inherit": The skybox from any enclosing zone is dislayed in this zone.
+ * "disabled": The skybox from any enclosing zone and the skybox of this zone are disabled in this zone.
+ * "enabled": The skybox properties of this zone are enabled, overriding the skybox from any enclosing zone. + * @property {Entities.Skybox} skybox - The skybox properties of the zone. + * + * @property {string} hazeMode="inherit" - Configures the haze in the zone. Possible values:
+ * "inherit": The haze from any enclosing zone continues into this zone.
+ * "disabled": The haze from any enclosing zone and the haze of this zone are disabled in this zone.
+ * "enabled": The haze properties of this zone are enabled, overriding the haze from any enclosing zone. + * @property {Entities.Haze} haze - The haze properties of the zone. + * + * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. + * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not + * collide with content in the zone; otherwise visitors will always collide with content in the zone. + + * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the + * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to + * certain properties.
+ *
+ * function filter(properties) {
+ *     // Test and edit properties object values,
+ *     // e.g., properties.modelURL, as required.
+ *     return properties;
+ * }
+ * 
+ * + * @example Create a zone that casts a red key light along the x-axis. + * var zone = Entities.addEntity({ + * type: "Zone", + * position: MyAvatar.position, + * dimensions: { x: 100, y: 100, z: 100 }, + * keyLightMode: "enabled", + * keyLight: { + * "color": { "red": 255, "green": 0, "blue": 0 }, + * "direction": { "x": 1, "y": 0, "z": 0 } + * }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics) const { // If strictSemantics is true and skipDefaults is false, then all and only those properties are copied for which the property flag // is included in _desiredProperties, or is one of the specially enumerated ALWAYS properties below. @@ -491,7 +1192,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); // Relevant to Shape and Model entities only. COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONLESS, collisionless); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISIONLESS, collisionless, ignoreForCollisions, getCollisionless()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_MASK, collisionMask); @@ -500,7 +1201,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_DYNAMIC, dynamic, collisionsWillMove, getDynamic()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); // Text only. COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); @@ -521,7 +1222,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); - // Boxes, Spheres, Light, Line, Model(??), Particle, PolyLine + // Light, Line, Model, ParticleEffect, PolyLine, Shape COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR, color); // Particles only @@ -569,12 +1270,15 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); } + + // FIXME: Shouldn't provide a shapeType property for Box and Sphere entities. if (_type == EntityTypes::Box) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Box")); } if (_type == EntityTypes::Sphere) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Sphere")); } + if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); } @@ -653,11 +1357,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Line || _type == EntityTypes::PolyLine) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_WIDTH, lineWidth); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_POINTS, linePoints); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_COLORS, strokeColors); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_COLORS, strokeColors); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); // Polyline only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); // Polyline only. } // Materials @@ -671,6 +1375,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); } + /**jsdoc + * The axis-aligned bounding box of an entity. + * @typedef Entities.BoundingBox + * @property {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box. + * @property {Vec3} tfl - The top far left (maximum axes values) corner of the AA box. + * @property {Vec3} center - The center of the AA box. + * @property {Vec3} dimensions - The dimensions of the AA box. + */ if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); @@ -692,6 +1404,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); @@ -700,13 +1413,23 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable // Rendering info if (!skipDefaults && !strictSemantics) { QScriptValue renderInfo = engine->newObject(); + /**jsdoc + * Information on how an entity is rendered. Properties are only filled in for Model entities; other + * entity types have an empty object, {}. + * @typedef {object} Entities.RenderInfo + * @property {number} verticesCount - The number of vertices in the entity. + * @property {number} texturesCount - The number of textures in the entity. + * @property {number} textureSize - The total size of the textures in the entity, in bytes. + * @property {boolean} hasTransparent - Is true if any of the textures has transparency. + * @property {number} drawCalls - The number of draw calls required to render the entity. + */ // currently only supported by models if (_type == EntityTypes::Model) { renderInfo.setProperty("verticesCount", (int)getRenderInfoVertexCount()); // FIXME - theoretically the number of vertex could be > max int @@ -719,6 +1442,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable } + // FIXME: These properties should already have been set above. properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 0838bb937a..9e5d6ddc79 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -208,7 +208,7 @@ public: DEFINE_PROPERTY(PROP_NORMALS, Normals, normals, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector, QVector()); - DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); + DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString, ""); DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString, ""); DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString, ""); diff --git a/libraries/entities/src/EntityNodeData.h b/libraries/entities/src/EntityNodeData.h index eb5a1610cc..090a5e0526 100644 --- a/libraries/entities/src/EntityNodeData.h +++ b/libraries/entities/src/EntityNodeData.h @@ -33,7 +33,7 @@ public: // these can only be called from the OctreeSendThread for the given Node void insertSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.insert(entityID); } void removeSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.remove(entityID); } - bool sentFilteredEntity(const QUuid& entityID) { return _sentFilteredEntities.contains(entityID); } + bool sentFilteredEntity(const QUuid& entityID) const { return _sentFilteredEntities.contains(entityID); } QSet getSentFilteredEntities() { return _sentFilteredEntities; } // the following flagged extra entity methods can only be called from the OctreeSendThread for the given Node diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 93cbe781be..266ef41cff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -538,7 +538,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& NestableType nestableType = nestable->getNestableType(); if (nestableType == NestableType::Overlay || nestableType == NestableType::Avatar) { qCWarning(entities) << "attempted edit on non-entity: " << id << nestable->getName(); - return QUuid(); // null UUID to indicate failure + return QUuid(); // null script value to indicate failure } } } @@ -1017,6 +1017,25 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c QString faceName = ""; // handle BoxFace + /**jsdoc + *

A BoxFace specifies the face of an axis-aligned (AA) box. + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
+ * @typedef {string} BoxFace + */ + // FIXME: Move enum to string function to BoxBase.cpp. switch (value.face) { case MIN_X_FACE: faceName = "MIN_X_FACE"; diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 6b4cd81e44..b483225390 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -52,6 +52,23 @@ private: QPointer _engine; }; +/**jsdoc + * The result of a {@link PickRay} search using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}. + * @typedef {object} Entities.RayToEntityIntersectionResult + * @property {boolean} intersects - true if the {@link PickRay} intersected an entity, otherwise + * false. + * @property {boolean} accurate - Is always true. + * @property {Uuid} entityID - The ID if the entity intersected, if any, otherwise null. + * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. + * @property {Vec3} intersection - The intersection point. + * @property {Vec3} surfaceNormal - The surface normal of the entity at the intersection point. + * @property {BoxFace} face - The face of the entity's axis-aligned box that the ray intersects. + * @property {object} extraInfo - Extra information depending on the entity intersected. Currently, only Model + * entities provide extra information, and the information provided depends on the precisionPicking parameter + * value that the search function was called with. + */ +// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: RayToEntityIntersectionResult(); @@ -72,7 +89,16 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra /**jsdoc + * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible + * to everyone and typically are persisted to the domain. For Interface scripts, the entities available are those that + * Interface has displayed and so knows about. + * * @namespace Entities + * @property {number} currentAvatarEnergy - Deprecated + * @property {number} costMultiplier - Deprecated + * @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus. + * If no entity has keyboard focus, get returns null; set to null or {@link Uuid|Uuid.NULL} to + * clear keyboard focus. */ /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { @@ -107,58 +133,88 @@ public: public slots: /**jsdoc - * Returns `true` if the DomainServer will allow this Node/Avatar to make changes - * + * Check whether or not you can change the locked property of entities. Locked entities have their + * locked property set to true and cannot be edited or deleted. Whether or not you can change + * entities' locked properties is configured in the domain server's permissions. * @function Entities.canAdjustLocks - * @return {bool} `true` if the client can adjust locks, `false` if not. + * @returns {boolean} true if the client can change the locked property of entities, + * otherwise false. + * @example Set an entity's locked property to true if you can. + * if (Entities.canAdjustLocks()) { + * Entities.editEntity(entityID, { locked: true }); + * } else { + * Window.alert("You do not have the permissions to set an entity locked!"); + * } */ Q_INVOKABLE bool canAdjustLocks(); /**jsdoc + * Check whether or not you can rez (create) new entities in the domain. * @function Entities.canRez - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new entities + * @returns {boolean} true if the domain server will allow the script to rez (create) new entities, + * otherwise false. */ Q_INVOKABLE bool canRez(); /**jsdoc + * Check whether or not you can rez (create) new temporary entities in the domain. Temporary entities are entities with a + * finite lifetime property value set. * @function Entities.canRezTmp - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new temporary entities + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary + * entities, otherwise false. */ Q_INVOKABLE bool canRezTmp(); /**jsdoc - * @function Entities.canRezCertified - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new certified entities - */ + * Check whether or not you can rez (create) new certified entities in the domain. Certified entities are entities that have + * PoP certificates. + * @function Entities.canRezCertified + * @returns {boolean} true if the domain server will allow the script to rez (create) new certified + * entities, otherwise false. + */ Q_INVOKABLE bool canRezCertified(); /**jsdoc - * @function Entities.canRezTmpCertified - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to rez new temporary certified entities - */ + * Check whether or not you can rez (create) new temporary certified entities in the domain. Temporary entities are entities + * with a finite lifetime property value set. Certified entities are entities that have PoP certificates. + * @function Entities.canRezTmpCertified + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary + * certified entities, otherwise false. + */ Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * @function Entities.canWriteAssets - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server - */ + * Check whether or not you can make changes to the asset server's assets. + * @function Entities.canWriteAssets + * @returns {boolean} true if the domain server will allow the script to make changes to the asset server's + * assets, otherwise false. + */ Q_INVOKABLE bool canWriteAssets(); /**jsdoc - * @function Entities.canReplaceContent - * @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set - */ + * Check whether or not you can replace the domain's content set. + * @function Entities.canReplaceContent + * @returns {boolean} true if the domain server will allow the script to replace the domain's content set, + * otherwise false. + */ Q_INVOKABLE bool canReplaceContent(); /**jsdoc - * Add a new entity with the specified properties. If `clientOnly` is true, the entity will - * not be sent to the server and will only be visible/accessible on the local client. - * + * Add a new entity with specified properties. * @function Entities.addEntity - * @param {EntityItemProperties} properties Properties of the entity to create. - * @param {bool} [clientOnly=false] Whether the entity should only exist locally or not. - * @return {EntityID} The entity ID of the newly created entity. The ID will be a null - * UUID (`{00000000-0000-0000-0000-000000000000}`) if the entity could not be created. + * @param {Entities.EntityProperties} properties - The properties of the entity to create. + * @param {boolean} [clientOnly=false] - If true, the entity is created as an avatar entity, otherwise it + * is created on the server. An avatar entity follows you to each domain you visit, rendering at the same world + * coordinates unless it's parented to your avatar. + * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}. + * @example Create a box entity in front of your avatar. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * print("Entity created: " + entityID); */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); @@ -168,115 +224,202 @@ public slots: bool collisionless, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc - * Return the properties for the specified {EntityID}. - * not be sent to the server and will only be visible/accessible on the local client. - * @param {EntityItemProperties} properties Properties of the entity to create. - * @param {EntityPropertyFlags} [desiredProperties=[]] Array containing the names of the properties you - * would like to get. If the array is empty, all properties will be returned. - * @return {EntityItemProperties} The entity properties for the specified entity. + * Get the properties of an entity. + * @function Entities.getEntityProperties + * @param {Uuid} entityID - The ID of the entity to get the properties of. + * @param {string[]} [desiredProperties=[]] - Array of the names of the properties to get. If the array is empty, + * all properties are returned. + * @returns {Entities.EntityProperties} The properties of the entity if the entity can be found, otherwise an empty object. + * @example Report the color of a new box entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * var properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID); Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties); /**jsdoc - * Updates an entity with the specified properties. - * + * Update an entity with specified properties. * @function Entities.editEntity - * @return {EntityID} The EntityID of the entity if the edit was successful, otherwise the null {EntityID}. + * @param {Uuid} entityID - The ID of the entity to edit. + * @param {Entities.EntityProperties} properties - The properties to update the entity with. + * @returns {Uuid} The ID of the entity if the edit was successful, otherwise null. + * @example Change the color of an entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * var properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); + * + * Entities.editEntity(entityID, { + * color: { red: 255, green: 0, blue: 0 } + * }); + * properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); */ Q_INVOKABLE QUuid editEntity(QUuid entityID, const EntityItemProperties& properties); /**jsdoc - * Deletes an entity. - * + * Delete an entity. * @function Entities.deleteEntity - * @param {EntityID} entityID The ID of the entity to delete. + * @param {Uuid} entityID - The ID of the entity to delete. + * @example Delete an entity a few seconds after creating it. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * }); + * + * Script.setTimeout(function () { + * Entities.deleteEntity(entityID); + * }, 3000); */ Q_INVOKABLE void deleteEntity(QUuid entityID); + /**jsdoc - * Call a method on an entity in the same context as this function is called. Allows a script - * to call a method on an entity's script. The method will execute in the entity script engine. - * If the entity does not have an entity script or the method does not exist, this call will - * have no effect. If it is running an entity script (specified by the `script` property) - * and it exposes a property with the specified name `method`, it will be called - * using `params` as the list of arguments. If this is called within an entity script, the - * method will be executed on the client in the entity script engine in which it was called. If - * this is called in an entity server script, the method will be executed on the entity server - * script engine. - * + * Call a method in a client entity script from a client script or client entity script, or call a method in a server + * entity script from a server entity script. The entity script method must be exposed as a property in the target client + * entity script. Additionally, if calling a server entity script, the server entity script must include the method's name + * in an exposed property called remotelyCallable that is an array of method names that can be called. * @function Entities.callEntityMethod - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. */ Q_INVOKABLE void callEntityMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a server method on an entity. Allows a client entity script to call a method on an - * entity's server script. The method will execute in the entity server script engine. If - * the entity does not have an entity server script or the method does not exist, this call will - * have no effect. If the entity is running an entity script (specified by the `serverScripts` property) - * and it exposes a property with the specified name `method`, it will be called using `params` as - * the list of arguments. - * - * @function Entities.callEntityServerMethod - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. - */ + * Call a method in a server entity script from a client script or client entity script. The entity script method must be + * exposed as a property in the target server entity script. Additionally, the target server entity script must include the + * method's name in an exposed property called remotelyCallable that is an array of method names that can be + * called. + * @function Entities.callEntityServerMethod + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + */ Q_INVOKABLE void callEntityServerMethod(QUuid entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a client method on an entity on a specific client node. Allows a server entity script to call a - * method on an entity's client script for a particular client. The method will execute in the entity script - * engine on that single client. If the entity does not have an entity script or the method does not exist, or - * the client is not connected to the domain, or you attempt to make this call outside of the entity server - * script, this call will have no effect. - * - * @function Entities.callEntityClientMethod - * @param {SessionID} clientSessionID The session ID of the client to call the method on. - * @param {EntityID} entityID The ID of the entity to call the method on. - * @param {string} method The name of the method to call. - * @param {string[]} params The list of parameters to call the specified method with. - */ - Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, const QStringList& params = QStringList()); + * Call a method in a specific user's client entity script from a server entity script. The entity script method must be + * exposed as a property in the target client entity script. + * @function Entities.callEntityClientMethod + * @param {Uuid} clientSessionID - The session ID of the user to call the method in. + * @param {Uuid} entityID - The ID of the entity to call the method in. + * @param {string} method - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + */ + Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, + const QStringList& params = QStringList()); + /**jsdoc - * finds the closest model to the center point, within the radius - * will return a EntityItemID.isKnownID = false if no models are in the radius - * this function will not find any models in script engine contexts which don't have access to models + * Find the entity with a position closest to a specified point and within a specified radius. * @function Entities.findClosestEntity - * @param {vec3} center point - * @param {float} radius to search - * @return {EntityID} The EntityID of the entity that is closest and in the radius. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid} The ID of the entity that is closest to the center and within the radius if + * there is one, otherwise null. + * @example Find the closest entity within 10m of your avatar. + * var entityID = Entities.findClosestEntity(MyAvatar.position, 10); + * print("Closest entity: " + entityID); */ + /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; - /// finds models within the search sphere specified by the center point and radius + /**jsdoc + * Find all entities that intersect a sphere defined by a center point and radius. + * @function Entities.findEntities + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid[]} An array of entity IDs that were found that intersect the search sphere. The array is empty if no + * entities could be found. + * @example Report how many entities are within 10m of your avatar. + * var entityIDs = Entities.findEntities(MyAvatar.position, 10); + * print("Number of entities within 10m: " + entityIDs.length); + */ /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; - /// finds models within the box specified by the corner and dimensions + /**jsdoc + * Find all entities whose axis-aligned boxes intersect a search axis-aligned box defined by its minimum coordinates corner + * and dimensions. + * @function Entities.findEntitiesInBox + * @param {Vec3} corner - The corner of the search AA box with minimum co-ordinate values. + * @param {Vec3} dimensions - The dimensions of the search AA box. + * @returns {Uuid[]} An array of entity IDs whose AA boxes intersect the search AA box. The array is empty if no entities + * could be found. + */ /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; - /// finds models within the frustum - /// the frustum must have the following properties: - /// - position - /// - orientation - /// - projection - /// - centerRadius + /**jsdoc + * Find all entities whose axis-aligned boxes intersect a search frustum. + * @function Entities.findEntitiesInFrustum + * @param {ViewFrustum} frustum - The frustum to search in. The position, orientation, + * projection, and centerRadius properties must be specified. + * @returns {Uuid[]} An array of entity IDs axis-aligned boxes intersect the frustum. The array is empty if no entities + * could be found. + * @example Report the number of entities in view. + * var entityIDs = Entities.findEntitiesInFrustum(Camera.frustum); + * print("Number of entities in view: " + entityIDs.length); + */ /// this function will not find any models in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; - /// finds entities of the indicated type within a sphere given by the center point and radius - /// @param {QString} string representation of entity type - /// @param {vec3} center point - /// @param {float} radius to search + /**jsdoc + * Find all entities of a particular type that intersect a sphere defined by a center point and radius. + * @function Entities.findEntitiesByType + * @param {Entities.EntityType} entityType - The type of entity to search for. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @returns {Uuid[]} An array of entity IDs of the specified type that intersect the search sphere. The array is empty if + * no entities could be found. + * @example Report the number of Model entities within 10m of your avatar. + * var entityIDs = Entities.findEntitiesByType("Model", MyAvatar.position, 10); + * print("Number of Model entities within 10m: " + entityIDs.length); + */ /// this function will not find any entities in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; + /**jsdoc + * Find the first entity intersected by a {@link PickRay}. Light and Zone entities are not + * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} + * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.
+ * @function Entities.findRayIntersection + * @param {PickRay} pickRay - The PickRay to use for finding entities. + * @param {boolean} [precisionPicking=false] - If true and the intersected entity is a Model + * entity, the result's extraInfo property includes more information than it otherwise would. + * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. + * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. + * @param {boolean} [visibleOnly=false] - If true then only entities that are + * {@link Entities.EntityProperties|visible} are searched. + * @param {boolean} [collideableOnly=false] - If true then only entities that are not + * {@link Entities.EntityProperties|collisionless} are searched. + * @returns {Entities.RayToEntityIntersectionResult} The result of the search for the first intersected entity. + * @example Find the entity directly in front of your avatar. + * var pickRay = { + * origin: MyAvatar.position, + * direction: Quat.getFront(MyAvatar.orientation) + * }; + * + * var intersection = Entities.findRayIntersection(pickRay, true); + * if (intersection.intersects) { + * print("Entity in front of avatar: " + intersection.entityID); + * } else { + * print("No entity in front of avatar."); + * } + */ /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. @@ -289,187 +432,1424 @@ public slots: const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly); + /**jsdoc + * Find the first entity intersected by a {@link PickRay}. Light and Zone entities are not + * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} + * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.
+ * This is a synonym for {@link Entities.findRayIntersection|findRayIntersection}. + * @function Entities.findRayIntersectionBlocking + * @param {PickRay} pickRay - The PickRay to use for finding entities. + * @param {boolean} [precisionPicking=false] - If true and the intersected entity is a Model + * entity, the result's extraInfo property includes more information than it otherwise would. + * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. + * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. + * @deprecated This function is deprecated and will soon be removed. Use + * {@link Entities.findRayIntersection|findRayIntersection} instead; it blocks and performs the same function. + */ /// If the scripting context has visible entities, this will determine a ray intersection, and will block in /// order to return an accurate result - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, + const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); + + /**jsdoc + * Reloads an entity's server entity script such that the latest version re-downloaded. + * @function Entities.reloadServerScripts + * @param {Uuid} entityID - The ID of the entity to reload the server entity script of. + * @returns {boolean} true if the reload request was successfully sent to the server, otherwise + * false. + */ Q_INVOKABLE bool reloadServerScripts(QUuid entityID); /**jsdoc - * Query additional metadata for "magic" Entity properties like `script` and `serverScripts`. - * - * @function Entities.queryPropertyMetadata - * @param {EntityID} entityID The ID of the entity. - * @param {string} property The name of the property extended metadata is wanted for. - * @param {ResultCallback} callback Executes callback(err, result) with the query results. + * Gets the status of server entity script attached to an entity + * @function Entities.getServerScriptStatus + * @property {Uuid} entityID - The ID of the entity to get the server entity script status for. + * @property {Entities~getServerScriptStatusCallback} callback - The function to call upon completion. + * @returns {boolean} true always. */ /**jsdoc - * Query additional metadata for "magic" Entity properties like `script` and `serverScripts`. - * - * @function Entities.queryPropertyMetadata - * @param {EntityID} entityID The ID of the entity. - * @param {string} property The name of the property extended metadata is wanted for. - * @param {Object} thisObject The scoping "this" context that callback will be executed within. - * @param {ResultCallback} callbackOrMethodName Executes thisObject[callbackOrMethodName](err, result) with the query results. + * Called when {@link Entities.getServerScriptStatus} is complete. + * @callback Entities~getServerScriptStatusCallback + * @param {boolean} success - true if the server entity script status could be obtained, otherwise + * false. + * @param {boolean} isRunning - true if there is a server entity script running, otherwise false. + * @param {string} status - "running" if there is a server entity script running, otherwise an error string. + * @param {string} errorInfo - "" if there is a server entity script running, otherwise it may contain extra + * information on the error. */ - Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); - Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback); + /**jsdoc + * Get metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ + /**jsdoc + * Get metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {object} scope - The "this" context that the callback will be executed within. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ + /**jsdoc + * Called when {@link Entities.queryPropertyMetadata} is complete. + * @callback Entities~queryPropertyMetadataCallback + * @param {string} error - undefined if there was no error, otherwise an error message. + * @param {object} result - The metadata for the requested entity property if there was no error, otherwise + * undefined. + */ + Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, + QScriptValue methodOrName = QScriptValue()); + + + /**jsdoc + * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. By default, Light + * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.setLightsArePickable + * @param {boolean} value - Set true to make ray picks intersect the bounding box of + * {@link Entities.EntityType|Light} entities, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setLightsArePickable(bool value); + + /**jsdoc + * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. Ray picks are + * done using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.getLightsArePickable + * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Light} + * entities, otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getLightsArePickable() const; + /**jsdoc + * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. By default, Light + * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.setZonesArePickable + * @param {boolean} value - Set true to make ray picks intersect the bounding box of + * {@link Entities.EntityType|Zone} entities, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setZonesArePickable(bool value); + + /**jsdoc + * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. Ray picks are + * done using {@link Entities.findRayIntersection|findRayIntersection} or + * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} + * APIs. + * @function Entities.getZonesArePickable + * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Zone} + * entities, otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getZonesArePickable() const; + /**jsdoc + * Set whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. + * @function Entities.setDrawZoneBoundaries + * @param {boolean} value - Set to true if {@link Entities.EntityType|Zone} entities' boundaries should be + * drawn, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void setDrawZoneBoundaries(bool value); + + /**jsdoc + * Get whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. + * @function Entities.getDrawZoneBoundaries + * @returns {boolean} true if {@link Entities.EntityType|Zone} entities' boundaries should be drawn, + * otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool getDrawZoneBoundaries() const; + + /**jsdoc + * Set the values of all voxels in a spherical portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelSphere + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} center - The center of the sphere of voxels to set, in world coordinates. + * @param {number} radius - The radius of the sphere of voxels to set, in world coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox sphere. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 32, y: 32, z: 32 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setVoxelSphere(polyVox, position, 0.9, 255); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); + + /**jsdoc + * Set the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelCapsule + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} start - The center of the sphere of voxels to set, in world coordinates. + * @param {Vec3} end - The center of the sphere of voxels to set, in world coordinates. + * @param {number} radius - The radius of the capsule cylinder and spherical ends, in world coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox capsule shape. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: position, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 32, y: 32, z: 32 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var startPosition = Vec3.sum({ x: -0.5, y: 0, z: 0 }, position); + * var endPosition = Vec3.sum({ x: 0.5, y: 0, z: 0 }, position); + * Entities.setVoxelCapsule(polyVox, startPosition, endPosition, 0.5, 255); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxelCapsule(QUuid entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); + /**jsdoc + * Set the value of a particular voxels in a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxel + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} position - The position relative to the minimum axes values corner of the entity. The + * position coordinates are rounded to the nearest integer to get the voxel coordinate. The minimum axes + * corner voxel is { x: 0, y: 0, z: 0 }. + * @param {number} value - If value % 256 == 0 then voxel is cleared, otherwise the voxel is set. + * @example Create a cube PolyVox entity and clear the minimum axes corner voxel. + * var entity = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(entity, 1); + * Entities.setVoxel(entity, { x: 0, y: 0, z: 0 }, 0); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value); + + /**jsdoc + * Set the values of all voxels in a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setAllVoxels + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox cube. + * var entity = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(entity, 1); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value); - Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, - const glm::vec3& cuboidSize, int value); - Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); - Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); - - Q_INVOKABLE void dumpTree() const; - - Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); - Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); - Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); - Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); - Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); + /**jsdoc + * Set the values of all voxels in a cubic portion of a {@link Entities.EntityType|PolyVox} entity. + * @function Entities.setVoxelsInCuboid + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} lowPosition - The position of the minimum axes value corner of the cube of voxels to set, in voxel + * coordinates. + * @param {Vec3} cuboidSize - The size of the cube of voxels to set, in voxel coordinates. + * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. + * @example Create a PolyVox cube and clear the voxels in one corner. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(polyVox, 1); + * var cuboidPosition = { x: 12, y: 12, z: 12 }; + * var cuboidSize = { x: 4, y: 4, z: 4 }; + * Entities.setVoxelsInCuboid(polyVox, cuboidPosition, cuboidSize, 0); + */ + // FIXME move to a renderable entity interface + Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); + /**jsdoc + * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to world coordinates. Voxel coordinates are + * relative to the minimum axes values corner of the entity with a scale of Vec3.ONE being the dimensions of + * each voxel. + * @function Entities.voxelCoordsToWorldCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. + * @returns {Vec3} The world coordinates of the voxelCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * @example Create a PolyVox cube with the 0,0,0 voxel replaced by a sphere. + * // Cube PolyVox with 0,0,0 voxel missing. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * Entities.setAllVoxels(polyVox, 1); + * Entities.setVoxel(polyVox, { x: 0, y: 0, z: 0 }, 0); + * + * // Red sphere in 0,0,0 corner position. + * var cornerPosition = Entities.voxelCoordsToWorldCoords(polyVox, { x: 0, y: 0, z: 0 }); + * var voxelDimensions = Vec3.multiply(2 / 16, Vec3.ONE); + * var sphere = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(cornerPosition, Vec3.multiply(0.5, voxelDimensions)), + * dimensions: voxelDimensions, + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); + + /**jsdoc + * Convert world coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Voxel coordinates are + * relative to the minimum axes values corner of the entity, with a scale of Vec3.ONE being the dimensions of + * each voxel. + * @function Entities.worldCoordsToVoxelCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} worldCoords - The world coordinates. May be outside the entity's bounding box. + * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); + + /**jsdoc + * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to local coordinates relative to the minimum + * axes value corner of the entity, with the scale being the same as world coordinates. + * @function Entities.voxelCoordsToLocalCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. + * @returns {Vec3} The local coordinates of the voxelCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * @example Get the world dimensions of a voxel in a PolyVox entity. + * var polyVox = Entities.addEntity({ + * type: "PolyVox", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), + * dimensions: { x: 2, y: 2, z: 2 }, + * voxelVolumeSize: { x: 16, y: 16, z: 16 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var voxelDimensions = Entities.voxelCoordsToLocalCoords(polyVox, Vec3.ONE); + * print("Voxel dimensions: " + JSON.stringify(voxelDimensions)); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); + + /**jsdoc + * Convert local coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Local coordinates are + * relative to the minimum axes value corner of the entity, with the scale being the same as world coordinates. + * @function Entities.localCoordsToVoxelCoords + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Vec3} localCoords - The local coordinates. May be outside the entity's bounding box. + * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); + /**jsdoc + * Set the linePoints property of a {@link Entities.EntityType|Line} entity. + * @function Entities.setAllPoints + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. + * @param {Vec3[]} points - The array of points to set the entity's linePoints property to. + * @returns {boolean} true if the entity's property was updated, otherwise false. The property + * may fail to be updated if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, + * one of the points is outside the entity's dimensions, or the number of points is greater than the maximum allowed. + * @example Change the shape of a Line entity. + * // Draw a horizontal line between two points. + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 0, z: 0 }, + * { x:1, y: -0, z: 0 } + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Change the line to be a "V". + * Script.setTimeout(function () { + * Entities.setAllPoints(entity, [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 }, + * { x: 1, y: 1, z: 0 }, + * ]); + * }, 2000); + */ + Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector& points); + + /**jsdoc + * Append a point to a {@link Entities.EntityType|Line} entity. + * @function Entities.appendPoint + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. + * @param {Vec3} point - The point to add to the line. The coordinates are relative to the entity's position. + * @returns {boolean} true if the point was added to the line, otherwise false. The point may + * fail to be added if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, the + * point is outside the entity's dimensions, or the maximum number of points has been reached. + * @example Append a point to a Line entity. + * // Draw a line between two points. + * var entity = Entities.addEntity({ + * type: "Line", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * rotation: MyAvatar.orientation, + * dimensions: { x: 2, y: 2, z: 1 }, + * linePoints: [ + * { x: -1, y: 1, z: 0 }, + * { x: 0, y: -1, z: 0 } + * ], + * color: { red: 255, green: 0, blue: 0 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Add a third point to create a "V". + * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); + */ + Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point); + + /**jsdoc + * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about — domain + * and client-only — to the program log. + * @function Entities.dumpTree + */ + Q_INVOKABLE void dumpTree() const; + + + /**jsdoc + * Add an action to an entity. An action is registered with the physics engine and is applied every physics simulation + * step. Any entity may have more than one action associated with it, but only as many as will fit in an entity's + * actionData property. + * @function Entities.addAction + * @param {Entities.ActionType} actionType - The type of action. + * @param {Uuid} entityID - The ID of the entity to add the action to. + * @param {Entities.ActionArguments} arguments - Configure the action. + * @returns {Uuid} The ID of the action added if successfully added, otherwise null. + * @example Constrain a cube to move along a vertical line. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.75, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * dynamic: true, + * collisionless: false, + * userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }", + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var actionID = Entities.addAction("slider", entityID, { + * axis: { x: 0, y: 1, z: 0 }, + * linearLow: 0, + * linearHigh: 0.6 + * }); + */ + Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); + + /**jsdoc + * Update an entity action. + * @function Entities.updateAction + * @param {Uuid} entityID - The ID of the entity with the action to update. + * @param {Uuid} actionID - The ID of the action to update. + * @param {Entities.ActionArguments} arguments - The arguments to update. + * @returns {boolean} true if the update was successful, otherwise false. + */ + Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); + + /**jsdoc + * Delete an action from an entity. + * @function Entities.deleteAction + * @param {Uuid} entityID - The ID of entity to delete the action from. + * @param {Uuid} actionID - The ID of the action to delete. + * @returns {boolean} true if the update was successful, otherwise false. + */ + Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); + + /**jsdoc + * Get the IDs of the actions that are associated with an entity. + * @function Entities.getActionIDs + * @param {Uuid} entityID - The entity to get the action IDs for. + * @returns {Uuid[]} An array of action IDs if any are found, otherwise an empty array. + */ + Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); + + /**jsdoc + * Get the arguments of an action. + * @function Entities.getActionArguments + * @param {Uuid} entityID - The ID of the entity with the action. + * @param {Uuid} actionID - The ID of the action to get the arguments of. + * @returns {Entities.ActionArguments} The arguments of the requested action if found, otherwise an empty object. + */ + Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); + + + /**jsdoc + * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.getAbsoluteJointTranslationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Vec3} The translation of the joint relative to the entity's position and orientation if the entity is a + * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Vec3(0)|Vec3.ZERO}. + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.getAbsoluteJointRotationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Quat} The rotation of the joint relative to the entity's orientation if the entity is a + * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Quat(0)|Quat.IDENTITY}. + * @example Compare the local and absolute rotations of an avatar model's left hand joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "LeftHand"); + * var localRotation = Entities.getLocalJointRotation(entityID, index); + * var absoluteRotation = Entities.getAbsoluteJointRotationInObjectFrame(entityID, index); + * print("Left hand local rotation: " + JSON.stringify(Quat.safeEulerAngles(localRotation))); + * print("Left hand absolute rotation: " + JSON.stringify(Quat.safeEulerAngles(absoluteRotation))); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Set the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.setAbsoluteJointTranslationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Vec3} translation - The translation to set the joint to relative to the entity's position and orientation. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); + + /**jsdoc + * Set the rotation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and + * orientation. + * @function Entities.setAbsoluteJointRotationInObjectFrame + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Quat} rotation - The rotation to set the joint to relative to the entity's orientation. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @example Raise an avatar model's left palm. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "LeftHand"); + * var absoluteRotation = Entities.getAbsoluteJointRotationInObjectFrame(entityID, index); + * absoluteRotation = Quat.multiply(Quat.fromPitchYawRollDegrees(0, 0, 90), absoluteRotation); + * var success = Entities.setAbsoluteJointRotationInObjectFrame(entityID, index, absoluteRotation); + * print("Success: " + success); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation); + + /**jsdoc + * Get the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getLocalJointTranslation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Vec3} The local translation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the + * entity is loaded, and the joint index is valid; otherwise {@link Vec3(0)|Vec3.ZERO}. + */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getLocalJointTranslation(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Get the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getLocalJointRotation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @returns {Quat} The local rotation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity + * is loaded, and the joint index is valid; otherwise {@link Quat(0)|Quat.IDENTITY}. + * @example Report the local rotation of an avatar model's head joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * var rotation = Entities.getLocalJointRotation(entityID, index); + * print("Head local rotation: " + JSON.stringify(Quat.safeEulerAngles(rotation))); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE glm::quat getLocalJointRotation(const QUuid& entityID, int jointIndex); + + /**jsdoc + * Set the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointTranslation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Vec3} translation - The local translation to set the joint to. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation); + + /**jsdoc + * Set the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointRotation + * @param {Uuid} entityID - The ID of the entity. + * @param {number} jointIndex - The integer index of the joint. + * @param {Quat} rotation - The local rotation to set the joint to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @example Make an avatar model turn its head left. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * var rotation = Quat.fromPitchYawRollDegrees(0, 60, 0); + * var success = Entities.setLocalJointRotation(entityID, index, rotation); + * print("Success: " + success); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::quat rotation); + + /**jsdoc + * Set the local translations of joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointTranslations + * @param {Uuid} entityID - The ID of the entity. + * @param {Vec3[]} translations - The local translations to set the joints to. + * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the translations is different to the model's current translations; + * otherwise false. + */ + // FIXME move to a renderable entity interface + Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + + /**jsdoc + * Set the local rotations of joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.setLocalJointRotations + * @param {Uuid} entityID - The ID of the entity. + * @param {Quat[]} rotations - The local rotations to set the joints to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the rotations is different to the model's current rotations; otherwise + * false. + * @example Raise both palms of an avatar model. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * + * // Get all the joint rotations. + * var jointNames = Entities.getJointNames(entityID); + * var jointRotations = []; + * for (var i = 0, length = jointNames.length; i < length; i++) { + * var index = Entities.getJointIndex(entityID, jointNames[i]); + * jointRotations.push(Entities.getLocalJointRotation(entityID, index)); + * } + * + * // Raise both palms. + * var index = jointNames.indexOf("LeftHand"); + * jointRotations[index] = Quat.multiply(Quat.fromPitchYawRollDegrees(-90, 0, 0), jointRotations[index]); + * index = jointNames.indexOf("RightHand"); + * jointRotations[index] = Quat.multiply(Quat.fromPitchYawRollDegrees(-90, 0, 0), jointRotations[index]); + * + * // Update all the joint rotations. + * var success = Entities.setLocalJointRotations(entityID, jointRotations); + * print("Success: " + success); + * }, 2000); + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector& rotations); - Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + + /**jsdoc + * Set the local rotations and translations of joints in a {@link Entities.EntityType|Model} entity. This is the same as + * calling both {@link Entities.setLocalJointRotations|setLocalJointRotations} and + * {@link Entities.setLocalJointTranslations|setLocalJointTranslations} at the same time. + * @function Entities.setLocalJointsData + * @param {Uuid} entityID - The ID of the entity. + * @param {Quat[]} rotations - The local rotations to set the joints to. + * @param {Vec3[]} translations - The local translations to set the joints to. + * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, + * the model has joints, and at least one of the rotations or translations is different to the model's current values; + * otherwise false. + */ + // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, const QVector& rotations, const QVector& translations); + + /**jsdoc + * Get the index of a named joint in a {@link Entities.EntityType|Model} entity. + * @function Entities.getJointIndex + * @param {Uuid} entityID - The ID of the entity. + * @param {string} name - The name of the joint. + * @returns {number} The integer index of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity + * is loaded, and the joint is present; otherwise -1. The joint indexes are in order per + * {@link Entities.getJointNames|getJointNames}. + * @example Report the index of a model's head joint. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var index = Entities.getJointIndex(entityID, "Head"); + * print("Head joint index: " + index); + * }, 2000); + */ // FIXME move to a renderable entity interface Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); + + /**jsdoc + * Get the names of all the joints in a {@link Entities.EntityType|Model} entity. + * @function Entities.getJointNames + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Model} entity. + * @returns {string[]} The names of all the joints in the entity if it is a {@link Entities.EntityType|Model} entity and + * is loaded, otherwise an empty array. The joint names are in order per {@link Entities.getJointIndex|getJointIndex}. + * @example Report a model's joint names. + * entityID = Entities.addEntity({ + * type: "Model", + * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * rotation: MyAvatar.orientation, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Script.setTimeout(function () { + * // Joint data aren't available until after the model has loaded. + * var jointNames = Entities.getJointNames(entityID); + * print("Joint names: " + JSON.stringify(jointNames)); + * }, 2000); + */ + // FIXME move to a renderable entity interface Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + /**jsdoc + * Get the IDs of entities, overlays, and avatars that are directly parented to an entity. To get all descendants of an + * entity, recurse on the IDs returned by the function. + * @function Entities.getChildrenIDs + * @param {Uuid} parentID - The ID of the entity to get the children IDs of. + * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID + * entity. Does not include children's children, etc. The array is empty if no children can be found or + * parentID cannot be found. + * @example Report the children of an entity. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * var grandChild = createEntity("Grandchild", Vec3.sum(position, { x: 0, y: -2, z: 0 }), child); + * + * var children = Entities.getChildrenIDs(root); + * print("Children of root: " + JSON.stringify(children)); // Only the child entity. + */ Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); + + /**jsdoc + * Get the IDs of entities, overlays, and avatars that are directly parented to an entity, overlay, or avatar model's joint. + * @function Entities.getChildrenIDsOfJoint + * @param {Uuid} parentID - The ID of the entity, overlay, or avatar to get the children IDs of. + * @param {number} jointIndex - Integer number of the model joint to get the children IDs of. + * @returns {Uuid[]} An array of entity, overlay, and avatar IDs that are parented directly to the parentID + * entity, overlay, or avatar at the jointIndex joint. Does not include children's children, etc. The + * array is empty if no children can be found or parentID cannot be found. + * @example Report the children of your avatar's right hand. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * + * Entities.editEntity(root, { + * parentID: MyAvatar.sessionUUID, + * parentJointIndex: MyAvatar.getJointIndex("RightHand") + * }); + * + * var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, MyAvatar.getJointIndex("RightHand")); + * print("Children of hand: " + JSON.stringify(children)); // Only the root entity. + */ Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); + + /**jsdoc + * Check whether an entity or overlay has an entity as an ancestor (parent, parent's parent, etc.). + * @function Entities.isChildOfParent + * @param {Uuid} childID - The ID of the child entity or overlay to test for being a child, grandchild, etc. + * @param {Uuid} parentID - The ID of the parent entity to test for being a parent, grandparent, etc. + * @returns {boolean} true if the childID entity or overlay has the parentID entity + * as a parent or grandparent etc., otherwise false. + * @example Check that a grandchild entity is a child of its grandparent. + * function createEntity(description, position, parent) { + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: position, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * print(description + ": " + entity); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * var root = createEntity("Root", position, Uuid.NULL); + * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); + * var grandChild = createEntity("Grandchild", Vec3.sum(position, { x: 0, y: -2, z: 0 }), child); + * + * print("grandChild has root as parent: " + Entities.isChildOfParent(grandChild, root)); // true + */ Q_INVOKABLE bool isChildOfParent(QUuid childID, QUuid parentID); - Q_INVOKABLE QString getNestableType(QUuid id); + /**jsdoc + * Get the type — entity, overlay, or avatar — of an in-world item. + * @function Entities.getNestableType + * @param {Uuid} entityID - The ID of the item to get the type of. + * @returns {string} The type of the item: "entity" if the item is an entity, "overlay" if the + * the item is an overlay, "avatar" if the item is an avatar; otherwise "unknown" if the item + * cannot be found. + * @example Print some nestable types. + * var entity = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -2 })), + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * print(Entities.getNestableType(entity)); // "entity" + * print(Entities.getNestableType(Uuid.generate())); // "unknown" + */ + Q_INVOKABLE QString getNestableType(QUuid entityID); + /**jsdoc + * Get the ID of the {@link Entities.EntityType|Web} entity that has keyboard focus. + * @function Entities.getKeyboardFocusEntity + * @returns {Uuid} The ID of the {@link Entities.EntityType|Web} entity that has focus, if any, otherwise null. + */ Q_INVOKABLE QUuid getKeyboardFocusEntity() const; + + /**jsdoc + * Set the {@link Entities.EntityType|Web} entity that has keyboard focus. + * @function Entities.setKeyboardFocusEntity + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity to set keyboard focus to. Use + * null or {@link Uuid|Uuid.NULL} to unset keyboard focus from an entity. + */ Q_INVOKABLE void setKeyboardFocusEntity(const EntityItemID& id); + + /**jsdoc + * Emit a {@link Entities.mousePressOnEntity|mousePressOnEntity} event. + * @function Entities.sendMousePressOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMousePressOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.mouseMoveOnEntity|mouseMoveOnEntity} event. + * @function Entities.sendMouseMoveOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMouseMoveOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.mouseReleaseOnEntity|mouseReleaseOnEntity} event. + * @function Entities.sendMouseReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendMouseReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Emit a {@link Entities.clickDownOnEntity|clickDownOnEntity} event. + * @function Entities.sendClickDownOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendClickDownOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.holdingClickOnEntity|holdingClickOnEntity} event. + * @function Entities.sendHoldingClickOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoldingClickOnEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.clickReleaseOnEntity|clickReleaseOnEntity} event. + * @function Entities.sendClickReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendClickReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Emit a {@link Entities.hoverEnterEntity|hoverEnterEntity} event. + * @function Entities.sendHoverEnterEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverEnterEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.hoverOverEntity|hoverOverEntity} event. + * @function Entities.sendHoverOverEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverOverEntity(const EntityItemID& id, const PointerEvent& event); + + /**jsdoc + * Emit a {@link Entities.hoverLeaveEntity|hoverLeaveEntity} event. + * @function Entities.sendHoverLeaveEntity + * @param {Uuid} entityID - The ID of the entity to emit the event for. + * @param {PointerEvent} event - The event details. + */ Q_INVOKABLE void sendHoverLeaveEntity(const EntityItemID& id, const PointerEvent& event); + /**jsdoc + * Check whether an entity wants hand controller pointer events. For example, a {@link Entities.EntityType|Web} entity does + * but a {@link Entities.EntityType|Shape} entity doesn't. + * @function Entities.wantsHandControllerPointerEvents + * @param {Uuid} entityID - The ID of the entity. + * @returns {boolean} true if the entity can be found and it wants hand controller pointer events, otherwise + * false. + */ Q_INVOKABLE bool wantsHandControllerPointerEvents(QUuid id); + /**jsdoc + * Send a script event over a {@link Entities.EntityType|Web} entity's EventBridge to the Web page's scripts. + * @function Entities.emitScriptEvent + * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity. + * @param {string} message - The message to send. + * @todo This function is currently not implemented. + */ Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); + /**jsdoc + * Check whether an axis-aligned box and a capsule intersect. + * @function Entities.AABoxIntersectsCapsule + * @param {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box. + * @param {Vec3} dimensions - The dimensions of the AA box. + * @param {Vec3} start - One end of the capsule. + * @param {Vec3} end - The other end of the capsule. + * @param {number} radius - The radiues of the capsule. + * @returns {boolean} true if the AA box and capsule intersect, otherwise false. + */ Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); + /**jsdoc + * Get the meshes in a {@link Entities.EntityType|Model} or {@link Entities.EntityType|PolyVox} entity. + * @function Entities.getMeshes + * @param {Uuid} entityID - The ID of the Model or PolyVox entity to get the meshes of. + * @param {Entities~getMeshesCallback} callback - The function to call upon completion. + * @deprecated Use the {@link Graphics} API instead. + */ + /**jsdoc + * Called when {@link Entities.getMeshes} is complete. + * @callback Entities~getMeshesCallback + * @param {MeshProxy[]} meshes - If success< is true, a {@link MeshProxy} per mesh in the + * Model or PolyVox entity; otherwise undefined. + * @param {boolean} success - true if the {@link Entities.getMeshes} call was successful, false + * otherwise. The call may be unsuccessful if the requested entity could not be found. + * @deprecated Use the {@link Graphics} API instead. + */ // FIXME move to a renderable entity interface Q_INVOKABLE void getMeshes(QUuid entityID, QScriptValue callback); /**jsdoc - * Returns object to world transform, excluding scale - * + * Get the object to world transform, excluding scale, of an entity. * @function Entities.getEntityTransform - * @param {EntityID} entityID The ID of the entity whose transform is to be returned - * @return {Mat4} Entity's object to world transform, excluding scale + * @param {Uuid} entityID - The ID of the entity. + * @returns {Mat4} The entity's object to world transform excluding scale (i.e., translation and rotation, with scale of 1) + * if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. + * @example Position and rotation in an entity's world transform. + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -2 })); + * var orientation = MyAvatar.orientation; + * print("Position: " + JSON.stringify(position)); + * print("Orientation: " + JSON.stringify(orientation)); + * + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: position, + * rotation: orientation, + * dimensions: Vec3.HALF, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var transform = Entities.getEntityTransform(entityID); + * print("Transform: " + JSON.stringify(transform)); + * print("Translation: " + JSON.stringify(Mat4.extractTranslation(transform))); // Same as position. + * print("Rotation: " + JSON.stringify(Mat4.extractRotation(transform))); // Same as orientation. + * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityTransform(const QUuid& entityID); - /**jsdoc - * Returns object to world transform, excluding scale - * + * Get the object to parent transform, excluding scale, of an entity. * @function Entities.getEntityLocalTransform - * @param {EntityID} entityID The ID of the entity whose local transform is to be returned - * @return {Mat4} Entity's object to parent transform, excluding scale - */ + * @param {Uuid} entityID - The ID of the entity. + * @returns {Mat4} The entity's object to parent transform excluding scale (i.e., translation and rotation, with scale of + * 1) if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. + * @example Position and rotation in an entity's local transform. + * function createEntity(position, rotation, parent) { + * var entity = Entities.addEntity({ + * type: "Box", + * position: position, + * rotation: rotation, + * dimensions: Vec3.HALF, + * parentID: parent, + * lifetime: 300 // Delete after 5 minutes. + * }); + * return entity; + * } + * + * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -5 })); + * + * var parent = createEntity(position, MyAvatar.orientation, Uuid.NULL); + * + * var childTranslation = { x: 0, y: -1.5, z: 0 }; + * var childRotation = Quat.fromPitchYawRollDegrees(0, 45, 0); + * var child = createEntity(Vec3.sum(position, childTranslation), Quat.multiply(childRotation, MyAvatar.orientation), parent); + * + * var transform = Entities.getEntityLocalTransform(child); + * print("Transform: " + JSON.stringify(transform)); + * print("Translation: " + JSON.stringify(Mat4.extractTranslation(transform))); // childTranslation + * print("Rotation: " + JSON.stringify(Quat.safeEulerAngles(Mat4.extractRotation(transform)))); // childRotation + * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); - /**jsdoc - * Return the Static Certificate JSON for the specified {EntityID}. - * @return {QByteArray} The Static Certificate JSON for the specified entity. + * Get the static certificate for an entity. The static certificate contains static properties of the item which cannot + * be altered. + * @function Entities.getStaticCertificateJSON + * @param {Uuid} entityID - The ID of the entity to get the static certificate for. + * @returns {string} The entity's static certificate as a JSON string if the entity can be found, otherwise an empty string. */ Q_INVOKABLE QString getStaticCertificateJSON(const QUuid& entityID); + + /**jsdoc + * Verify the entity's proof of provenance, i.e., that the entity's certificateID property was produced by + * High Fidelity signing the entity's static certificate JSON. + * @function Entities.verifyStaticCertificateProperties + * @param {Uuid} entityID - The ID of the entity to verify. + * @returns {boolean} true if the entity can be found an its certificateID property is present + * and its value matches the entity's static certificate JSON; otherwise false. + */ Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); signals: + /**jsdoc + * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered + * for a collision with an avatar. + * @function Entities.collisionWithEntity + * @param {Uuid} idA - The ID of one entity in the collision. For an entity script, this is the ID of the entity containing + * the script. + * @param {Uuid} idB - The ID of the other entity in the collision. + * @param {Collision} collision - The details of the collision. + * @returns {Signal} + * @example Change the color of an entity when it collides with another entity. + * var entityScript = (function () { + * function randomInteger(min, max) { + * return Math.floor(Math.random() * (max - min + 1)) + min; + * } + * + * this.collisionWithEntity = function (myID, otherID, collision) { + * Entities.editEntity(myID, { + * color: { + * red: randomInteger(128, 255), + * green: randomInteger(128, 255), + * blue: randomInteger(128, 255) + * } + * }); + * }; + * }); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 128, green: 128, blue: 128 }, + * gravity: { x: 0, y: -9.8, z: 0 }, + * velocity: { x: 0, y: 0.1, z: 0 }, // Kick off physics. + * dynamic: true, + * collisionless: false, // So that collision events are generated. + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + /**jsdoc + * Triggered when your ability to change the locked property of entities changes. + * @function Entities.canAdjustLocksChanged + * @param {boolean} canAdjustLocks - true if the script can change the locked property of an + * entity, otherwise false. + * @returns {Signal} + * @example Report when your ability to change locks changes. + * function onCanAdjustLocksChanged(canAdjustLocks) { + * print("You can adjust entity locks: " + canAdjustLocks); + * } + * Entities.canAdjustLocksChanged.connect(onCanAdjustLocksChanged); + */ void canAdjustLocksChanged(bool canAdjustLocks); + + /**jsdoc + * Triggered when your ability to rez (create) entities changes. + * @function Entities.canRezChanged + * @param {boolean} canRez - true if the script can rez (create) entities, otherwise false. + * @returns {Signal} + */ void canRezChanged(bool canRez); - void canRezTmpChanged(bool canRez); - void canRezCertifiedChanged(bool canRez); - void canRezTmpCertifiedChanged(bool canRez); + + /**jsdoc + * Triggered when your ability to rez (create) temporary entities changes. Temporary entities are entities with a finite + * lifetime property value set. + * @function Entities.canRezTmpChanged + * @param {boolean} canRezTmp - true if the script can rez (create) temporary entities, otherwise + * false. + * @returns {Signal} + */ + void canRezTmpChanged(bool canRezTmp); + + /**jsdoc + * Triggered when your ability to rez (create) certified entities changes. Certified entities are entities that have PoP + * certificates. + * @function Entities.canRezCertifiedChanged + * @param {boolean} canRezCertified - true if the script can rez (create) certified entities, otherwise + * false. + * @returns {Signal} + */ + void canRezCertifiedChanged(bool canRezCertified); + + /**jsdoc + * Triggered when your ability to rez (create) temporary certified entities changes. Temporary entities are entities with a + * finite lifetime property value set. Certified entities are entities that have PoP certificates. + * @function Entities.canRezTmpCertifiedChanged + * @param {boolean} canRezTmpCertified - true if the script can rez (create) temporary certified entities, + * otherwise false. + * @returns {Signal} + */ + void canRezTmpCertifiedChanged(bool canRezTmpCertified); + + /**jsdoc + * Triggered when your ability to make changes to the asset server's assets changes. + * @function Entities.canWriteAssetsChanged + * @param {boolean} canWriteAssets - true if the script can change the ? property of an entity, + * otherwise false. + * @returns {Signal} + */ void canWriteAssetsChanged(bool canWriteAssets); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully + * pressed while its laser is on an entity. + * @function Entities.mousePressOnEntity + * @param {Uuid} entityID - The ID of the entity that was pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + * @example Report when an entity is clicked with the mouse or laser. + * function onMousePressOnEntity(entityID, event) { + * print("Clicked on entity: " + entityID); + * } + * + * Entities.mousePressOnEntity.connect(onMousePressOnEntity); + */ void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is double-clicked while the mouse cursor is on an entity. + * @function Entities.mousePressOnEntity + * @param {Uuid} entityID - The ID of the entity that was double-pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseDoublePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered while the mouse cursor or controller laser moves on an entity. + * @function Entities.mouseMoveOnEntity + * @param {Uuid} entityID - The ID of the entity that was moved on. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is released after clicking on an entity or the controller trigger is partly or fully + * released after pressing on an entity, even if the mouse pointer or controller laser has moved off the entity. + * @function Entities.mouseReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally pressed. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is not on an entity. + * @function Entities.mousePressOffEntity + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mousePressOffEntity(); + + /**jsdoc + * Triggered when a mouse button is double-clicked while the mouse cursor is not on an entity. + * @function Entities.mouseDoublePressOffEntity + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void mouseDoublePressOffEntity(); + + /**jsdoc + * Triggered when a mouse button is clicked while the mouse cursor is on an entity. Note: Not triggered by controller. + * @function Entities.clickDownOnEntity + * @param {Uuid} entityID - The ID of the entity that was clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered while a mouse button continues to be held after clicking an entity, even if the mouse cursor has + * moved off the entity. Note: Not triggered by controller. + * @function Entities.holdingClickOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when a mouse button is released after clicking on an entity, even if the mouse cursor has moved off the + * entity. Note: Not triggered by controller. + * @function Entities.clickReleaseOnEntity + * @param {Uuid} entityID - The ID of the entity that was originally clicked. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void clickReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + /**jsdoc + * Triggered when the mouse cursor or controller laser starts hovering on an entity. + * @function Entities.hoverEnterEntity + * @param {Uuid} entityID - The ID of the entity that is being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverEnterEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Repeatedly triggered when the mouse cursor or controller laser moves while hovering over an entity. + * @function Entities.hoverOverEntity + * @param {Uuid} entityID - The ID of the entity that is being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverOverEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when the mouse cursor or controller laser stops hovering over an entity. + * @function Entities.hoverLeaveEntity + * @param {Uuid} entityID - The ID of the entity that was being hovered. + * @param {PointerEvent} event - Details of the event. + * @returns {Signal} + */ void hoverLeaveEntity(const EntityItemID& entityItemID, const PointerEvent& event); + + /**jsdoc + * Triggered when an avatar enters an entity. + * @function Entities.enterEntity + * @param {Uuid} entityID - The ID of the entity that the avatar entered. + * @returns {Signal} + * @example Change the color of an entity when an avatar enters or leaves. + * var entityScript = (function () { + * this.enterEntity = function (entityID) { + * print("Enter entity"); + * Entities.editEntity(entityID, { + * color: { red: 255, green: 64, blue: 64 }, + * }); + * }; + * this.leaveEntity = function (entityID) { + * print("Leave entity"); + * Entities.editEntity(entityID, { + * color: { red: 128, green: 128, blue: 128 }, + * }); + * }; + * }); + * + * var entityID = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 3, y: 3, z: 3 }, + * color: { red: 128, green: 128, blue: 128 }, + * collisionless: true, // So that avatar can walk through entity. + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ void enterEntity(const EntityItemID& entityItemID); + + /**jsdoc + * Triggered when an avatar leaves an entity. + * @function Entities.leaveEntity + * @param {Uuid} entityID - The ID of the entity that the avatar left. + * @returns {Signal} + */ void leaveEntity(const EntityItemID& entityItemID); + + /**jsdoc + * Triggered when an entity is deleted. + * @function Entities.deletingEntity + * @param {Uuid} entityID - The ID of the entity deleted. + * @returns {Signal} + * @example Report when an entity is deleted. + * Entities.deletingEntity.connect(function (entityID) { + * print("Deleted entity: " + entityID); + * }); + */ void deletingEntity(const EntityItemID& entityID); + + /**jsdoc + * Triggered when an entity is added to Interface's local in-memory tree of entities it knows about. This may occur when + * entities are loaded upon visiting a domain, when the user rotates their view so that more entities become visible, and + * when a domain or client-only entity is added (e.g., by {@Entities.addEntity|addEntity}). + * @function Entities.addingEntity + * @param {Uuid} entityID - The ID of the entity added. + * @returns {Signal} + * @example Report when an entity is added. + * Entities.addingEntity.connect(function (entityID) { + * print("Added entity: " + entityID); + * }); + */ void addingEntity(const EntityItemID& entityID); + + /**jsdoc + * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about + * is cleared. + * @function Entities.clearingEntities + * @returns {Signal} + * @example Report when Interfaces's entity tree is cleared. + * Entities.clearingEntities.connect(function () { + * print("Entities cleared"); + * }); + */ void clearingEntities(); + + /**jsdoc + * @function Entities.debitEnergySource + * @param {number} value - The amount to debit. + * @returns {Signal} + * @deprecated This function is deprecated and will soon be removed. + */ void debitEnergySource(float value); + /**jsdoc + * Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the + * script's EventBridge. + * @function Entities.webEventReceived + * @param {Uuid} entityID - The ID of the entity that event was received from. + * @param {string} message - The message received. + * @returns {Signal} + */ void webEventReceived(const EntityItemID& entityItemID, const QVariant& message); protected: diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 62011c6e26..0e2fca8180 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -35,6 +35,56 @@ typedef EntityItemPointer (*EntityTypeFactory)(const EntityItemID& entityID, con class EntityTypes { public: + /**jsdoc + *

An entity may be one of the following types:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescriptionProperties
"Box"A rectangular prism. This is a synonym of "Shape" for the case + * where the entity's shape property value is "Cube".
+ * If an entity is created with its type + * set to "Box" it will always be created with a shape property value of + * "Cube". If an entity of type Shape or Sphere has its shape set + * to "Cube" then its type will be reported as "Box". + *
{@link Entities.EntityProperties-Box|EntityProperties-Box}
"Light"A local lighting effect.{@link Entities.EntityProperties-Light|EntityProperties-Light}
"Line"A sequence of one or more simple straight lines.{@link Entities.EntityProperties-Line|EntityProperties-Line}
"Material"Modifies the existing materials on Model entities, Shape entities (albedo + * only), {@link Overlays.OverlayType|model overlays}, and avatars.{@link Entities.EntityProperties-Material|EntityProperties-Material}
"Model"A mesh model from an FBX or OBJ file.{@link Entities.EntityProperties-Model|EntityProperties-Model}
"ParticleEffect"A particle system that can be used to simulate things such as fire, + * smoke, snow, magic spells, etc.{@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect}
"PolyLine"A sequence of one or more textured straight lines.{@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine}
"PolyVox"A set of textured voxels.{@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}
"Shape"A basic entity such as a cube. + * See also, the "Box" and "Sphere" entity types.{@link Entities.EntityProperties-Shape|EntityProperties-Shape}
"Sphere"A sphere. This is a synonym of "Shape" for the case + * where the entity's shape property value is "Sphere".
+ * If an entity is created with its type + * set to "Sphere" it will always be created with a shape property value of + * "Sphere". If an entity of type Box or Shape has its shape set + * to "Sphere" then its type will be reported as "Sphere". + *
{@link Entities.EntityProperties-Sphere|EntityProperties-Sphere}
"Text"A pane of text oriented in space.{@link Entities.EntityProperties-Text|EntityProperties-Text}
"Web"A browsable Web page.{@link Entities.EntityProperties-Web|EntityProperties-Web}
"Zone"A volume of lighting effects and avatar permissions.{@link Entities.EntityProperties-Zone|EntityProperties-Zone}
+ * @typedef {string} Entities.EntityType + */ typedef enum EntityType_t { Unknown, Model, diff --git a/libraries/entities/src/HazePropertyGroup.h b/libraries/entities/src/HazePropertyGroup.h index 939391caf9..e992aefbf3 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -40,6 +40,35 @@ static const float INITIAL_HAZE_BACKGROUND_BLEND{ 0.0f }; static const float INITIAL_KEY_LIGHT_RANGE{ 1000.0f }; static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; +// FIXME: Document hazeAttenuationKeyLight, hazeKeyLightRange, and hazeKeyLightAltitude once they're working and are provided +// in the Create app's UI. +/**jsdoc + * Haze is defined by the following properties. + * @typedef {object} Entities.Haze + * + * @property {number} hazeRange=1000 - The horizontal distance at which visibility is reduced to 95%; i.e., 95% of each pixel's + * color is haze. + * @property {Color} hazeColor=128,154,179 - The color of the haze when looking away from the key light. + * @property {boolean} hazeEnableGlare=false - If true then the haze is colored with glare from the key light; + * hazeGlareColor and hazeGlareAngle are used. + * @property {Color} hazeGlareColor=255,299,179 - The color of the haze when looking towards the key light. + * @property {number} hazeGlareAngle=20 - The angle in degrees across the circle around the key light that the glare color and + * haze color are blended 50/50. + * + * @property {boolean} hazeAltitudeEffect=false - If true then haze decreases with altitude as defined by the + * entity's local coordinate system; hazeBaseRef and
hazeCeiling
are used. + * @property {number} hazeBaseRef=0 - The y-axis value in the entity's local coordinate system at which the haze density starts + * reducing with altitude. + * @property {number} hazeCeiling=200 - The y-axis value in the entity's local coordinate system at which the haze density has + * reduced to 5%. + * + * @property {number} hazeBackgroundBlend=0 - The proportion of the skybox image to show through the haze: 0.0 + * displays no skybox image; 1.0 displays no haze. + * + * @property {boolean} hazeAttenuateKeyLight=false - Currently not supported. + * @property {number} hazeKeyLightRange=1000 - Currently not supported. + * @property {number} hazeKeyLightAltitude=200 - Currently not supported. + */ class HazePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index 5e13a6afa6..2be33787de 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -27,6 +27,16 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * A key light is defined by the following properties. + * @typedef {object} Entities.KeyLight + * @property {Color} color=255,255,255 - The color of the light. + * @property {number} intensity=1 - The intensity of the light. + * @property {Vec3} direction=0,-1,0 - The direction the light is shining. + * @property {boolean} castShadows=false - If true then shadows are cast. Shadows are cast by avatars, plus + * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities that have their + * {@link Entities.EntityProperties|canCastShadows} property set to true. + */ class KeyLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 90982fe448..0ddfe3e8cc 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -59,6 +59,25 @@ class PolyVoxEntityItem : public EntityItem { virtual int getOnCount() const { return 0; } + /**jsdoc + *

A PolyVoxSurfaceStyle may be one of the following:

+ * + * + * + * + * + * + * + * + * + * + *
ValueTypeDescription
0Marching cubes.Chamfered edges. Open volume. + * Joins neighboring PolyVox entities reasonably well.
1Cubic.Square edges. Open volume. + * Joins neighboring PolyVox entities cleanly.
2Edged cubic.Square edges. Enclosed volume. + * Joins neighboring PolyVox entities cleanly.
3Edged marching cubes.Chamfered edges. Enclosed volume. + * Doesn't join neighboring PolyVox entities.
+ * @typedef {number} Entities.PolyVoxSurfaceStyle + */ enum PolyVoxSurfaceStyle { SURFACE_MARCHING_CUBES, SURFACE_CUBIC, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index db3d6798be..60ba429938 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -20,6 +20,33 @@ #include "ShapeEntityItem.h" namespace entity { + + /**jsdoc + *

A Shape, Box, or Sphere {@link Entities.EntityType|EntityType} may display as + * one of the following geometrical shapes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDimensionsNotes
"Circle"2DA circle oriented in 3D.
"Cube"3D
"Cone"3D
"Cylinder"3D
"Dodecahedron"3D
"Hexagon"3DA hexagonal prism.
"Icosahedron"3D
"Octagon"3DAn octagonal prism.
"Octahedron"3D
"Quad"2DA square oriented in 3D.
"Sphere"3D
"Tetrahedron"3D
"Torus"3DNot implemented.
"Triangle"3DA triangular prism.
+ * @typedef {string} Entities.Shape + */ static const std::array shapeStrings { { "Triangle", "Quad", @@ -32,7 +59,7 @@ namespace entity { "Octahedron", "Dodecahedron", "Icosahedron", - "Torus", + "Torus", // Not implemented yet. "Cone", "Cylinder" } }; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 46d696f979..97202afcf7 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -105,7 +105,7 @@ public: protected: - float _alpha { 1 }; + float _alpha { 1 }; // FIXME: This property is not used. rgbColor _color; entity::Shape _shape { entity::Shape::Sphere }; diff --git a/libraries/entities/src/SkyboxPropertyGroup.h b/libraries/entities/src/SkyboxPropertyGroup.h index 8298a7b74e..d7b422bf11 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.h +++ b/libraries/entities/src/SkyboxPropertyGroup.h @@ -27,6 +27,13 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +/**jsdoc + * A skybox is defined by the following properties. + * @typedef {object} Entities.Skybox + * @property {Color} color=0,0,0 - Sets the color of the sky if url is "", otherwise modifies the + * color of the cube map image. + * @property {string} url="" - A cube map image that is used to render the sky. + */ class SkyboxPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp index 5029b489bc..511f253193 100644 --- a/libraries/fbx/src/FBXWriter.cpp +++ b/libraries/fbx/src/FBXWriter.cpp @@ -161,23 +161,19 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { case QMetaType::QString: { auto bytes = prop.toString().toUtf8(); - out << 'S'; - out << bytes.length(); - out << bytes; + out.device()->write("S", 1); out << (int32_t)bytes.size(); out.writeRawData(bytes, bytes.size()); break; } - case QMetaType::QByteArray: - { - auto bytes = prop.toByteArray(); - out.device()->write("S", 1); - out << (int32_t)bytes.size(); - out.writeRawData(bytes, bytes.size()); - break; - } - + { + auto bytes = prop.toByteArray(); + out.device()->write("S", 1); + out << (int32_t)bytes.size(); + out.writeRawData(bytes, bytes.size()); + break; + } default: { if (prop.canConvert>()) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 63fb93ae46..caac08f777 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -643,13 +643,13 @@ done: } -FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { +FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); QBuffer buffer { &model }; buffer.open(QIODevice::ReadOnly); - FBXGeometry* geometryPtr = new FBXGeometry(); - FBXGeometry& geometry = *geometryPtr; + auto geometryPtr { std::make_shared() }; + FBXGeometry& geometry { *geometryPtr }; OBJTokenizer tokenizer { &buffer }; float scaleGuess = 1.0f; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index df356fada8..13ddc6e21c 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -85,7 +85,7 @@ public: QString currentMaterialName; QHash materials; - FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); + FBXGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); private: QUrl _url; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 641155ad6c..9bb02d678d 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -56,12 +56,16 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setResourceBuffer), (&::gpu::gl::GLBackend::do_setResourceTexture), (&::gpu::gl::GLBackend::do_setResourceTextureTable), + (&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture), (&::gpu::gl::GLBackend::do_setFramebuffer), + (&::gpu::gl::GLBackend::do_setFramebufferSwapChain), (&::gpu::gl::GLBackend::do_clearFramebuffer), (&::gpu::gl::GLBackend::do_blit), (&::gpu::gl::GLBackend::do_generateTextureMips), + (&::gpu::gl::GLBackend::do_advance), + (&::gpu::gl::GLBackend::do_beginQuery), (&::gpu::gl::GLBackend::do_endQuery), (&::gpu::gl::GLBackend::do_getQuery), @@ -715,9 +719,13 @@ void GLBackend::recycle() const { Texture::KtxStorage::releaseOpenKtxFiles(); } -void GLBackend::setCameraCorrection(const Mat4& correction) { +void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { + auto invCorrection = glm::inverse(correction); + auto invPrevView = glm::inverse(prevRenderView); + _transform._correction.prevView = (reset ? Mat4() : prevRenderView); + _transform._correction.prevViewInverse = (reset ? Mat4() : invPrevView); _transform._correction.correction = correction; - _transform._correction.correctionInverse = glm::inverse(correction); + _transform._correction.correctionInverse = invCorrection; _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); _pipeline._cameraCorrectionBuffer._buffer->flush(); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index feff3e2710..6355137a19 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -71,7 +71,7 @@ public: virtual ~GLBackend(); - void setCameraCorrection(const Mat4& correction); + void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false); void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState @@ -136,15 +136,19 @@ public: virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; virtual void do_setResourceTextureTable(const Batch& batch, size_t paramOffset); + virtual void do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) final; // Pipeline Stage virtual void do_setPipeline(const Batch& batch, size_t paramOffset) final; // Output stage virtual void do_setFramebuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) final; virtual void do_clearFramebuffer(const Batch& batch, size_t paramOffset) final; virtual void do_blit(const Batch& batch, size_t paramOffset) = 0; + virtual void do_advance(const Batch& batch, size_t paramOffset) final; + // Query section virtual void do_beginQuery(const Batch& batch, size_t paramOffset) final; virtual void do_endQuery(const Batch& batch, size_t paramOffset) final; @@ -259,6 +263,8 @@ protected: void setupStereoSide(int side); #endif + virtual void setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture); + virtual void setFramebuffer(const FramebufferPointer& framebuffer); virtual void initInput() final; virtual void killInput() final; virtual void syncInputStateCache() final; @@ -317,9 +323,12 @@ protected: // Allows for correction of the camera pose to account for changes // between the time when a was recorded and the time(s) when it is // executed + // Prev is the previous correction used at previous frame struct CameraCorrection { - Mat4 correction; - Mat4 correctionInverse; + mat4 correction; + mat4 correctionInverse; + mat4 prevView; + mat4 prevViewInverse; }; struct TransformStageState { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index ab17a6295d..2285b0e486 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -42,6 +42,19 @@ void GLBackend::resetOutputStage() { void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + setFramebuffer(framebuffer); +} + +void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) { + auto swapChain = batch._swapChains.get(batch._params[paramOffset]._uint); + if (swapChain) { + auto index = batch._params[paramOffset + 1]._uint; + FramebufferPointer framebuffer = static_cast(swapChain.get())->get(index); + setFramebuffer(framebuffer); + } +} + +void GLBackend::setFramebuffer(const FramebufferPointer& framebuffer) { if (_output._framebuffer != framebuffer) { auto newFBO = getFramebufferID(framebuffer); if (_output._drawFBO != newFBO) { @@ -52,6 +65,13 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { } } +void GLBackend::do_advance(const Batch& batch, size_t paramOffset) { + auto ringbuffer = batch._swapChains.get(batch._params[paramOffset]._uint); + if (ringbuffer) { + ringbuffer->advance(); + } +} + void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { qWarning("Clear without scissor in stereo mode"); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 03b53bc5cb..58fcc51605 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -258,6 +258,31 @@ void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourc releaseResourceTexture(slot); return; } + setResourceTexture(slot, resourceTexture); +} + +void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) { + GLuint slot = batch._params[paramOffset + 1]._uint; + if (slot >= (GLuint)MAX_NUM_RESOURCE_TEXTURES) { + qCDebug(gpugllogging) << "GLBackend::do_setResourceFramebufferSwapChainTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); + return; + } + + SwapChainPointer swapChain = batch._swapChains.get(batch._params[paramOffset + 0]._uint); + + if (!swapChain) { + releaseResourceTexture(slot); + return; + } + auto index = batch._params[paramOffset + 2]._uint; + auto renderBufferSlot = batch._params[paramOffset + 3]._uint; + FramebufferPointer resourceFramebuffer = static_cast(swapChain.get())->get(index); + TexturePointer resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); + + setResourceTexture(slot, resourceTexture); +} + +void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture) { // check cache before thinking if (_resource._textures[slot] == resourceTexture) { return; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp index 3bc2bc3812..3692e3dcb9 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp @@ -36,13 +36,15 @@ R"SHADER( // Shader domain -static const size_t NUM_SHADER_DOMAINS = 2; +static const size_t NUM_SHADER_DOMAINS = 3; +static_assert(Shader::Type::NUM_DOMAINS == NUM_SHADER_DOMAINS, "GL shader domains must equal defined GPU shader domains"); // GL Shader type enums // Must match the order of type specified in gpu::Shader::Type static const std::array SHADER_DOMAINS{ { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, + GL_GEOMETRY_SHADER, } }; // Domain specific defines @@ -50,6 +52,7 @@ static const std::array SHADER_DOMAINS{ { static const std::array DOMAIN_DEFINES{ { "#define GPU_VERTEX_SHADER", "#define GPU_PIXEL_SHADER", + "#define GPU_GEOMETRY_SHADER", } }; // Stereo specific defines diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index f286a5cca9..72aaa5aa66 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -105,7 +105,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? Transform result; - _view.mult(result, _view, _correction.correction); + _view.mult(result, _view, _correction.correctionInverse); if (_skybox) { result.setTranslation(vec3()); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp index 96f170ec1f..99a9afb4c4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTexelFormat.cpp @@ -12,12 +12,17 @@ using namespace gpu; using namespace gpu::gl; #if defined(USE_GLES) +// Missing GL formats #define GL_R16 GL_R16_EXT #define GL_R16_SNORM GL_R16_SNORM_EXT +#define GL_RG16 GL_RG16_EXT +#define GL_RG16_SNORM GL_RG16_SNORM_EXT +#define GL_RGBA2 GL_RGBA8 #define GL_RGBA16 GL_RGBA16_EXT #define GL_RGBA16_SNORM GL_RGBA16_SNORM_EXT #define GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT32_OES -#define GL_RGBA2 GL_RGBA8 +#define GL_SLUMINANCE8_EXT GL_SLUMINANCE8_NV +// Missing GL compressed formats #define GL_COMPRESSED_RED_RGTC1 0x8DBB #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC #define GL_COMPRESSED_RG_RGTC2 0x8DBD @@ -26,7 +31,6 @@ using namespace gpu::gl; #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F -#define GL_SLUMINANCE8_EXT GL_SLUMINANCE8_NV #endif bool GLTexelFormat::isCompressed() const { @@ -208,7 +212,51 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::RGB: case gpu::RGBA: case gpu::XY: - result = GL_RG8; + switch (dstFormat.getType()) { + case gpu::UINT32: + result = GL_RG32UI; + break; + case gpu::INT32: + result = GL_RG32I; + break; + case gpu::FLOAT: + result = GL_RG32F; + break; + case gpu::UINT16: + result = GL_RG16UI; + break; + case gpu::INT16: + result = GL_RG16I; + break; + case gpu::NUINT16: + result = GL_RG16; + break; + case gpu::NINT16: + result = GL_RG16_SNORM; + break; + case gpu::HALF: + result = GL_RG16F; + break; + case gpu::UINT8: + result = GL_RG8UI; + break; + case gpu::INT8: + result = GL_RG8I; + break; + case gpu::NUINT8: + result = GL_RG8; + break; + case gpu::NINT8: + result = GL_RG8_SNORM; + break; + case gpu::NUINT32: + case gpu::NINT32: + case gpu::NUINT2: + case gpu::NINT2_10_10_10: + case gpu::COMPRESSED: + case gpu::NUM_TYPES: // quiet compiler + Q_UNREACHABLE(); + } break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; @@ -560,11 +608,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::NUINT8: { if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { -#if defined(USE_GLES) - texel.internalFormat = GL_SLUMINANCE8_NV; -#else texel.internalFormat = GL_SLUMINANCE8_EXT; -#endif } else { texel.internalFormat = GL_R8; } @@ -598,21 +642,13 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it -#if defined(USE_GLES) - texel.internalFormat = GL_DEPTH_COMPONENT32_OES; -#else texel.internalFormat = GL_DEPTH_COMPONENT32; -#endif switch (dstFormat.getType()) { case gpu::UINT32: case gpu::INT32: case gpu::NUINT32: case gpu::NINT32: { -#if defined(USE_GLES) - texel.internalFormat = GL_DEPTH_COMPONENT32_OES; -#else texel.internalFormat = GL_DEPTH_COMPONENT32; -#endif break; } case gpu::FLOAT: { @@ -662,7 +698,52 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::RGB: case gpu::RGBA: case gpu::XY: - texel.internalFormat = GL_RG8; + switch (dstFormat.getType()) { + case gpu::UINT32: + texel.internalFormat = GL_RG32UI; + break; + case gpu::INT32: + texel.internalFormat = GL_RG32I; + break; + case gpu::FLOAT: + texel.internalFormat = GL_RG32F; + break; + case gpu::UINT16: + texel.internalFormat = GL_RG16UI; + break; + case gpu::INT16: + texel.internalFormat = GL_RG16I; + break; + case gpu::NUINT16: + texel.internalFormat = GL_RG16; + break; + case gpu::NINT16: + texel.internalFormat = GL_RG16_SNORM; + break; + case gpu::HALF: + texel.type = GL_FLOAT; + texel.internalFormat = GL_RG16F; + break; + case gpu::UINT8: + texel.internalFormat = GL_RG8UI; + break; + case gpu::INT8: + texel.internalFormat = GL_RG8I; + break; + case gpu::NUINT8: + texel.internalFormat = GL_RG8; + break; + case gpu::NINT8: + texel.internalFormat = GL_RG8_SNORM; + break; + case gpu::NUINT32: + case gpu::NINT32: + case gpu::NUINT2: + case gpu::NINT2_10_10_10: + case gpu::COMPRESSED: + case gpu::NUM_TYPES: // quiet compiler + Q_UNREACHABLE(); + } break; default: qCWarning(gpugllogging) << "Unknown combination of texel format"; diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 550175ef75..db4941c163 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -45,7 +45,12 @@ size_t Batch::_dataMax { BATCH_PREALLOCATE_MIN }; size_t Batch::_objectsMax { BATCH_PREALLOCATE_MIN }; size_t Batch::_drawCallInfosMax { BATCH_PREALLOCATE_MIN }; -Batch::Batch() { +Batch::Batch(const char* name) { +#ifdef DEBUG + if (name) { + _name = name; + } +#endif _commands.reserve(_commandsMax); _commandOffsets.reserve(_commandOffsetsMax); _params.reserve(_paramsMax); @@ -56,6 +61,9 @@ Batch::Batch() { Batch::Batch(const Batch& batch_) { Batch& batch = *const_cast(&batch_); +#ifdef DEBUG + _name = batch_._name; +#endif _commands.swap(batch._commands); _commandOffsets.swap(batch._commandOffsets); _params.swap(batch._params); @@ -72,6 +80,7 @@ Batch::Batch(const Batch& batch_) { _transforms._items.swap(batch._transforms._items); _pipelines._items.swap(batch._pipelines._items); _framebuffers._items.swap(batch._framebuffers._items); + _swapChains._items.swap(batch._swapChains._items); _drawCallInfos.swap(batch._drawCallInfos); _queries._items.swap(batch._queries._items); _lambdas._items.swap(batch._lambdas._items); @@ -110,6 +119,7 @@ void Batch::clear() { _transforms.clear(); _pipelines.clear(); _framebuffers.clear(); + _swapChains.clear(); _objects.clear(); _drawCallInfos.clear(); } @@ -335,6 +345,15 @@ void Batch::setResourceTextureTable(const TextureTablePointer& textureTable, uin _params.emplace_back(slot); } +void Batch::setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex, unsigned int renderBufferSlot) { + ADD_COMMAND(setResourceFramebufferSwapChainTexture); + + _params.emplace_back(_swapChains.cache(framebuffer)); + _params.emplace_back(slot); + _params.emplace_back(swapChainIndex); + _params.emplace_back(renderBufferSlot); +} + void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { ADD_COMMAND(setFramebuffer); @@ -342,6 +361,19 @@ void Batch::setFramebuffer(const FramebufferPointer& framebuffer) { } +void Batch::setFramebufferSwapChain(const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex) { + ADD_COMMAND(setFramebufferSwapChain); + + _params.emplace_back(_swapChains.cache(framebuffer)); + _params.emplace_back(swapChainIndex); +} + +void Batch::advance(const SwapChainPointer& swapChain) { + ADD_COMMAND(advance); + + _params.emplace_back(_swapChains.cache(swapChain)); +} + void Batch::clearFramebuffer(Framebuffer::Masks targets, const Vec4& color, float depth, int stencil, bool enableScissor) { ADD_COMMAND(clearFramebuffer); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 2db1b9b420..96b234295d 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -91,7 +91,7 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - Batch(); + Batch(const char* name = nullptr); Batch(const Batch& batch); ~Batch(); @@ -187,12 +187,15 @@ public: void setResourceTexture(uint32 slot, const TexturePointer& texture); void setResourceTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView - void setResourceTextureTable(const TextureTablePointer& table, uint32 slot = 0); + void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swpaChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView // Ouput Stage void setFramebuffer(const FramebufferPointer& framebuffer); - + void setFramebufferSwapChain(const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex); + + void advance(const SwapChainPointer& swapChain); + // Clear framebuffer layers // Targets can be any of the render buffers contained in the currnetly bound Framebuffer // Optionally the scissor test can be enabled locally for this command and to restrict the clearing command to the pixels contained in the scissor rectangle @@ -301,12 +304,16 @@ public: COMMAND_setResourceBuffer, COMMAND_setResourceTexture, COMMAND_setResourceTextureTable, + COMMAND_setResourceFramebufferSwapChainTexture, COMMAND_setFramebuffer, + COMMAND_setFramebufferSwapChain, COMMAND_clearFramebuffer, COMMAND_blit, COMMAND_generateTextureMips, + COMMAND_advance, + COMMAND_beginQuery, COMMAND_endQuery, COMMAND_getQuery, @@ -425,6 +432,7 @@ public: typedef Cache::Vector TransformCaches; typedef Cache::Vector PipelineCaches; typedef Cache::Vector FramebufferCaches; + typedef Cache::Vector SwapChainCaches; typedef Cache::Vector QueryCaches; typedef Cache::Vector StringCaches; typedef Cache>::Vector LambdaCache; @@ -480,6 +488,7 @@ public: TransformCaches _transforms; PipelineCaches _pipelines; FramebufferCaches _framebuffers; + SwapChainCaches _swapChains; QueryCaches _queries; LambdaCache _lambdas; StringCaches _profileRanges; @@ -491,6 +500,11 @@ public: bool _enableSkybox { false }; protected: + +#ifdef DEBUG + std::string _name; +#endif + friend class Context; friend class Frame; diff --git a/libraries/gpu/src/gpu/Buffer.cpp b/libraries/gpu/src/gpu/Buffer.cpp index 7ed2411c71..ebb768e597 100644 --- a/libraries/gpu/src/gpu/Buffer.cpp +++ b/libraries/gpu/src/gpu/Buffer.cpp @@ -98,8 +98,9 @@ Buffer::Update::Update(const Buffer& parent) : buffer(parent) { void Buffer::Update::apply() const { // Make sure we're loaded in order - ++buffer._applyUpdateCount; - assert(buffer._applyUpdateCount.load() == updateNumber); + buffer._applyUpdateCount++; + assert(buffer._applyUpdateCount == updateNumber); + const auto pageSize = buffer._pages._pageSize; buffer._renderSysmem.resize(size); buffer._renderPages.accommodate(size); diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index 526cc3dd46..d70e588f4d 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -11,18 +11,45 @@ <@if not GPU_COLOR_SLH@> <@def GPU_COLOR_SLH@> -float sRGBFloatToLinear(float value) { +// Linear ====> linear RGB +// sRGB ======> standard RGB with gamma of 2.2 +// YCoCg =====> Luma (Y) chrominance green (Cg) and chrominance orange (Co) +// https://software.intel.com/en-us/node/503873 + +float color_scalar_sRGBToLinear(float value) { const float SRGB_ELBOW = 0.04045; return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); } -vec3 colorToLinearRGB(vec3 srgb) { - return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); +vec3 color_sRGBToLinear(vec3 srgb) { + return vec3(color_scalar_sRGBToLinear(srgb.r), color_scalar_sRGBToLinear(srgb.g), color_scalar_sRGBToLinear(srgb.b)); } -vec4 colorToLinearRGBA(vec4 srgba) { - return vec4(colorToLinearRGB(srgba.xyz), srgba.w); +vec4 color_sRGBAToLinear(vec4 srgba) { + return vec4(color_sRGBToLinear(srgba.xyz), srgba.w); +} + +vec3 color_LinearToYCoCg(vec3 rgb) { + // Y = R/4 + G/2 + B/4 + // Co = R/2 - B/2 + // Cg = -R/4 + G/2 - B/4 + return vec3( + rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0, + rgb.x/2.0 - rgb.z/2.0, + -rgb.x/4.0 + rgb.y/2.0 - rgb.z/4.0 + ); +} + +vec3 color_YCoCgToLinear(vec3 ycocg) { + // R = Y + Co - Cg + // G = Y + Cg + // B = Y - Co - Cg + return clamp(vec3( + ycocg.x + ycocg.y - ycocg.z, + ycocg.x + ycocg.z, + ycocg.x - ycocg.y - ycocg.z + ), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index d7d86c3ef7..e1b68c88ca 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -53,11 +53,12 @@ const std::string& Context::getBackendVersion() const { return _backend->getVersion(); } -void Context::beginFrame(const glm::mat4& renderPose) { +void Context::beginFrame(const glm::mat4& renderView, const glm::mat4& renderPose) { assert(!_frameActive); _frameActive = true; _currentFrame = std::make_shared(); _currentFrame->pose = renderPose; + _currentFrame->view = renderView; if (!_frameRangeTimer) { _frameRangeTimer = std::make_shared("gpu::Context::Frame"); @@ -108,7 +109,7 @@ void Context::executeFrame(const FramePointer& frame) const { consumeFrameUpdates(frame); _backend->setStereoState(frame->stereoState); { - Batch beginBatch; + Batch beginBatch("Context::executeFrame::begin"); _frameRangeTimer->begin(beginBatch); _backend->render(beginBatch); @@ -117,7 +118,7 @@ void Context::executeFrame(const FramePointer& frame) const { _backend->render(batch); } - Batch endBatch; + Batch endBatch("Context::executeFrame::end"); _frameRangeTimer->end(endBatch); _backend->render(endBatch); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 195565f438..2df7de2331 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -161,7 +161,7 @@ public: const std::string& getBackendVersion() const; - void beginFrame(const glm::mat4& renderPose = glm::mat4()); + void beginFrame(const glm::mat4& renderView = glm::mat4(), const glm::mat4& renderPose = glm::mat4()); void appendFrameBatch(Batch& batch); FramePointer endFrame(); @@ -274,8 +274,8 @@ protected: typedef std::shared_ptr ContextPointer; template -void doInBatch(std::shared_ptr context, F f) { - gpu::Batch batch; +void doInBatch(const char* name, std::shared_ptr context, F f) { + gpu::Batch batch(name); f(batch); context->appendFrameBatch(batch); } diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 1ed77a26b7..441e3c5b5d 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -28,6 +28,8 @@ namespace gpu { StereoState stereoState; uint32_t frameIndex{ 0 }; + /// The view matrix used for rendering the frame, only applicable for HMDs + Mat4 view; /// The sensor pose used for rendering the frame, only applicable for HMDs Mat4 pose; /// The collection of batches which make up the frame diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index f470cc8aa9..fbbec50a28 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -12,6 +12,7 @@ #define hifi_gpu_Framebuffer_h #include "Texture.h" +#include "ResourceSwapChain.h" #include class Transform; // Texcood transform util @@ -177,6 +178,8 @@ protected: Framebuffer() {} }; typedef std::shared_ptr FramebufferPointer; +typedef ResourceSwapChain FramebufferSwapChain; +typedef std::shared_ptr FramebufferSwapChainPointer; } diff --git a/libraries/gpu/src/gpu/ResourceSwapChain.h b/libraries/gpu/src/gpu/ResourceSwapChain.h new file mode 100644 index 0000000000..7b46b35521 --- /dev/null +++ b/libraries/gpu/src/gpu/ResourceSwapChain.h @@ -0,0 +1,62 @@ +// +// Created by Olivier Prat on 2018/02/19 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_ResourceSwapChain_h +#define hifi_gpu_ResourceSwapChain_h + +#include +#include + +namespace gpu { + class SwapChain { + public: + + SwapChain(unsigned int size = 2U) : _size{ size } {} + virtual ~SwapChain() {} + + void advance() { + _frontIndex = (_frontIndex + 1) % _size; + } + + unsigned int getSize() const { return _size; } + + protected: + unsigned int _size; + unsigned int _frontIndex{ 0U }; + + }; + typedef std::shared_ptr SwapChainPointer; + + template + class ResourceSwapChain : public SwapChain { + public: + + enum { + MAX_SIZE = 4 + }; + + using Type = R; + using TypePointer = std::shared_ptr; + + ResourceSwapChain(unsigned int size = 2U) : SwapChain{ size } {} + + void reset() { + for (auto& ptr : _resources) { + ptr.reset(); + } + } + + TypePointer& edit(unsigned int index) { return _resources[(index + _frontIndex) % _size]; } + const TypePointer& get(unsigned int index) const { return _resources[(index + _frontIndex) % _size]; } + + private: + + std::array _resources; + }; +} + +#endif diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 84c6cb6fa8..526352804b 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -38,7 +38,7 @@ public slots: * Returns a model reference object associated with the specified UUID ({@link EntityID}, {@link OverlayID}, or {@link AvatarID}). * * @function Graphics.getModel - * @param {UUID} The objectID of the model whose meshes are to be retrieved. + * @param {UUID} entityID - The objectID of the model whose meshes are to be retrieved. * @return {Graphics.Model} the resulting Model object */ scriptable::ScriptableModelPointer getModel(QUuid uuid); diff --git a/libraries/graphics/src/graphics/Geometry.h b/libraries/graphics/src/graphics/Geometry.h index a75fb1bf62..485542d26b 100755 --- a/libraries/graphics/src/graphics/Geometry.h +++ b/libraries/graphics/src/graphics/Geometry.h @@ -74,7 +74,7 @@ public: size_t getNumIndices() const { return _indexBuffer.getNumElements(); } // Access vertex position value - const Vec3& getPos3(Index index) const { return _vertexBuffer.get(index); } + const Vec3& getPos(Index index) const { return _vertexBuffer.get(index); } enum Topology { POINTS = 0, diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 96f1daba8c..6f94e7592c 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -20,9 +20,10 @@ InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { new KeyboardMouseDevice(), - new TouchscreenDevice(), #if defined(Q_OS_ANDROID) new TouchscreenVirtualPadDevice(), +#else + new TouchscreenDevice(), // Touchscreen and Controller Scripts take care on Android #endif nullptr }; diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index e323ed8fbc..0a28368e9e 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -50,8 +50,8 @@ void TouchscreenVirtualPadDevice::init() { _screenDPIProvided = eventScreen->physicalDotsPerInch(); _screenDPI = eventScreen->physicalDotsPerInch(); - _fixedRadius = _screenDPI * 256 / 534; - _fixedRadiusForCalc = _fixedRadius - _screenDPI * 105 / 534; // 105 is the radius of the stick circle + _fixedRadius = _screenDPI * 0.5f * VirtualPad::Manager::PIXEL_SIZE / VirtualPad::Manager::DPI; + _fixedRadiusForCalc = _fixedRadius - _screenDPI * VirtualPad::Manager::STICK_RADIUS / VirtualPad::Manager::DPI; } auto& virtualPadManager = VirtualPad::Manager::instance(); @@ -60,6 +60,8 @@ void TouchscreenVirtualPadDevice::init() { if (_fixedPosition) { virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled } + + KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin } void TouchscreenVirtualPadDevice::setupFixedCenter(VirtualPad::Manager& virtualPadManager, bool force) { @@ -69,12 +71,12 @@ void TouchscreenVirtualPadDevice::setupFixedCenter(VirtualPad::Manager& virtualP if (_extraBottomMargin == virtualPadManager.extraBottomMargin() && !force) return; // Our only criteria to decide a center change is the bottom margin _extraBottomMargin = virtualPadManager.extraBottomMargin(); - float margin = _screenDPI * 59 / 534; // 59px is for our 'base' of 534dpi (Pixel XL or Huawei Mate 9 Pro) + float margin = _screenDPI * VirtualPad::Manager::BASE_MARGIN / VirtualPad::Manager::DPI; QScreen* eventScreen = qApp->primaryScreen(); // do not call every time _fixedCenterPosition = glm::vec2( _fixedRadius + margin, eventScreen->size().height() - margin - _fixedRadius - _extraBottomMargin); - _firstTouchLeftPoint = _fixedCenterPosition; - virtualPadManager.getLeftVirtualPad()->setFirstTouch(_firstTouchLeftPoint); + _moveRefTouchPoint = _fixedCenterPosition; + virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); } float clip(float n, float lower, float upper) { @@ -82,10 +84,10 @@ float clip(float n, float lower, float upper) { } glm::vec2 TouchscreenVirtualPadDevice::clippedPointInCircle(float radius, glm::vec2 origin, glm::vec2 touchPoint) { - float deltaX = touchPoint.x-origin.x; - float deltaY = touchPoint.y-origin.y; + float deltaX = touchPoint.x - origin.x; + float deltaY = touchPoint.y - origin.y; - float distance = sqrt(pow(deltaX,2)+pow(deltaY,2)); + float distance = sqrt(pow(deltaX, 2) + pow(deltaY, 2)); // First case, inside the boundaires, just use the distance if (distance <= radius) { @@ -99,59 +101,52 @@ glm::vec2 TouchscreenVirtualPadDevice::clippedPointInCircle(float radius, glm::v // Third case, calculate point in circumference // line formula - float m = deltaY/deltaX; + float m = deltaY / deltaX; float b = touchPoint.y - m * touchPoint.x; // quadtratic coefs of circumference and line intersection - float qa = pow(m,2)+1; - float qb = 2 * ( m * b - origin.x - origin.y * m ); - float qc = powf(origin.x, 2) - powf(radius,2) + b * b - 2 * b * origin.y + powf(origin.y, 2); + float qa = powf(m, 2.0f) + 1.0f; + float qb = 2.0f * ( m * b - origin.x - origin.y * m); + float qc = powf(origin.x, 2.0f) - powf(radius, 2.0f) + b * b - 2.0f * b * origin.y + powf(origin.y, 2.0f); - float discr = qb * qb - 4 * qa * qc; - float discrSign = deltaX>0?1.0:-1.0; + float discr = qb * qb - 4.0f * qa * qc; + float discrSign = deltaX > 0.0f ? 1.0f : - 1.0f; - float finalX = (- qb + discrSign * sqrtf(discr)) / (2 * qa); + float finalX = (-qb + discrSign * sqrtf(discr)) / (2.0f * qa); float finalY = m * finalX + b; return vec2(finalX, finalY); } -void TouchscreenVirtualPadDevice::processInputUseCircleMethod(VirtualPad::Manager& virtualPadManager) { - vec2 clippedPoint = clippedPointInCircle(_fixedRadiusForCalc, _firstTouchLeftPoint, _currentTouchLeftPoint); +void TouchscreenVirtualPadDevice::processInputDeviceForMove(VirtualPad::Manager& virtualPadManager) { + vec2 clippedPoint = clippedPointInCircle(_fixedRadiusForCalc, _moveRefTouchPoint, _moveCurrentTouchPoint); - _inputDevice->_axisStateMap[controller::LX] = (clippedPoint.x - _firstTouchLeftPoint.x) / _fixedRadiusForCalc; - _inputDevice->_axisStateMap[controller::LY] = (clippedPoint.y - _firstTouchLeftPoint.y) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LX] = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LY] = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; - virtualPadManager.getLeftVirtualPad()->setFirstTouch(_firstTouchLeftPoint); + virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); virtualPadManager.getLeftVirtualPad()->setCurrentTouch(clippedPoint); virtualPadManager.getLeftVirtualPad()->setBeingTouched(true); virtualPadManager.getLeftVirtualPad()->setShown(true); // If touched, show in any mode (fixed joystick position or non-fixed) } -void TouchscreenVirtualPadDevice::processInputUseSquareMethod(VirtualPad::Manager& virtualPadManager) { - float leftDistanceScaleX, leftDistanceScaleY; - leftDistanceScaleX = (_currentTouchLeftPoint.x - _firstTouchLeftPoint.x) / _screenDPIScale.x; - leftDistanceScaleY = (_currentTouchLeftPoint.y - _firstTouchLeftPoint.y) / _screenDPIScale.y; +void TouchscreenVirtualPadDevice::processInputDeviceForView() { + float rightDistanceScaleX, rightDistanceScaleY; + rightDistanceScaleX = (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _screenDPIScale.x; + rightDistanceScaleY = (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _screenDPIScale.y; - leftDistanceScaleX = clip(leftDistanceScaleX, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - leftDistanceScaleY = clip(leftDistanceScaleY, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); + rightDistanceScaleX = clip(rightDistanceScaleX, -_viewStickRadiusInches, _viewStickRadiusInches); + rightDistanceScaleY = clip(rightDistanceScaleY, -_viewStickRadiusInches, _viewStickRadiusInches); // NOW BETWEEN -1 1 - leftDistanceScaleX /= STICK_RADIUS_INCHES; - leftDistanceScaleY /= STICK_RADIUS_INCHES; + rightDistanceScaleX /= _viewStickRadiusInches; + rightDistanceScaleY /= _viewStickRadiusInches; - _inputDevice->_axisStateMap[controller::LX] = leftDistanceScaleX; - _inputDevice->_axisStateMap[controller::LY] = leftDistanceScaleY; + _inputDevice->_axisStateMap[controller::RX] = rightDistanceScaleX; + _inputDevice->_axisStateMap[controller::RY] = rightDistanceScaleY; - /* Shared variables for stick rendering (clipped to the stick radius)*/ - // Prevent this for being done when not in first person view - virtualPadManager.getLeftVirtualPad()->setFirstTouch(_firstTouchLeftPoint); - virtualPadManager.getLeftVirtualPad()->setCurrentTouch( - glm::vec2(clip(_currentTouchLeftPoint.x, -STICK_RADIUS_INCHES * _screenDPIScale.x + _firstTouchLeftPoint.x, STICK_RADIUS_INCHES * _screenDPIScale.x + _firstTouchLeftPoint.x), - clip(_currentTouchLeftPoint.y, -STICK_RADIUS_INCHES * _screenDPIScale.y + _firstTouchLeftPoint.y, STICK_RADIUS_INCHES * _screenDPIScale.y + _firstTouchLeftPoint.y)) - ); - virtualPadManager.getLeftVirtualPad()->setBeingTouched(true); - virtualPadManager.getLeftVirtualPad()->setShown(true); // If touched, show in any mode (fixed joystick position or non-fixed) + // after use, save last touch point as ref + _viewRefTouchPoint = _viewCurrentTouchPoint; } void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { @@ -163,8 +158,8 @@ void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller auto& virtualPadManager = VirtualPad::Manager::instance(); setupFixedCenter(virtualPadManager); - if (_validTouchLeft) { - processInputUseCircleMethod(virtualPadManager); + if (_moveHasValidTouch) { + processInputDeviceForMove(virtualPadManager); } else { virtualPadManager.getLeftVirtualPad()->setBeingTouched(false); if (_fixedPosition) { @@ -175,20 +170,8 @@ void TouchscreenVirtualPadDevice::pluginUpdate(float deltaTime, const controller } } - if (_validTouchRight) { - float rightDistanceScaleX, rightDistanceScaleY; - rightDistanceScaleX = (_currentTouchRightPoint.x - _firstTouchRightPoint.x) / _screenDPIScale.x; - rightDistanceScaleY = (_currentTouchRightPoint.y - _firstTouchRightPoint.y) / _screenDPIScale.y; - - rightDistanceScaleX = clip(rightDistanceScaleX, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - rightDistanceScaleY = clip(rightDistanceScaleY, -STICK_RADIUS_INCHES, STICK_RADIUS_INCHES); - - // NOW BETWEEN -1 1 - rightDistanceScaleX /= STICK_RADIUS_INCHES; - rightDistanceScaleY /= STICK_RADIUS_INCHES; - - _inputDevice->_axisStateMap[controller::RX] = rightDistanceScaleX; - _inputDevice->_axisStateMap[controller::RY] = rightDistanceScaleY; + if (_viewHasValidTouch) { + processInputDeviceForView(); } } @@ -228,70 +211,133 @@ void TouchscreenVirtualPadDevice::touchBeginEvent(const QTouchEvent* event) { // touch begin here is a big begin -> begins both pads? maybe it does nothing debugPoints(event, " BEGIN ++++++++++++++++"); auto& virtualPadManager = VirtualPad::Manager::instance(); - if (!virtualPadManager.isEnabled()) { + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { return; } - KeyboardMouseDevice::enableTouch(false); } void TouchscreenVirtualPadDevice::touchEndEvent(const QTouchEvent* event) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (!virtualPadManager.isEnabled()) { - touchLeftEnd(); - touchRightEnd(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + moveTouchEnd(); + viewTouchEnd(); return; } // touch end here is a big reset -> resets both pads _touchPointCount = 0; - KeyboardMouseDevice::enableTouch(true); + _unusedTouches.clear(); debugPoints(event, " END ----------------"); - touchLeftEnd(); - touchRightEnd(); + moveTouchEnd(); + viewTouchEnd(); _inputDevice->_axisStateMap.clear(); } +void TouchscreenVirtualPadDevice::processUnusedTouches(std::map unusedTouchesInEvent) { + std::vector touchesToDelete; + for (auto const& touchEntry : _unusedTouches) { + if (!unusedTouchesInEvent.count(touchEntry.first)) { + touchesToDelete.push_back(touchEntry.first); + } + } + for (int touchToDelete : touchesToDelete) { + _unusedTouches.erase(touchToDelete); + } + + for (auto const& touchEntry : unusedTouchesInEvent) { + if (!_unusedTouches.count(touchEntry.first)) { + _unusedTouches[touchEntry.first] = touchEntry.second; + } + } + +} + void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (!virtualPadManager.isEnabled()) { - touchLeftEnd(); - touchRightEnd(); + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + moveTouchEnd(); + viewTouchEnd(); return; } _touchPointCount = event->touchPoints().count(); const QList& tPoints = event->touchPoints(); - bool leftTouchFound = false; - bool rightTouchFound = false; + bool moveTouchFound = false; + bool viewTouchFound = false; + + int idxMoveStartingPointCandidate = -1; + int idxViewStartingPointCandidate = -1; + + glm::vec2 thisPoint; + int thisPointId; + std::map unusedTouchesInEvent; + for (int i = 0; i < _touchPointCount; ++i) { - glm::vec2 thisPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); - if (_validTouchLeft) { - leftTouchFound = true; - touchLeftUpdate(thisPoint); - } else if (touchLeftBeginPointIsValid(thisPoint)) { - if (!leftTouchFound) { - leftTouchFound = true; - touchLeftBegin(thisPoint); - } + thisPoint.x = tPoints[i].pos().x(); + thisPoint.y = tPoints[i].pos().y(); + thisPointId = tPoints[i].id(); + + if (!moveTouchFound && _moveHasValidTouch && _moveCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + moveTouchFound = true; + moveTouchUpdate(thisPoint); + continue; + } + + if (!viewTouchFound && _viewHasValidTouch && _viewCurrentTouchId == thisPointId) { + // valid if it's an ongoing touch + viewTouchFound = true; + viewTouchUpdate(thisPoint); + continue; + } + + if (!moveTouchFound && idxMoveStartingPointCandidate == -1 && moveTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == MOVE )) { + idxMoveStartingPointCandidate = i; + continue; + } + + if (!viewTouchFound && idxViewStartingPointCandidate == -1 && viewTouchBeginIsValid(thisPoint) && + (!_unusedTouches.count(thisPointId) || _unusedTouches[thisPointId] == VIEW )) { + idxViewStartingPointCandidate = i; + continue; + } + + if (moveTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = MOVE; + } else if (viewTouchBeginIsValid(thisPoint)) { + unusedTouchesInEvent[thisPointId] = VIEW; + } + + } + + processUnusedTouches(unusedTouchesInEvent); + + if (!moveTouchFound) { + if (idxMoveStartingPointCandidate != -1) { + _moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id(); + _unusedTouches.erase(_moveCurrentTouchId); + moveTouchBegin(thisPoint); } else { - if (!rightTouchFound) { - rightTouchFound = true; - if (!_validTouchRight) { - touchRightBegin(thisPoint); - } else { - touchRightUpdate(thisPoint); - } - } + moveTouchEnd(); } } - if (!leftTouchFound) { - touchLeftEnd(); - } - if (!rightTouchFound) { - touchRightEnd(); + if (!viewTouchFound) { + if (idxViewStartingPointCandidate != -1) { + _viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id(); + _unusedTouches.erase(_viewCurrentTouchId); + viewTouchBegin(thisPoint); + } else { + viewTouchEnd(); + } } + } -bool TouchscreenVirtualPadDevice::touchLeftBeginPointIsValid(glm::vec2 touchPoint) { +bool TouchscreenVirtualPadDevice::viewTouchBeginIsValid(glm::vec2 touchPoint) { + return !moveTouchBeginIsValid(touchPoint); +} + +bool TouchscreenVirtualPadDevice::moveTouchBeginIsValid(glm::vec2 touchPoint) { if (_fixedPosition) { // inside circle return pow(touchPoint.x - _fixedCenterPosition.x,2.0) + pow(touchPoint.y - _fixedCenterPosition.y, 2.0) < pow(_fixedRadius, 2.0); @@ -301,45 +347,46 @@ bool TouchscreenVirtualPadDevice::touchLeftBeginPointIsValid(glm::vec2 touchPoin } } -void TouchscreenVirtualPadDevice::touchLeftBegin(glm::vec2 touchPoint) { +void TouchscreenVirtualPadDevice::moveTouchBegin(glm::vec2 touchPoint) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (virtualPadManager.isEnabled()) { + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { if (_fixedPosition) { - _firstTouchLeftPoint = _fixedCenterPosition; + _moveRefTouchPoint = _fixedCenterPosition; } else { - _firstTouchLeftPoint = touchPoint; + _moveRefTouchPoint = touchPoint; } - _validTouchLeft = true; + _moveHasValidTouch = true; } } -void TouchscreenVirtualPadDevice::touchLeftUpdate(glm::vec2 touchPoint) { - _currentTouchLeftPoint = touchPoint; +void TouchscreenVirtualPadDevice::moveTouchUpdate(glm::vec2 touchPoint) { + _moveCurrentTouchPoint = touchPoint; } -void TouchscreenVirtualPadDevice::touchLeftEnd() { - if (_validTouchLeft) { // do stuff once - _validTouchLeft = false; +void TouchscreenVirtualPadDevice::moveTouchEnd() { + if (_moveHasValidTouch) { // do stuff once + _moveHasValidTouch = false; _inputDevice->_axisStateMap[controller::LX] = 0; _inputDevice->_axisStateMap[controller::LY] = 0; } } -void TouchscreenVirtualPadDevice::touchRightBegin(glm::vec2 touchPoint) { +void TouchscreenVirtualPadDevice::viewTouchBegin(glm::vec2 touchPoint) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (virtualPadManager.isEnabled()) { - _firstTouchRightPoint = touchPoint; - _validTouchRight = true; + if (virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { + _viewRefTouchPoint = touchPoint; + _viewCurrentTouchPoint = touchPoint; + _viewHasValidTouch = true; } } -void TouchscreenVirtualPadDevice::touchRightUpdate(glm::vec2 touchPoint) { - _currentTouchRightPoint = touchPoint; +void TouchscreenVirtualPadDevice::viewTouchUpdate(glm::vec2 touchPoint) { + _viewCurrentTouchPoint = touchPoint; } -void TouchscreenVirtualPadDevice::touchRightEnd() { - if (_validTouchRight) { // do stuff once - _validTouchRight = false; +void TouchscreenVirtualPadDevice::viewTouchEnd() { + if (_viewHasValidTouch) { // do stuff once + _viewHasValidTouch = false; _inputDevice->_axisStateMap[controller::RX] = 0; _inputDevice->_axisStateMap[controller::RY] = 0; } @@ -347,7 +394,7 @@ void TouchscreenVirtualPadDevice::touchRightEnd() { void TouchscreenVirtualPadDevice::touchGestureEvent(const QGestureEvent* event) { auto& virtualPadManager = VirtualPad::Manager::instance(); - if (!virtualPadManager.isEnabled()) { + if (!virtualPadManager.isEnabled() && !virtualPadManager.isHidden()) { return; } if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index d5019da805..3540c6d909 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -20,8 +20,6 @@ class QTouchEvent; class QGestureEvent; -const float STICK_RADIUS_INCHES = .3f; - class TouchscreenVirtualPadDevice : public InputPlugin { Q_OBJECT public: @@ -62,17 +60,30 @@ public: const std::shared_ptr& getInputDevice() const { return _inputDevice; } protected: + + enum TouchType { + MOVE = 1, + VIEW + }; + float _lastPinchScale; float _pinchScale; float _screenDPI; qreal _screenDPIProvided; glm::vec2 _screenDPIScale; - bool _validTouchLeft; - glm::vec2 _firstTouchLeftPoint; - glm::vec2 _currentTouchLeftPoint; - bool _validTouchRight; - glm::vec2 _firstTouchRightPoint; - glm::vec2 _currentTouchRightPoint; + + bool _moveHasValidTouch; + glm::vec2 _moveRefTouchPoint; + glm::vec2 _moveCurrentTouchPoint; + int _moveCurrentTouchId; + + bool _viewHasValidTouch; + glm::vec2 _viewRefTouchPoint; + glm::vec2 _viewCurrentTouchPoint; + int _viewCurrentTouchId; + + std::map _unusedTouches; + int _touchPointCount; int _screenWidthCenter; std::shared_ptr _inputDevice { std::make_shared() }; @@ -83,18 +94,26 @@ protected: float _fixedRadiusForCalc; int _extraBottomMargin {0}; - void touchLeftBegin(glm::vec2 touchPoint); - void touchLeftUpdate(glm::vec2 touchPoint); - void touchLeftEnd(); - bool touchLeftBeginPointIsValid(glm::vec2 touchPoint); - void touchRightBegin(glm::vec2 touchPoint); - void touchRightUpdate(glm::vec2 touchPoint); - void touchRightEnd(); + float _viewStickRadiusInches {0.1333f}; // agreed default + + void moveTouchBegin(glm::vec2 touchPoint); + void moveTouchUpdate(glm::vec2 touchPoint); + void moveTouchEnd(); + bool moveTouchBeginIsValid(glm::vec2 touchPoint); + + void viewTouchBegin(glm::vec2 touchPoint); + void viewTouchUpdate(glm::vec2 touchPoint); + void viewTouchEnd(); + bool viewTouchBeginIsValid(glm::vec2 touchPoint); + void setupFixedCenter(VirtualPad::Manager& virtualPadManager, bool force = false); - void processInputUseCircleMethod(VirtualPad::Manager& virtualPadManager); - void processInputUseSquareMethod(VirtualPad::Manager& virtualPadManager); + void processInputDeviceForMove(VirtualPad::Manager& virtualPadManager); glm::vec2 clippedPointInCircle(float radius, glm::vec2 origin, glm::vec2 touchPoint); + + void processUnusedTouches(std::map unusedTouchesInEvent); + + void processInputDeviceForView(); // just for debug private: void debugPoints(const QTouchEvent* event, QString who); diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index 85f2f43652..cf3e255e0c 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -28,6 +28,25 @@ void NetworkMaterialResource::downloadFinished(const QByteArray& data) { finishedLoading(true); } +/**jsdoc + *

An RGB or SRGB color value.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
IndexTypeAttributesDefaultValue
0numberRed component value. Number in the range 0.01.0.
1numberGreen component value. Number in the range 0.01.0.
2numberBlue component value. Number in the range 0.01.0.
3boolean<optional>falseIf true then the color is an SRGB color.
+ * @typedef {array} RGBS + */ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB) { if (array.isArray()) { QJsonArray colorArray = array.toArray(); @@ -50,6 +69,12 @@ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& return false; } +/**jsdoc + * A material or set of materials such as may be used by a {@link Entities.EntityType|Material} entity. + * @typedef {object} MaterialResource + * @property {number} materialVersion=1 - The version of the material. Currently not used. + * @property {Material|Material[]} materials - The details of the material or materials. + */ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) { ParsedMaterials toReturn; if (!materialJSON.isNull() && materialJSON.isObject()) { @@ -83,6 +108,36 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater return toReturn; } +/**jsdoc + * A material such as may be used by a {@link Entities.EntityType|Material} entity. + * @typedef {object} Material + * @property {string} name="" - A name for the material. + * @property {string} model="hifi_pbr" - Currently not used. + * @property {Vec3Color|RGBS} emissive - The emissive color, i.e., the color that the material emits. A {@link Vec3Color} value + * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. + * @property {number} opacity=1.0 - The opacity, 0.01.0. + * @property {boolean} unlit=false - If true, the material is not lit. + * @property {Vec3Color|RGBS} albedo - The albedo color. A {@link Vec3Color} value is treated as sRGB. A {@link RGBS} value can + * be either RGB or sRGB. + * @property {number} roughness - The roughness, 0.01.0. + * @property {number} metallic - The metallicness, 0.01.0. + * @property {number} scattering - The scattering, 0.01.0. + * @property {string} emissiveMap - URL of emissive texture image. + * @property {string} albedoMap - URL of albedo texture image. + * @property {string} opacityMap - URL of opacity texture image. Set value the same as the albedoMap value for + * transparency. + * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. + * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. + * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. + * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. + * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. + * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. + * @property {string} occlusionMap - URL of occlusion texture image. + * @property {string} scatteringMap - URL of scattering texture image. Only used if normalMap or + * bumpMap is specified. + * @property {string} lightMap - URL of light map texture image. Currently not used. + */ +// Note: See MaterialEntityItem.h for default values used in practice. std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { std::string name = ""; std::shared_ptr material = std::make_shared(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index d21e942581..b253ac4af3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -190,11 +190,11 @@ void GeometryReader::run() { throw QString("empty geometry, possibly due to an unsupported FBX version"); } } else if (_url.path().toLower().endsWith(".obj")) { - fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _combineParts, _url)); + fbxGeometry = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); } else if (_url.path().toLower().endsWith(".obj.gz")) { QByteArray uncompressedData; - if (gunzip(_data, uncompressedData)) { - fbxGeometry.reset(OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url)); + if (gunzip(_data, uncompressedData)){ + fbxGeometry = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); } else { throw QString("failed to decompress .obj.gz"); } diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp index 741478789e..8f3d1ffec1 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.cpp @@ -21,7 +21,7 @@ int SimpleMeshProxy::getNumVertices() const { return (int)_mesh->getNumVertices(); } -glm::vec3 SimpleMeshProxy::getPos3(int index) const { - return _mesh->getPos3(index); +glm::vec3 SimpleMeshProxy::getPos(int index) const { + return _mesh->getPos(index); } diff --git a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h index 24c3fca27e..073eb1c00f 100644 --- a/libraries/model-networking/src/model-networking/SimpleMeshProxy.h +++ b/libraries/model-networking/src/model-networking/SimpleMeshProxy.h @@ -26,8 +26,8 @@ public: int getNumVertices() const override; - glm::vec3 getPos3(int index) const override; - + glm::vec3 getPos(int index) const override; + glm::vec3 getPos3(int index) const override { return getPos(index); } // deprecated protected: const MeshPointer _mesh; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index ffc9af5264..b03195041b 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -459,6 +459,10 @@ void NetworkTexture::makeRequest() { } +void NetworkTexture::handleLocalRequestCompleted() { + TextureCache::requestCompleted(_self); +} + void NetworkTexture::makeLocalRequest() { const QString scheme = _url.scheme(); QString path; @@ -468,6 +472,8 @@ void NetworkTexture::makeLocalRequest() { path = ":" + _url.path(); } + connect(this, &Resource::finished, this, &NetworkTexture::handleLocalRequestCompleted); + path = FileUtils::selectFile(path); auto storage = std::make_shared(path); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 080c2704c6..b2740e2ca1 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -71,6 +71,7 @@ public slots: protected: void makeRequest() override; void makeLocalRequest(); + Q_INVOKABLE void handleLocalRequestCompleted(); virtual bool isCacheable() const override { return _loaded; } diff --git a/libraries/networking/src/MessagesClient.h b/libraries/networking/src/MessagesClient.h index 6d0483fe9d..6ef3777d8c 100644 --- a/libraries/networking/src/MessagesClient.h +++ b/libraries/networking/src/MessagesClient.h @@ -23,6 +23,21 @@ #include "Node.h" #include "ReceivedMessage.h" +/**jsdoc + *

The Messages API enables text and data to be sent between scripts over named "channels". A channel can have an arbitrary + * name to help separate messaging between different sets of scripts.

+ * + *

Note: If you want to call a function in another script, you should use one of the following rather than + * sending a message:

+ *
    + *
  • {@link Entities.callEntityClientMethod}
  • + *
  • {@link Entities.callEntityMethod}
  • + *
  • {@link Entities.callEntityServerMethod}
  • + *
  • {@link Script.callEntityScriptMethod}
  • + *
+ * + * @namespace Messages + */ class MessagesClient : public QObject, public Dependency { Q_OBJECT public: @@ -30,10 +45,115 @@ public: void startThread(); + /**jsdoc + * Send a text message on a channel. + * @function Messages.sendMessage + * @param {string} channel - The channel to send the message on. + * @param {string} message - The message to send. + * @param {boolean} [localOnly=false] - If false then the message is sent to all Interface, client entity, + * server entity, and assignment client scripts in the domain.
+ * If true then: if sent from an Interface or client entity script it is received by all Interface and + * client entity scripts; if sent from a server entity script it is received by all entity server scripts; and if sent + * from an assignment client script it is received only by that same assignment client script. + * @example Send and receive a message. + * // Receiving script. + * var channelName = "com.highfidelity.example.messages-example"; + * + * function onMessageReceived(channel, message, sender, localOnly) { + * print("Message received:"); + * print("- channel: " + channel); + * print("- message: " + message); + * print("- sender: " + sender); + * print("- localOnly: " + localOnly); + * } + * + * Messages.subscribe(channelName); + * Messages.messageReceived.connect(onMessageReceived); + * + * Script.scriptEnding.connect(function () { + * Messages.messageReceived.disconnect(onMessageReceived); + * Messages.unsubscribe(channelName); + * }); + * + * + * // Sending script. + * var channelName = "com.highfidelity.example.messages-example"; + * var message = "Hello"; + * Messages.sendMessage(channelName, message); + */ Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); + + /**jsdoc + * Send a text message locally on a channel. + * This is the same as calling {@link Messages.sendMessage|sendMessage} with localOnly set to + * true. + * @function Messages.sendLocalMessage + * @param {string} channel - The channel to send the message on. + * @param {string} message - The message to send. + */ Q_INVOKABLE void sendLocalMessage(QString channel, QString message); + + /**jsdoc + * Send a data message on a channel. + * @function Messages.sendData + * @param {string} channel - The channel to send the data on. + * @param {object} data - The data to send. The data is handled as a byte stream, for example as may be provided via a + * JavaScript Int8Array object. + * @param {boolean} [localOnly=false] - If false then the message is sent to all Interface, client entity, + * server entity, and assignment client scripts in the domain.
+ * If true then: if sent from an Interface or client entity script it is received by all Interface and + * client entity scripts; if sent from a server entity script it is received by all entity server scripts; and if sent + * from an assignment client script it is received only by that same assignment client script. + * @example Send and receive data. + * // Receiving script. + * var channelName = "com.highfidelity.example.messages-example"; + * + * function onDataReceived(channel, data, sender, localOnly) { + * var int8data = new Int8Array(data); + * var dataAsString = ""; + * for (var i = 0; i < int8data.length; i++) { + * if (i > 0) { + * dataAsString += ", "; + * } + * dataAsString += int8data[i]; + * } + * print("Data received:"); + * print("- channel: " + channel); + * print("- data: " + dataAsString); + * print("- sender: " + sender); + * print("- localOnly: " + localOnly); + * } + * + * Messages.subscribe(channelName); + * Messages.dataReceived.connect(onDataReceived); + * + * Script.scriptEnding.connect(function () { + * Messages.dataReceived.disconnect(onDataReceived); + * Messages.unsubscribe(channelName); + * }); + * + * + * // Sending script. + * var channelName = "com.highfidelity.example.messages-example"; + * var int8data = new Int8Array([1, 1, 2, 3, 5, 8, 13]); + * Messages.sendData(channelName, int8data.buffer); + */ Q_INVOKABLE void sendData(QString channel, QByteArray data, bool localOnly = false); + + /**jsdoc + * Subscribe the scripting environment — Interface, the entity script server, or assignment client instance — + * to receive messages on a specific channel. Note that, for example, if there are two Interface scripts that subscribe to + * different channels, both scripts will receive messages on both channels. + * @function Messages.subscribe + * @param {string} channel - The channel to subscribe to. + */ Q_INVOKABLE void subscribe(QString channel); + + /**jsdoc + * Unsubscribe the scripting environment from receiving messages on a specific channel. + * @function Messages.unsubscribe + * @param {string} channel - The channel to unsubscribe from. + */ Q_INVOKABLE void unsubscribe(QString channel); static void decodeMessagesPacket(QSharedPointer receivedMessage, QString& channel, @@ -43,7 +163,34 @@ public: static std::unique_ptr encodeMessagesDataPacket(QString channel, QByteArray data, QUuid senderID); signals: + /**jsdoc + * Triggered when the a text message is received. + * @function Messages.messageReceived + * @param {string} channel - The channel that the message was sent on. You can use this to filter out messages not relevant + * to your script. + * @param {string} message - The message received. + * @param {Uuid} senderID - The UUID of the sender: the user's session UUID if sent by an Interface or client entity + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * instance if sent by an assignment client script. + * @param {boolean} localOnly - true if the message was sent with localOnly = true. + * @returns {Signal} + */ void messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly); + + /**jsdoc + * Triggered when a data message is received. + * @function Messages.dataReceived + * @param {string} channel - The channel that the message was sent on. You can use this to filter out messages not relevant + * to your script. + * @param {object} data - The data received. The data is handled as a byte stream, for example as may be used by a + * JavaScript Int8Array object. + * @param {Uuid} senderID - The UUID of the sender: the user's session UUID if sent by an Interface or client entity + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * script, the UUID of the entity script server if sent by a server entity script, or the UUID of the assignment client + * instance if sent by an assignment client script. + * @param {boolean} localOnly - true if the message was sent with localOnly = true. + * @returns {Signal} + */ void dataReceived(QString channel, QByteArray data, QUuid senderUUID, bool localOnly); private slots: diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 317cabdc61..d06b74b724 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -26,12 +26,14 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" -ResourceManager::ResourceManager() { +ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { _thread.setObjectName("Resource Manager Thread"); - auto assetClient = DependencyManager::set(); - assetClient->moveToThread(&_thread); - QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::initCaching); + if (_atpSupportEnabled) { + auto assetClient = DependencyManager::set(); + assetClient->moveToThread(&_thread); + QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::initCaching); + } _thread.start(); } @@ -111,6 +113,10 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { request = new HTTPResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_ATP) { + if (!_atpSupportEnabled) { + qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url(); + return nullptr; + } request = new AssetResourceRequest(normalizedURL); } else { qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); @@ -146,7 +152,7 @@ bool ResourceManager::resourceExists(const QUrl& url) { reply->deleteLater(); return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; - } else if (scheme == URL_SCHEME_ATP) { + } else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) { auto request = new AssetResourceRequest(url); ByteRange range; range.fromInclusive = 1; diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 5728a7bd32..d5fa2aa0e2 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -34,7 +34,7 @@ class ResourceManager: public QObject, public Dependency { SINGLETON_DEPENDENCY public: - ResourceManager(); + ResourceManager(bool atpSupportEnabled = true); void setUrlPrefixOverride(const QString& prefix, const QString& replacement); QString normalizeURL(const QString& urlString); @@ -57,6 +57,7 @@ private: using PrefixMap = std::map; + bool _atpSupportEnabled; PrefixMap _prefixMap; QMutex _prefixMapLock; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index b34c05106f..c1fe6ccd85 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -576,7 +576,7 @@ void Connection::processControl(ControlPacketPointer controlPacket) { // where the other end expired our connection. Let's reset. #ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Got handshake request, stopping SendQueue"; + qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; #endif _hasReceivedHandshakeACK = false; stopSendQueue(); diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 8b93a05130..4189cb613c 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -402,6 +402,10 @@ void Socket::readPendingDatagrams() { packet->getDataSize(), packet->getPayloadSize())) { // the connection could not be created or indicated that we should not continue processing this packet +#ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet) + << ", type" << NLPacket::typeInHeader(*packet); +#endif continue; } } diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h index c31da94532..e501dd166a 100644 --- a/libraries/octree/src/OctreeScriptingInterface.h +++ b/libraries/octree/src/OctreeScriptingInterface.h @@ -35,48 +35,126 @@ private slots: void cleanupManagedObjects(); public slots: + + /**jsdoc + * Set the maximum number of entity packets that the client can send per second. + * @function Entities.setPacketsPerSecond + * @param {number} packetsPerSecond - Integer maximum number of entity packets that the client can send per second. + */ /// set the max packets per second send rate void setPacketsPerSecond(int packetsPerSecond) { return _packetSender->setPacketsPerSecond(packetsPerSecond); } + /**jsdoc + * Get the maximum number of entity packets that the client can send per second. + * @function Entities.getPacketsPerSecond + * @returns {number} Integer maximum number of entity packets that the client can send per second. + */ /// get the max packets per second send rate int getPacketsPerSecond() const { return _packetSender->getPacketsPerSecond(); } - /// does a particle server exist to send to + /**jsdoc + * Check whether servers exist for the client to send entity packets to, i.e., whether you are connected to a domain and + * its entity server is working. + * @function Entities.serversExist + * @returns {boolean} true if servers exist for the client to send entity packets to, otherwise + * false. + */ + /// does a server exist to send to bool serversExist() const { return _packetSender->serversExist(); } + /**jsdoc + * Check whether the client has entity packets waiting to be sent. + * @function Entities.hasPacketsToSend + * @returns {boolean} true if the client has entity packets waiting to be sent, otherwise false. + */ /// are there packets waiting in the send queue to be sent bool hasPacketsToSend() const { return _packetSender->hasPacketsToSend(); } + /**jsdoc + * Get the number of entity packets the client has waiting to be sent. + * @function Entities.packetsToSendCount + * @returns {number} Integer number of entity packets the client has waiting to be sent. + */ /// how many packets are there in the send queue waiting to be sent int packetsToSendCount() const { return (int)_packetSender->packetsToSendCount(); } + /**jsdoc + * Get the entity packets per second send rate of the client over its lifetime. + * @function Entities.getLifetimePPS + * @returns {number} Entity packets per second send rate of the client over its lifetime. + */ /// returns the packets per second send rate of this object over its lifetime float getLifetimePPS() const { return _packetSender->getLifetimePPS(); } + /**jsdoc + * Get the entity bytes per second send rate of the client over its lifetime. + * @function Entities.getLifetimeBPS + * @returns {number} Entity bytes per second send rate of the client over its lifetime. + */ /// returns the bytes per second send rate of this object over its lifetime float getLifetimeBPS() const { return _packetSender->getLifetimeBPS(); } + /**jsdoc + * Get the entity packets per second queued rate of the client over its lifetime. + * @function Entities.getLifetimePPSQueued + * @returns {number} Entity packets per second queued rate of the client over its lifetime. + */ /// returns the packets per second queued rate of this object over its lifetime float getLifetimePPSQueued() const { return _packetSender->getLifetimePPSQueued(); } + /**jsdoc + * Get the entity bytes per second queued rate of the client over its lifetime. + * @function Entities.getLifetimeBPSQueued + * @returns {number} Entity bytes per second queued rate of the client over its lifetime. + */ /// returns the bytes per second queued rate of this object over its lifetime float getLifetimeBPSQueued() const { return _packetSender->getLifetimeBPSQueued(); } + /**jsdoc + * Get the lifetime of the client from the first entity packet sent until now, in microseconds. + * @function Entities.getLifetimeInUsecs + * @returns {number} Lifetime of the client from the first entity packet sent until now, in microseconds. + */ /// returns lifetime of this object from first packet sent to now in usecs long long unsigned int getLifetimeInUsecs() const { return _packetSender->getLifetimeInUsecs(); } - /// returns lifetime of this object from first packet sent to now in usecs + /**jsdoc + * Get the lifetime of the client from the first entity packet sent until now, in seconds. + * @function Entities.getLifetimeInSeconds + * @returns {number} Lifetime of the client from the first entity packet sent until now, in seconds. + */ + /// returns lifetime of this object from first packet sent to now in secs float getLifetimeInSeconds() const { return _packetSender->getLifetimeInSeconds(); } + /**jsdoc + * Get the total number of entity packets sent by the client over its lifetime. + * @function Entities.getLifetimePacketsSent + * @returns {number} The total number of entity packets sent by the client over its lifetime. + */ /// returns the total packets sent by this object over its lifetime long long unsigned int getLifetimePacketsSent() const { return _packetSender->getLifetimePacketsSent(); } + /**jsdoc + * Get the total bytes of entity packets sent by the client over its lifetime. + * @function Entities.getLifetimeBytesSent + * @returns {number} The total bytes of entity packets sent by the client over its lifetime. + */ /// returns the total bytes sent by this object over its lifetime long long unsigned int getLifetimeBytesSent() const { return _packetSender->getLifetimeBytesSent(); } + /**jsdoc + * Get the total number of entity packets queued by the client over its lifetime. + * @function Entities.getLifetimePacketsQueued + * @returns {number} The total number of entity packets queued by the client over its lifetime. + */ /// returns the total packets queued by this object over its lifetime long long unsigned int getLifetimePacketsQueued() const { return _packetSender->getLifetimePacketsQueued(); } + /**jsdoc + * Get the total bytes of entity packets queued by the client over its lifetime. + * @function Entities.getLifetimeBytesQueued + * @returns {number} The total bytes of entity packets queued by the client over its lifetime. + */ /// returns the total bytes queued by this object over its lifetime long long unsigned int getLifetimeBytesQueued() const { return _packetSender->getLifetimeBytesQueued(); } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index d39930ab76..9b6e9fe7a0 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -756,6 +756,9 @@ void CharacterController::updateState() { SET_STATE(State::Hover, "double jump button"); } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { SET_STATE(State::Hover, "jump button held"); + } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { + // Transition to hover if we are above the fall threshold + SET_STATE(State::Hover, "above fall threshold"); } } break; diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index c1fb397e19..e90862266b 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -142,6 +142,18 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "offset" {@link Entities.ActionType|ActionType} moves an entity so that it is a set distance away from a + * target point. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Offset + * @property {Vec3} pointToOffsetFrom=0,0,0 - The target point to offset the entity from. + * @property {number} linearDistance=0 - The distance away from the target point to position the entity. + * @property {number} linearTimeScale=34e+38 - Controls how long it takes for the entity's position to catch up with the + * target offset. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + */ QVariantMap ObjectActionOffset::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index 03e6533c87..bc68d6de73 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -307,6 +307,23 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "tractor" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * orientation, optionally relative to another entity. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Tractor + * @property {Vec3} targetPosition=0,0,0 - The target position. + * @property {Quat} targetRotation=0,0,0,1 - The target rotation. + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to this entity's position and rotation. + * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * is applied using an exponential decay. + * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ QVariantMap ObjectActionTractor::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp index 8ab24511d7..accade8695 100644 --- a/libraries/physics/src/ObjectActionTravelOriented.cpp +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -146,6 +146,17 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "travel-oriented" {@link Entities.ActionType|ActionType} orients an entity to align with its direction of + * travel. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-TravelOriented + * @property {Vec3} forward=0,0,0 - The axis of the entity to align with the entity's direction of travel. + * @property {number} angularTimeScale=0.1 - Controls how long it takes for the entity's orientation to catch up with the + * direction of travel. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * action is applied using an exponential decay. + */ QVariantMap ObjectActionTravelOriented::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index 9dd85954a3..70613d46ae 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -181,6 +181,15 @@ bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "ball-socket" {@link Entities.ActionType|ActionType} connects two entities with a ball and socket joint. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-BallSocket + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + */ QVariantMap ObjectConstraintBallSocket::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 49f926af81..86f1f21c63 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -261,6 +261,21 @@ bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "cone-twist" {@link Entities.ActionType|ActionType} connects two entities with a joint that can move + * through a cone and can twist. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-ConeTwist + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Vec3} axis=1,0,0 - The axis of the entity that moves through the cone. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that moves through the cone. Must be a non-zero vector. + * @property {number} swingSpan1=6.238 - The angle through which the joint can move in one axis of the cone, in radians. + * @property {number} swingSpan2=6.238 - The angle through which the joint can move in the other axis of the cone, in radians. + * @property {number} twistSpan=6.238 - The angle through with the joint can twist, in radians. + */ QVariantMap ObjectConstraintConeTwist::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 52be64796a..99ddd45abd 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -245,6 +245,22 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "hinge" {@link Entities.ActionType|ActionType} lets an entity pivot about an axis or connects two entities + * with a hinge joint. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Hinge + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. + * @property {Vec3} axis=1,0,0 - The axis of the entity that it pivots about. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If none is + * specified then the first entity simply pivots about its specified axis. + * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. + * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that it pivots about. Must be a non-zero vector. + * @property {number} low=-6.283 - The most negative angle that the hinge can take, in radians. + * @property {number} high=6.283 - The most positive angle that the hinge can take, in radians. + * @property {number} angle=0 - The current angle of the hinge. Read-only. + */ QVariantMap ObjectConstraintHinge::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index ded9ad47e6..c236afc10d 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -261,6 +261,31 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { return true; } +/**jsdoc + * The "slider" {@link Entities.ActionType|ActionType} lets an entity slide and rotate along an axis, or connects + * two entities that slide and rotate along a shared axis. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * + * @typedef {object} Entities.ActionArguments-Slider + * @property {Vec3} point=0,0,0 - The local position of a point in the entity that slides along the axis. + * @property {Vec3} axis=1,0,0 - The axis of the entity that slides along the joint. Must be a non-zero vector. + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If non is + * specified then the first entity simply slides and rotates about its specified axis. + * @property {Vec3} otherPoint=0,0,0 - The local position of a point in the other entity that slides along the axis. + * @property {Vec3} axis=1,0,0 - The axis of the other entity that slides along the joint. Must be a non-zero vector. + * @property {number} linearLow=1.17e-38 - The most negative linear offset from the entity's initial point that the entity can + * have along the slider. + * @property {number} linearHigh=3.40e+38 - The most positive linear offset from the entity's initial point that the entity can + * have along the slider. + * @property {number} angularLow=-6.283 - The most negative angle that the entity can rotate about the axis if the action + * involves only one entity, otherwise the most negative angle the rotation can be between the two entities. In radians. + * @property {number} angularHigh=6.283 - The most positive angle that the entity can rotate about the axis if the action + * involves only one entity, otherwise the most positive angle the rotation can be between the two entities. In radians. + * @property {number} linearPosition=0 - The current linear offset the entity is from its initial point if the action involves + * only one entity, otherwise the linear offset between the two entities' action points. Read-only. + * @property {number} angularPosition=0 - The current angular offset of the entity from its initial rotation if the action + * involves only one entity, otherwise the angular offset between the two entities. Read-only. + */ QVariantMap ObjectConstraintSlider::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { diff --git a/libraries/physics/src/ObjectDynamic.cpp b/libraries/physics/src/ObjectDynamic.cpp index 3deadd6468..5bbb5981d1 100644 --- a/libraries/physics/src/ObjectDynamic.cpp +++ b/libraries/physics/src/ObjectDynamic.cpp @@ -93,6 +93,38 @@ bool ObjectDynamic::updateArguments(QVariantMap arguments) { return somethingChanged; } +/**jsdoc +* Different entity action types have different arguments: some common to all actions (listed below) and some specific to each +* {@link Entities.ActionType|ActionType} (linked to below). The arguments are accessed as an object of property names and +* values. +* +* @typedef {object} Entities.ActionArguments +* @property {Entities.ActionType} type - The type of action. +* @property {string} tag="" - A string that a script can use for its own purposes. +* @property {number} ttl=0 - How long the action should exist, in seconds, before it is automatically deleted. A value of +* 0 means that the action should not be deleted. +* @property {boolean} isMine=true - Is true if you created the action during your current Interface session, +* false otherwise. Read-only. +* @property {boolean} ::no-motion-state - Is present when the entity hasn't been registered with the physics engine yet (e.g., +* if the action hasn't been properly configured), otherwise undefined. Read-only. +* @property {boolean} ::active - Is true when the action is modifying the entity's motion, false +* otherwise. Is present once the entity has been registered with the physics engine, otherwise undefined. +* Read-only. +* @property {Entities.PhysicsMotionType} ::motion-type - How the entity moves with the action. Is present once the entity has +* been registered with the physics engine, otherwise undefined. Read-only. +* +* @see The different action types have additional arguments as follows: +* @see {@link Entities.ActionArguments-FarGrab|ActionArguments-FarGrab} +* @see {@link Entities.ActionArguments-Hold|ActionArguments-Hold} +* @see {@link Entities.ActionArguments-Offset|ActionArguments-Offset} +* @see {@link Entities.ActionArguments-Tractor|ActionArguments-Tractor} +* @see {@link Entities.ActionArguments-TravelOriented|ActionArguments-TravelOriented} +* @see {@link Entities.ActionArguments-Hinge|ActionArguments-Hinge} +* @see {@link Entities.ActionArguments-Slider|ActionArguments-Slider} +* @see {@link Entities.ActionArguments-ConeTwist|ActionArguments-ConeTwist} +* @see {@link Entities.ActionArguments-BallSocket|ActionArguments-BallSocket} +*/ +// Note: The "type" property is set in EntityItem::getActionArguments(). QVariantMap ObjectDynamic::getArguments() { QVariantMap arguments; withReadLock([&]{ diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 0b91ede574..7f583ca9ca 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -29,6 +29,23 @@ enum PhysicsMotionType { MOTION_TYPE_KINEMATIC // keyframed motion }; +/**jsdoc + *

An entity's physics motion type may be one of the following:

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
"static"There is no motion because the entity is locked — its locked + * property is set to true.
"kinematic"Motion is applied without physical laws (e.g., damping) because the entity is + * not locked and has its dynamic property set to false.
"dynamic"Motion is applied according to physical laws (e.g., damping) because the entity + * is not locked and has its dynamic property set to true.
+ * @typedef {string} Entities.PhysicsMotionType + */ inline QString motionTypeToString(PhysicsMotionType motionType) { switch(motionType) { case MOTION_TYPE_STATIC: return QString("static"); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 2a8a72f594..47503e8f85 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -42,4 +42,9 @@ std::function Displa hudOperator = _hudOperator; } return hudOperator; -} \ No newline at end of file +} + +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 2c717f629c..e95084df52 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -93,9 +93,7 @@ class HmdDisplay : public StereoDisplay { public: // HMD specific methods // TODO move these into another class? - virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { - static const glm::mat4 transform; return transform; - } + virtual glm::mat4 getEyeToHeadTransform(Eye eye) const; // returns a copy of the most recent head pose, computed via updateHeadPose virtual glm::mat4 getHeadPose() const { diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index dbd6845676..e9c084e132 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -96,7 +96,9 @@ const LoaderList& getLoadedPlugins() { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { -#ifdef Q_OS_MAC +#if defined(Q_OS_ANDROID) + QString pluginPath = QCoreApplication::applicationDirPath() + "/"; +#elif defined(Q_OS_MAC) QString pluginPath = QCoreApplication::applicationDirPath() + "/../PlugIns/"; #else QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins/"; @@ -106,6 +108,10 @@ const LoaderList& getLoadedPlugins() { pluginDir.setFilter(QDir::Files); if (pluginDir.exists()) { qInfo() << "Loading runtime plugins from " << pluginPath; +#if defined(Q_OS_ANDROID) + // Can be a better filter and those libs may have a better name to destinguish them from qt plugins + pluginDir.setNameFilters(QStringList() << "libplugins_lib*.so"); +#endif auto candidates = pluginDir.entryList(); for (auto plugin : candidates) { qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index e3e26bb7fa..6580c5b1e8 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -188,11 +188,13 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) event->ignore(); if (QCoreApplication::sendEvent(window->activeFocusItem(), event)) { bool eventAccepted = event->isAccepted(); - QInputMethodQueryEvent* imqEvent = static_cast(event); - // this block disables the selection cursor in android which appears in - // the top-left corner of the screen - if (imqEvent->queries() & Qt::ImEnabled) { - imqEvent->setValue(Qt::ImEnabled, QVariant(false)); + if (event->type() == QEvent::InputMethodQuery) { + QInputMethodQueryEvent *imqEvent = static_cast(event); + // this block disables the selection cursor in android which appears in + // the top-left corner of the screen + if (imqEvent->queries() & Qt::ImEnabled) { + imqEvent->setValue(Qt::ImEnabled, QVariant(false)); + } } return eventAccepted; } @@ -342,6 +344,11 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, // Make sure we make items focusable (critical for // supporting keyboard shortcuts) newItem->setFlag(QQuickItem::ItemIsFocusScope, true); +#ifdef DEBUG + for (auto frame : newObject->findChildren("Frame")) { + frame->setProperty("qmlFile", qmlComponent->url()); + } +#endif } bool rootCreated = getRootItem() != nullptr; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 015f5678c8..c526f16b75 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -385,7 +385,7 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte auto firstHBlurPipeline = getHBlurPipeline(); auto lastVBlurPipeline = getVBlurPipeline(); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("AmbientOcclusionEffect::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); _gpuTimer->begin(batch); @@ -518,7 +518,7 @@ void DebugAmbientOcclusion::run(const render::RenderContextPointer& renderContex auto debugPipeline = getDebugPipeline(); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugAmbientOcclusion::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(sourceViewport); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 02ff234c01..90424b04b2 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -9,6 +9,7 @@ #include "AnimDebugDraw.h" +#include #include #include @@ -21,7 +22,6 @@ #include "animdebugdraw_vert.h" #include "animdebugdraw_frag.h" - class AnimDebugDrawData { public: diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index bdd8f19a5c..4a9b69c099 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "AntialiasingEffect.h" #include "StencilMaskPass.h" @@ -22,7 +23,11 @@ #include "DependencyManager.h" #include "ViewFrustum.h" #include "GeometryCache.h" +#include "FramebufferCache.h" +#define ANTIALIASING_USE_TAA 1 + +#if !ANTIALIASING_USE_TAA #include "fxaa_vert.h" #include "fxaa_frag.h" #include "fxaa_blend_frag.h" @@ -108,7 +113,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); @@ -165,3 +170,372 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, texCoordTopLeft, texCoordBottomRight, color, _geometryId); }); } +#else + +#include "taa_frag.h" +#include "fxaa_blend_frag.h" +#include "taa_blend_frag.h" + +const int AntialiasingPass_ParamsSlot = 0; +const int AntialiasingPass_FrameTransformSlot = 1; + +const int AntialiasingPass_HistoryMapSlot = 0; +const int AntialiasingPass_SourceMapSlot = 1; +const int AntialiasingPass_VelocityMapSlot = 2; +const int AntialiasingPass_DepthMapSlot = 3; + +const int AntialiasingPass_NextMapSlot = 4; + + +Antialiasing::Antialiasing() { + _antialiasingBuffers = std::make_shared(2U); +} + +Antialiasing::~Antialiasing() { + _antialiasingBuffers.reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); +} + +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { + + if (!_antialiasingPipeline) { + + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = taa_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); + + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + PrepareStencil::testNoAA(*state); + + // Good to go add the brand new pipeline + _antialiasingPipeline = gpu::Pipeline::create(program, state); + } + + return _antialiasingPipeline; +} + +const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { + if (!_blendPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = fxaa_blend_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("colorTexture"), AntialiasingPass_NextMapSlot)); + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testNoAA(*state); + + + // Good to go add the brand new pipeline + _blendPipeline = gpu::Pipeline::create(program, state); + _sharpenLoc = program->getUniforms().findLocation("sharpenIntensity"); + + } + return _blendPipeline; +} + +const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { + if (!_debugBlendPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = taa_blend_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("nextMap"), AntialiasingPass_NextMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); + + + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testNoAA(*state); + + + // Good to go add the brand new pipeline + _debugBlendPipeline = gpu::Pipeline::create(program, state); + } + return _debugBlendPipeline; +} + +void Antialiasing::configure(const Config& config) { + _sharpen = config.sharpen; + _params.edit().blend = config.blend; + _params.edit().covarianceGamma = config.covarianceGamma; + + _params.edit().setConstrainColor(config.constrainColor); + _params.edit().setFeedbackColor(config.feedbackColor); + + _params.edit().debugShowVelocityThreshold = config.debugShowVelocityThreshold; + + _params.edit().regionInfo.x = config.debugX; + _params.edit().regionInfo.z = config.debugFXAAX; + + _params.edit().setDebug(config.debug); + _params.edit().setShowDebugCursor(config.showCursorPixel); + _params.edit().setDebugCursor(config.debugCursorTexcoord); + _params.edit().setDebugOrbZoom(config.debugOrbZoom); + + _params.edit().setShowClosestFragment(config.showClosestFragment); +} + + +void Antialiasing::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + auto& deferredFrameTransform = inputs.get0(); + auto& sourceBuffer = inputs.get1(); + auto& linearDepthBuffer = inputs.get2(); + auto& velocityBuffer = inputs.get3(); + + int width = sourceBuffer->getWidth(); + int height = sourceBuffer->getHeight(); + + if (_antialiasingBuffers->get(0)) { + if (_antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) {// || (sourceBuffer && (_antialiasingBuffer->getRenderBuffer(1) != sourceBuffer->getRenderBuffer(0)))) { + _antialiasingBuffers->edit(0).reset(); + _antialiasingBuffers->edit(1).reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); + } + } + + if (!_antialiasingBuffers->get(0)) { + // Link the antialiasing FBO to texture + for (int i = 0; i < 2; i++) { + auto& antiAliasingBuffer = _antialiasingBuffers->edit(i); + antiAliasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); + } + } + + gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + + // TAA step + getAntialiasingPipeline(); + batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_HistoryMapSlot, _antialiasingBuffers, 0); + batch.setResourceTexture(AntialiasingPass_SourceMapSlot, sourceBuffer->getRenderBuffer(0)); + batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, velocityBuffer->getVelocityTexture()); + // This is only used during debug + batch.setResourceTexture(AntialiasingPass_DepthMapSlot, linearDepthBuffer->getLinearDepthTexture()); + + batch.setUniformBuffer(AntialiasingPass_ParamsSlot, _params); + batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, deferredFrameTransform->getFrameTransformBuffer()); + + batch.setFramebufferSwapChain(_antialiasingBuffers, 1); + batch.setPipeline(getAntialiasingPipeline()); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + // Blend step + batch.setResourceTexture(AntialiasingPass_SourceMapSlot, nullptr); + + batch.setFramebuffer(sourceBuffer); + if (_params->isDebug()) { + batch.setPipeline(getDebugBlendPipeline()); + } else { + batch.setPipeline(getBlendPipeline()); + // Disable sharpen if FXAA + batch._glUniform1f(_sharpenLoc, _sharpen * _params.get().regionInfo.z); + } + batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_NextMapSlot, _antialiasingBuffers, 1); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.advance(_antialiasingBuffers); + + batch.setUniformBuffer(AntialiasingPass_ParamsSlot, nullptr); + batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, nullptr); + + batch.setResourceTexture(AntialiasingPass_DepthMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_HistoryMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr); + batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr); + }); +} + + +void JitterSampleConfig::setIndex(int current) { + _index = (current) % JitterSample::SEQUENCE_LENGTH; + emit dirty(); +} + +int JitterSampleConfig::cycleStopPauseRun() { + _state = (_state + 1) % 3; + switch (_state) { + case 0: { + return none(); + break; + } + case 1: { + return pause(); + break; + } + case 2: + default: { + return play(); + break; + } + } + return _state; +} + +int JitterSampleConfig::prev() { + setIndex(_index - 1); + return _index; +} + +int JitterSampleConfig::next() { + setIndex(_index + 1); + return _index; +} + +int JitterSampleConfig::none() { + _state = 0; + stop = true; + freeze = false; + setIndex(-1); + return _state; +} + +int JitterSampleConfig::pause() { + _state = 1; + stop = false; + freeze = true; + setIndex(0); + return _state; +} + + +int JitterSampleConfig::play() { + _state = 2; + stop = false; + freeze = false; + setIndex(0); + return _state; +} + +template +class Halton { +public: + + float eval(int index) const { + float f = 1.0f; + float r = 0.0f; + float invB = 1.0f / (float)B; + index++; // Indices start at 1, not 0 + + while (index > 0) { + f = f * invB; + r = r + f * (float)(index % B); + index = index / B; + + } + + return r; + } + +}; + + +JitterSample::SampleSequence::SampleSequence(){ + // Halton sequence (2,3) + Halton<2> genX; + Halton<3> genY; + + for (int i = 0; i < SEQUENCE_LENGTH; i++) { + offsets[i] = glm::vec2(genX.eval(i), genY.eval(i)); + offsets[i] -= vec2(0.5f); + } + offsets[SEQUENCE_LENGTH] = glm::vec2(0.0f); +} + +void JitterSample::configure(const Config& config) { + _freeze = config.freeze; + if (config.stop || _freeze) { + auto pausedIndex = config.getIndex(); + if (_sampleSequence.currentIndex != pausedIndex) { + _sampleSequence.currentIndex = pausedIndex; + } + } else { + if (_sampleSequence.currentIndex < 0) { + _sampleSequence.currentIndex = config.getIndex(); + } + } + _scale = config.scale; +} + +void JitterSample::run(const render::RenderContextPointer& renderContext) { + auto& current = _sampleSequence.currentIndex; + if (!_freeze) { + if (current >= 0) { + current = (current + 1) % SEQUENCE_LENGTH; + } else { + current = -1; + } + } + auto args = renderContext->args; + auto viewFrustum = args->getViewFrustum(); + + auto jit = _sampleSequence.offsets[(current < 0 ? SEQUENCE_LENGTH : current)]; + auto width = (float)args->_viewport.z; + auto height = (float)args->_viewport.w; + + auto jx = 2.0f * jit.x / width; + auto jy = 2.0f * jit.y / height; + + if (!args->isStereo()) { + auto projMat = viewFrustum.getProjection(); + + projMat[2][0] += jx; + projMat[2][1] += jy; + + viewFrustum.setProjection(projMat); + viewFrustum.calculate(); + args->setViewFrustum(viewFrustum); + } else { + mat4 projMats[2]; + args->_context->getStereoProjections(projMats); + + jx *= 2.0f; + + for (int i = 0; i < 2; i++) { + auto& projMat = projMats[i]; + projMat[2][0] += jx; + projMat[2][1] += jy; + } + + args->_context->setStereoProjections(projMats); + } +} + + +#endif \ No newline at end of file diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index cec2554a3b..03fdb9d9a4 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -15,7 +15,184 @@ #include #include "render/DrawTask.h" +#include "DeferredFrameTransform.h" +#include "VelocityBufferPass.h" + +class JitterSampleConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float scale MEMBER scale NOTIFY dirty) + Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty) + Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) + Q_PROPERTY(int index READ getIndex NOTIFY dirty) +public: + JitterSampleConfig() : render::Job::Config(true) {} + + float scale{ 0.5f }; + bool stop{ false }; + bool freeze{ false }; + + void setIndex(int current); + +public slots: + int cycleStopPauseRun(); + int prev(); + int next(); + int none(); + int pause(); + int play(); + + int getIndex() const { return _index; } + int getState() const { return _state; } +signals: + void dirty(); + +private: + int _state{ 0 }; + int _index{ 0 }; + +}; + + +class JitterSample { +public: + + enum { + SEQUENCE_LENGTH = 128 + }; + + using Config = JitterSampleConfig; + using JobModel = render::Job::Model; + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext); + +private: + + struct SampleSequence { + SampleSequence(); + + glm::vec2 offsets[SEQUENCE_LENGTH + 1]; + int sequenceLength{ SEQUENCE_LENGTH }; + int currentIndex{ 0 }; + }; + + SampleSequence _sampleSequence; + float _scale{ 1.0 }; + bool _freeze{ false }; +}; + + +class AntialiasingConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float blend MEMBER blend NOTIFY dirty) + Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty) + Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty) + + Q_PROPERTY(bool constrainColor MEMBER constrainColor NOTIFY dirty) + Q_PROPERTY(bool feedbackColor MEMBER feedbackColor NOTIFY dirty) + + Q_PROPERTY(bool debug MEMBER debug NOTIFY dirty) + Q_PROPERTY(float debugX MEMBER debugX NOTIFY dirty) + Q_PROPERTY(float debugFXAAX MEMBER debugFXAAX NOTIFY dirty) + Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty) + Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) + Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) + Q_PROPERTY(float debugOrbZoom MEMBER debugOrbZoom NOTIFY dirty) + + Q_PROPERTY(bool showClosestFragment MEMBER showClosestFragment NOTIFY dirty) + +public: + AntialiasingConfig() : render::Job::Config(true) {} + + float blend{ 0.05f }; + float sharpen{ 0.15f }; + + bool constrainColor{ true }; + float covarianceGamma{ 0.9f }; + bool feedbackColor{ false }; + + float debugX{ 0.0f }; + float debugFXAAX{ 1.0f }; + float debugShowVelocityThreshold{ 1.0f }; + glm::vec2 debugCursorTexcoord{ 0.5f, 0.5f }; + float debugOrbZoom{ 2.0f }; + + bool debug { false }; + bool showCursorPixel { false }; + bool showClosestFragment{ false }; + +signals: + void dirty(); +}; + +#define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) +#define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) + +struct TAAParams { + float nope{ 0.0f }; + float blend{ 0.05f }; + float covarianceGamma{ 1.0f }; + float debugShowVelocityThreshold{ 1.0f }; + + glm::ivec4 flags{ 0 }; + glm::vec4 pixelInfo{ 0.5f, 0.5f, 2.0f, 0.0f }; + glm::vec4 regionInfo{ 0.0f, 0.0f, 1.0f, 0.0f }; + + void setConstrainColor(bool enabled) { SET_BIT(flags.y, 1, enabled); } + bool isConstrainColor() const { return (bool)GET_BIT(flags.y, 1); } + + void setFeedbackColor(bool enabled) { SET_BIT(flags.y, 4, enabled); } + bool isFeedbackColor() const { return (bool)GET_BIT(flags.y, 4); } + + void setDebug(bool enabled) { SET_BIT(flags.x, 0, enabled); } + bool isDebug() const { return (bool) GET_BIT(flags.x, 0); } + + void setShowDebugCursor(bool enabled) { SET_BIT(flags.x, 1, enabled); } + bool showDebugCursor() const { return (bool)GET_BIT(flags.x, 1); } + + void setDebugCursor(glm::vec2 debugCursor) { pixelInfo.x = debugCursor.x; pixelInfo.y = debugCursor.y; } + glm::vec2 getDebugCursor() const { return glm::vec2(pixelInfo.x, pixelInfo.y); } + + void setDebugOrbZoom(float orbZoom) { pixelInfo.z = orbZoom; } + float getDebugOrbZoom() const { return pixelInfo.z; } + + void setShowClosestFragment(bool enabled) { SET_BIT(flags.x, 3, enabled); } + +}; +using TAAParamsBuffer = gpu::StructBuffer; + +class Antialiasing { +public: + using Inputs = render::VaryingSet4 < DeferredFrameTransformPointer, gpu::FramebufferPointer, LinearDepthFramebufferPointer, VelocityFramebufferPointer > ; + using Config = AntialiasingConfig; + using JobModel = render::Job::ModelI; + + Antialiasing(); + ~Antialiasing(); + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + + const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getBlendPipeline(); + const gpu::PipelinePointer& getDebugBlendPipeline(); + +private: + + gpu::FramebufferSwapChainPointer _antialiasingBuffers; + gpu::TexturePointer _antialiasingTextures[2]; + + gpu::PipelinePointer _antialiasingPipeline; + gpu::PipelinePointer _blendPipeline; + gpu::PipelinePointer _debugBlendPipeline; + + TAAParamsBuffer _params; + float _sharpen{ 0.15f }; + int _sharpenLoc{ -1 }; +}; + + +/* class AntiAliasingConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled) @@ -27,27 +204,28 @@ class Antialiasing { public: using Config = AntiAliasingConfig; using JobModel = render::Job::ModelI; - + Antialiasing(); ~Antialiasing(); void configure(const Config& config) {} void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); - - const gpu::PipelinePointer& getAntialiasingPipeline(RenderArgs* args); + + const gpu::PipelinePointer& getAntialiasingPipeline(); const gpu::PipelinePointer& getBlendPipeline(); - + private: - + // Uniforms for AA gpu::int32 _texcoordOffsetLoc; - + gpu::FramebufferPointer _antialiasingBuffer; - + gpu::TexturePointer _antialiasingTexture; - + gpu::PipelinePointer _antialiasingPipeline; gpu::PipelinePointer _blendPipeline; int _geometryId { 0 }; }; +*/ #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 493c28d840..886795ec79 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -93,7 +93,7 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, PerformanceTimer perfTimer("skybox"); auto args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawBackgroundStage::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.enableSkybox(true); diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 89a83a651a..ddd63f012f 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -75,7 +75,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomThreshold::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(viewport); @@ -135,7 +135,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In const auto blur2FB = inputs.get3(); const glm::ivec4 viewport{ 0, 0, framebufferSize.x, framebufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomApply::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); @@ -180,7 +180,7 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp _pipeline = gpu::Pipeline::create(program, state); } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BloomDraw::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); @@ -238,7 +238,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In _pipeline = gpu::Pipeline::create(program, state); } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugBloom::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(frameBuffer); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 24cffe2fb8..c17044be6d 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -53,7 +53,8 @@ enum TextureSlot { DiffusedCurvature, Scattering, AmbientOcclusion, - AmbientOcclusionBlurred + AmbientOcclusionBlurred, + Velocity, }; enum ParamSlot { @@ -254,6 +255,12 @@ static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{ " }" }; +static const std::string DEFAULT_VELOCITY_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(vec2(texture(velocityMap, uv).xy), 0.0, 1.0);" + " }" +}; + static const std::string DEFAULT_CUSTOM_SHADER { "vec4 getFragmentColor() {" " return vec4(1.0, 0.0, 0.0, 1.0);" @@ -341,6 +348,8 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust return DEFAULT_AMBIENT_OCCLUSION_SHADER; case AmbientOcclusionBlurredMode: return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER; + case VelocityMode: + return DEFAULT_VELOCITY_SHADER; case CustomMode: return getFileContent(customFile, DEFAULT_CUSTOM_SHADER); default: @@ -402,6 +411,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str slotBindings.insert(gpu::Shader::Binding("diffusedCurvatureMap", DiffusedCurvature)); slotBindings.insert(gpu::Shader::Binding("scatteringMap", Scattering)); slotBindings.insert(gpu::Shader::Binding("occlusionBlurredMap", AmbientOcclusionBlurred)); + slotBindings.insert(gpu::Shader::Binding("velocityMap", Velocity)); gpu::Shader::makeProgram(*program, slotBindings); auto pipeline = gpu::Pipeline::create(program, std::make_shared()); @@ -439,9 +449,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto& linearDepthTarget = inputs.get1(); auto& surfaceGeometryFramebuffer = inputs.get2(); auto& ambientOcclusionFramebuffer = inputs.get3(); - auto& frameTransform = inputs.get4(); + auto& velocityFramebuffer = inputs.get4(); + auto& frameTransform = inputs.get5(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugDeferredBuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); @@ -468,6 +479,9 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Depth, deferredFramebuffer->getPrimaryDepthTexture()); batch.setResourceTexture(Lighting, deferredFramebuffer->getLightingTexture()); } + if (velocityFramebuffer) { + batch.setResourceTexture(Velocity, velocityFramebuffer->getVelocityTexture()); + } auto lightStage = renderContext->_scene->getStage(); assert(lightStage); @@ -515,5 +529,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(AmbientOcclusion, nullptr); batch.setResourceTexture(AmbientOcclusionBlurred, nullptr); + batch.setResourceTexture(Velocity, nullptr); + }); } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 8227c4f7a3..5384a77b76 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -19,6 +19,7 @@ #include "DeferredFramebuffer.h" #include "SurfaceGeometryPass.h" #include "AmbientOcclusionEffect.h" +#include "VelocityBufferPass.h" class DebugDeferredBufferConfig : public render::Job::Config { Q_OBJECT @@ -38,7 +39,7 @@ signals: class DebugDeferredBuffer { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet6; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI; @@ -81,6 +82,7 @@ protected: ScatteringDebugMode, AmbientOcclusionMode, AmbientOcclusionBlurredMode, + VelocityMode, CustomMode, // Needs to stay last NumModes, diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index baf523312c..d1c51bf46f 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -38,12 +38,13 @@ void DeferredFrameTransform::update(RenderArgs* args) { args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); - // Running in stero ? + // Running in stereo ? bool isStereo = args->isStereo(); if (!isStereo) { frameTransformBuffer.projection[0] = frameTransformBuffer.projectionMono; frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); + frameTransformBuffer.invProjection[0] = glm::inverse(frameTransformBuffer.projection[0]); } else { mat4 projMats[2]; @@ -55,6 +56,7 @@ void DeferredFrameTransform::update(RenderArgs* args) { // Compose the mono Eye space to Stereo clip space Projection Matrix auto sideViewMat = projMats[i] * eyeViews[i]; frameTransformBuffer.projection[i] = sideViewMat; + frameTransformBuffer.invProjection[i] = glm::inverse(sideViewMat); } frameTransformBuffer.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index 93e194f052..8c2f0a7321 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -45,6 +45,8 @@ protected: glm::vec4 stereoInfo{ 0.0 }; // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space glm::mat4 projection[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 invProjection[2]; // THe mono projection for sure glm::mat4 projectionMono; // Inv View matrix from eye space (mono) to world space diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 78bc3ba195..fc35267ddc 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -431,7 +431,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input outputs.edit0() = _deferredFramebuffer; outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("PrepareDeferred::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index e7771821dc..5fccbd6a99 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -16,6 +16,9 @@ struct CameraCorrection { mat4 _correction; mat4 _correctionInverse; + + mat4 _prevView; + mat4 _prevViewInverse; }; uniform cameraCorrectionBuffer { @@ -28,6 +31,7 @@ struct DeferredFrameTransform { vec4 _depthInfo; vec4 _stereoInfo; mat4 _projection[2]; + mat4 _invProjection[2]; mat4 _projectionMono; mat4 _viewInverse; mat4 _view; @@ -37,13 +41,6 @@ uniform deferredFrameTransformBuffer { DeferredFrameTransform frameTransform; }; -DeferredFrameTransform getDeferredFrameTransform() { - DeferredFrameTransform result = frameTransform; - result._view = result._view * cameraCorrection._correctionInverse; - result._viewInverse = result._viewInverse * cameraCorrection._correction; - return result; -} - vec2 getWidthHeight(int resolutionLevel) { return vec2(ivec2(frameTransform._pixelInfo.zw) >> resolutionLevel); } @@ -79,11 +76,26 @@ float getPosLinearDepthFar() { } mat4 getViewInverse() { - return frameTransform._viewInverse * cameraCorrection._correction; + return frameTransform._viewInverse * cameraCorrection._correctionInverse; } mat4 getView() { - return frameTransform._view * cameraCorrection._correctionInverse; + return cameraCorrection._correction * frameTransform._view; +} + +mat4 getPreviousView() { + return cameraCorrection._prevView; +} + +mat4 getPreviousViewInverse() { + return cameraCorrection._prevViewInverse; +} + +DeferredFrameTransform getDeferredFrameTransform() { + DeferredFrameTransform result = frameTransform; + result._view = getView(); + result._viewInverse = getViewInverse(); + return result; } bool isStereo() { @@ -123,6 +135,14 @@ vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { return vec3(Xe, Ye, Zeye); } +vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { + // compute the view space position using the depth + vec3 clipPos; + clipPos.xyz = vec3(texcoord.xy, Zdb) * 2.0 - 1.0; + vec4 eyePos = frameTransform._invProjection[side] * vec4(clipPos.xyz, 1.0); + return eyePos.xyz / eyePos.w; +} + ivec2 getPixelPosTexcoordPosAndSide(in vec2 glFragCoord, out ivec2 pixelPos, out vec2 texcoordPos, out ivec4 stereoSide) { ivec2 fragPos = ivec2(glFragCoord.xy); diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index c0db1597ca..78569b2837 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -155,7 +155,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu auto sourceFramebufferSize = glm::ivec2(inputBuffer->getDimensions()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHaze::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(outputBuffer); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7455da13b6..bf265fae8c 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -75,32 +75,6 @@ static std::array MAPPING GeometryCache::Cylinder, } }; -/**jsdoc -*

{@link Entities} and {@link Overlays} may have the following geometrical shapes:

-* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
ValueDescription
LineA 1D line oriented in 3 dimensions.
TriangleA triangular prism.
QuadA 2D square oriented in 3 dimensions.
HexagonA hexagonal prism.
OctagonAn octagonal prism.
CircleA 2D circle oriented in 3 dimensions.
CubeA cube.
SphereA sphere.
TetrahedronA tetrahedron.
OctahedronAn octahedron.
DodecahedronA dodecahedron.
IcosahedronAn icosahedron.
TorusA torus. Not implemented.
ConeA cone.
CylinderA cylinder.
-* @typedef {string} Shape -*/ static const std::array GEOCACHE_SHAPE_STRINGS{ { "Line", "Triangle", @@ -2136,7 +2110,7 @@ static void buildWebShader(const gpu::ShaderPointer& vertShader, const gpu::Shad gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*state); + PrepareStencil::testMaskDrawShape(*state); pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); } @@ -2148,11 +2122,11 @@ void GeometryCache::bindWebBrowserProgram(gpu::Batch& batch, bool transparent) { gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent) { static std::once_flag once; std::call_once(once, [&]() { - buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipelineNoAA); - buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipelineNoAA); + buildWebShader(simple_vert::getShader(), simple_opaque_web_browser_frag::getShader(), false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); + buildWebShader(simple_vert::getShader(), simple_transparent_web_browser_frag::getShader(), true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); }); - return transparent ? _simpleTransparentWebBrowserPipelineNoAA : _simpleOpaqueWebBrowserPipelineNoAA; + return transparent ? _simpleTransparentWebBrowserPipeline : _simpleOpaqueWebBrowserPipeline; } void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool isAntiAliased) { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 998043b80e..3c6b85bed1 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -475,9 +475,9 @@ private: static QHash _simplePrograms; gpu::ShaderPointer _simpleOpaqueWebBrowserShader; - gpu::PipelinePointer _simpleOpaqueWebBrowserPipelineNoAA; + gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline; gpu::ShaderPointer _simpleTransparentWebBrowserShader; - gpu::PipelinePointer _simpleTransparentWebBrowserPipelineNoAA; + gpu::PipelinePointer _simpleTransparentWebBrowserPipeline; static render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true, bool unlit = false, bool depthBias = false); diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 0bf8e7fa71..d151da766b 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -161,7 +161,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c // Clear the framebuffer without stereo // Needs to be distinct from the other batch because using the clear call // while stereo is enabled triggers a warning - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run::begin", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(ressources->getDepthFramebuffer()); batch.clearDepthStencilFramebuffer(1.0f, 0); @@ -174,7 +174,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c render::ItemBounds itemBounds; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); @@ -212,7 +212,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlightMask::run::end", args->_context, [&](gpu::Batch& batch) { // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); @@ -284,7 +284,7 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const shaderParameters._size.y = size; } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFrameBuffer); @@ -357,7 +357,7 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DebugHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); batch.setFramebuffer(highlightRessources->getColorFramebuffer()); diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index a824a2221b..d54cefa5c7 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -8,53 +8,23 @@ #include "RenderCommonTask.h" -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#include "LightingModel.h" -#include "StencilMaskPass.h" -#include "DebugDeferredBuffer.h" -#include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" -#include "SurfaceGeometryPass.h" -#include "FramebufferCache.h" -#include "TextureCache.h" -#include "ZoneRenderer.h" -#include "FadeEffect.h" #include "RenderUtilsLogging.h" -#include "AmbientOcclusionEffect.h" -#include "AntialiasingEffect.h" -#include "ToneMappingEffect.h" -#include "SubsurfaceScattering.h" -#include "DrawHaze.h" -#include "BloomEffect.h" -#include "HighlightEffect.h" - -#include - using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { timer = _gpuTimer; - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("BeginGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { _gpuTimer->begin(batch); }); } void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("EndGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { timer->end(batch); }); @@ -87,14 +57,14 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& // Needs to be distinct from the other batch because using the clear call // while stereo is enabled triggers a warning if (_opaquePass) { - gpu::doInBatch(args->_context, [&](gpu::Batch& batch){ + gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch){ batch.enableStereo(false); batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); }); } // Render the items - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -127,7 +97,13 @@ void CompositeHUD::run(const RenderContextPointer& renderContext) { // Grab the HUD texture #if !defined(DISABLE_QML) - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("CompositeHUD", renderContext->args->_context, [&](gpu::Batch& batch) { + glm::mat4 projMat; + Transform viewMat; + renderContext->args->getViewFrustum().evalProjectionMatrix(projMat); + renderContext->args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); if (renderContext->args->_hudOperator) { renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); } @@ -154,7 +130,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer // Blit primary to blit FBO auto primaryFbo = srcFramebuffer; - gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("Blit", renderArgs->_context, [&](gpu::Batch& batch) { batch.setFramebuffer(blitFbo); if (renderArgs->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 50620bfcce..2377f5131f 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -34,6 +34,7 @@ #include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" #include "SurfaceGeometryPass.h" +#include "VelocityBufferPass.h" #include "FramebufferCache.h" #include "TextureCache.h" #include "ZoneRenderer.h" @@ -94,9 +95,12 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren fadeEffect->build(task, opaques); + task.addJob("JitterCam"); + // Prepare deferred, generate the shared Deferred Frame Transform const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); const auto lightingModel = task.addJob("LightingModel"); + // GPU jobs: Start preparing the primary, deferred and lighting buffer const auto primaryFramebuffer = task.addJob("PreparePrimaryBuffer"); @@ -142,6 +146,11 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN(0); const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN(1); + // Velocity + const auto velocityBufferInputs = VelocityBufferPass::Inputs(deferredFrameTransform, deferredFramebuffer).asVarying(); + const auto velocityBufferOutputs = task.addJob("VelocityBuffer", velocityBufferInputs); + const auto velocityBuffer = velocityBufferOutputs.getN(0); + // Clear Light, Haze and Skybox Stages and render zones from the general metas bucket const auto zones = task.addJob("ZoneRenderer", metas); @@ -162,6 +171,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("RenderDeferred", deferredLightingInputs); + // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); @@ -220,10 +230,30 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawSelectionBounds", selectedItems); } - // Debugging stages + // Layered Overlays + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer + task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); + task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); + } + + // AA job + const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, primaryFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); + task.addJob("Antialiasing", antialiasingInputs); + + // Debugging stages { // Debugging Deferred buffer job - const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, deferredFrameTransform)); + const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, velocityBuffer, deferredFrameTransform)); task.addJob("DebugDeferredBuffer", debugFramebuffers); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, @@ -250,25 +280,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); } - // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); - - { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer - task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); - task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); - } - - // AA job to be revisited - task.addJob("Antialiasing", primaryFramebuffer); - // Composite the HUD and HUD overlays task.addJob("HUD"); @@ -304,7 +315,7 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawDeferred::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup camera, projection and viewport for all items @@ -371,7 +382,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawStateSortDeferred::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup camera, projection and viewport for all items @@ -409,4 +420,3 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const config->setNumDrawn((int)inItems.size()); } - diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 8bf060751f..63370109e0 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -136,7 +136,7 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::Fra } auto args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("PrepareFramebuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); @@ -152,7 +152,8 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::Fra void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + + gpu::doInBatch("RenderForward::Draw::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; graphics::LightPointer keySunLight; @@ -186,7 +187,7 @@ void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& i const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawForward::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index afb6f00bae..69c5b3c689 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -140,7 +140,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con args->popViewFrustum(); args->pushViewFrustum(adjustedShadowFrustum); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderShadowMap::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.enableStereo(false); diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index f9af157e7c..b9b8274039 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -81,7 +81,7 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F return; } - doInBatch(args->_context, [&](gpu::Batch& batch) { + doInBatch("PrepareStencil::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index ff415accc3..50067d003f 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -322,7 +322,7 @@ void diffuseProfileGPU(gpu::TexturePointer& profileMap, RenderArgs* args) { auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseProfile")); makeFramebuffer->setRenderBuffer(0, profileMap); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::diffuseProfileGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -359,7 +359,7 @@ void diffuseScatterGPU(const gpu::TexturePointer& profileMap, gpu::TexturePointe auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("diffuseScatter")); makeFramebuffer->setRenderBuffer(0, lut); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::diffuseScatterGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -396,7 +396,7 @@ void computeSpecularBeckmannGPU(gpu::TexturePointer& beckmannMap, RenderArgs* ar auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("computeSpecularBeckmann")); makeFramebuffer->setRenderBuffer(0, beckmannMap); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SubsurfaceScattering::computeSpecularBeckmannGPU", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(glm::ivec4(0, 0, width, height)); @@ -537,7 +537,7 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo // const auto light = DependencyManager::get()->getLightStage()->getLight(0); const auto light = lightStage->getLight(0); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugSubsurfaceScattering::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index afed9ee8fd..cfdb67ecb6 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -174,7 +174,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con auto halfViewport = depthViewport >> 1; float clearLinearDepth = args->getViewFrustum().getFarClip() * 2.0f; - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("LinearDepthPass::run", args->_context, [=](gpu::Batch& batch) { _gpuTimer->begin(batch); batch.enableStereo(false); @@ -466,7 +466,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext, _diffusePass.getParameters()->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("SurfaceGeometryPass::run", args->_context, [=](gpu::Batch& batch) { _gpuTimer->begin(batch); batch.enableStereo(false); diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 0cc5be5ff9..6857de62a7 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -68,7 +68,7 @@ void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& ligh } auto framebufferSize = glm::ivec2(lightingBuffer->getDimensions()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("ToneMappingEffect::render", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFramebuffer); diff --git a/libraries/render-utils/src/VelocityBufferPass.cpp b/libraries/render-utils/src/VelocityBufferPass.cpp new file mode 100644 index 0000000000..78471d48af --- /dev/null +++ b/libraries/render-utils/src/VelocityBufferPass.cpp @@ -0,0 +1,173 @@ +// +// VelocityBufferPass.cpp +// libraries/render-utils/src/ +// +// Created by Sam Gateau 8/15/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 "VelocityBufferPass.h" + +#include + +#include +#include +#include "StencilMaskPass.h" + +const int VelocityBufferPass_FrameTransformSlot = 0; +const int VelocityBufferPass_DepthMapSlot = 0; + + +#include "velocityBuffer_cameraMotion_frag.h" + +VelocityFramebuffer::VelocityFramebuffer() { +} + + +void VelocityFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { + //If the depth buffer or size changed, we need to delete our FBOs + bool reset = false; + if ((_primaryDepthTexture != depthBuffer)) { + _primaryDepthTexture = depthBuffer; + reset = true; + } + if (_primaryDepthTexture) { + auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions()); + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + _halfFrameSize = newFrameSize >> 1; + + reset = true; + } + } + + if (reset) { + clear(); + } +} + +void VelocityFramebuffer::clear() { + _velocityFramebuffer.reset(); + _velocityTexture.reset(); +} + +void VelocityFramebuffer::allocate() { + + auto width = _frameSize.x; + auto height = _frameSize.y; + + // For Velocity Buffer: + _velocityTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::VEC2, gpu::HALF, gpu::RGB), width, height, gpu::Texture::SINGLE_MIP, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR)); + _velocityFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("velocity")); + _velocityFramebuffer->setRenderBuffer(0, _velocityTexture); + _velocityFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); +} + +gpu::FramebufferPointer VelocityFramebuffer::getVelocityFramebuffer() { + if (!_velocityFramebuffer) { + allocate(); + } + return _velocityFramebuffer; +} + +gpu::TexturePointer VelocityFramebuffer::getVelocityTexture() { + if (!_velocityTexture) { + allocate(); + } + return _velocityTexture; +} + +VelocityBufferPass::VelocityBufferPass() { +} + +void VelocityBufferPass::configure(const Config& config) { +} + +void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + RenderArgs* args = renderContext->args; + + const auto& frameTransform = inputs.get0(); + const auto& deferredFramebuffer = inputs.get1(); + + if (!_gpuTimer) { + _gpuTimer = std::make_shared < gpu::RangeTimer>(__FUNCTION__); + } + + if (!_velocityFramebuffer) { + _velocityFramebuffer = std::make_shared(); + } + _velocityFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture()); + + auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture(); + + auto velocityFBO = _velocityFramebuffer->getVelocityFramebuffer(); + auto velocityTexture = _velocityFramebuffer->getVelocityTexture(); + + outputs.edit0() = _velocityFramebuffer; + outputs.edit1() = velocityFBO; + outputs.edit2() = velocityTexture; + + auto cameraMotionPipeline = getCameraMotionPipeline(); + + auto fullViewport = args->_viewport; + + gpu::doInBatch("VelocityBufferPass::run", args->_context, [=](gpu::Batch& batch) { + _gpuTimer->begin(batch); + batch.enableStereo(false); + + batch.setViewportTransform(fullViewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_velocityFramebuffer->getDepthFrameSize(), fullViewport)); + + batch.setUniformBuffer(VelocityBufferPass_FrameTransformSlot, frameTransform->getFrameTransformBuffer()); + + // Velocity buffer camera motion + batch.setFramebuffer(velocityFBO); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); + batch.setPipeline(cameraMotionPipeline); + batch.setResourceTexture(VelocityBufferPass_DepthMapSlot, depthBuffer); + batch.draw(gpu::TRIANGLE_STRIP, 4); + + _gpuTimer->end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage()); +} + + +const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline() { + if (!_cameraMotionPipeline) { + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = velocityBuffer_cameraMotion_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), VelocityBufferPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), VelocityBufferPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + // Stencil test the curvature pass for objects pixels only, not the background + // PrepareStencil::testShape(*state); + + state->setColorWriteMask(true, true, false, false); + + // Good to go add the brand new pipeline + _cameraMotionPipeline = gpu::Pipeline::create(program, state); + } + + return _cameraMotionPipeline; +} + + + diff --git a/libraries/render-utils/src/VelocityBufferPass.h b/libraries/render-utils/src/VelocityBufferPass.h new file mode 100644 index 0000000000..fb2b729368 --- /dev/null +++ b/libraries/render-utils/src/VelocityBufferPass.h @@ -0,0 +1,89 @@ +// +// VelocityBufferPass.h +// libraries/render-utils/src/ +// +// Created by Sam Gateau 8/15/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_VelocityBufferPass_h +#define hifi_VelocityBufferPass_h + +#include "SurfaceGeometryPass.h" + + +// VelocityFramebuffer is a helper class gathering in one place theframebuffers and targets describing the surface geometry linear depth +// from a z buffer +class VelocityFramebuffer { +public: + VelocityFramebuffer(); + + gpu::FramebufferPointer getVelocityFramebuffer(); + gpu::TexturePointer getVelocityTexture(); + + // Update the depth buffer which will drive the allocation of all the other resources according to its size. + void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer); + + gpu::TexturePointer getPrimaryDepthTexture(); + const glm::ivec2& getDepthFrameSize() const { return _frameSize; } + + void setResolutionLevel(int level); + int getResolutionLevel() const { return _resolutionLevel; } + +protected: + void clear(); + void allocate(); + + gpu::TexturePointer _primaryDepthTexture; + + gpu::FramebufferPointer _velocityFramebuffer; + gpu::TexturePointer _velocityTexture; + + glm::ivec2 _frameSize; + glm::ivec2 _halfFrameSize; + int _resolutionLevel{ 0 }; +}; + +using VelocityFramebufferPointer = std::shared_ptr; + +class VelocityBufferPassConfig : public render::GPUJobConfig { + Q_OBJECT + Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) + +public: + VelocityBufferPassConfig() : render::GPUJobConfig(true) {} + + float depthThreshold{ 5.0f }; + +signals: + void dirty(); +}; + +class VelocityBufferPass { +public: + using Inputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; + using Config = VelocityBufferPassConfig; + using JobModel = render::Job::ModelIO; + + VelocityBufferPass(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + typedef gpu::BufferView UniformBufferView; + + VelocityFramebufferPointer _velocityFramebuffer; + + const gpu::PipelinePointer& getCameraMotionPipeline(); + gpu::PipelinePointer _cameraMotionPipeline; + + gpu::RangeTimerPointer _gpuTimer; +}; + + +#endif // hifi_VelocityBufferPass_h diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index b6d0e61577..51939efd4f 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -171,7 +171,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I } - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("DebugZoneLighting::run", args->_context, [=](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); auto viewFrustum = args->getViewFrustum(); diff --git a/libraries/render-utils/src/animdebugdraw.slv b/libraries/render-utils/src/animdebugdraw.slv index ffa44b6cee..3255c6783c 100644 --- a/libraries/render-utils/src/animdebugdraw.slv +++ b/libraries/render-utils/src/animdebugdraw.slv @@ -17,7 +17,7 @@ out vec4 _color; void main(void) { // pass along the color - _color = colorToLinearRGBA(inColor.rgba); + _color = color_sRGBAToLinear(inColor.rgba); TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index 426de623a1..fded04ca87 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -22,6 +22,7 @@ uniform sampler2D halfNormalMap; uniform sampler2D occlusionMap; uniform sampler2D occlusionBlurredMap; uniform sampler2D scatteringMap; +uniform sampler2D velocityMap; <@include ShadowCore.slh@> diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index 7b241a3ebf..94fa75c47f 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -23,72 +23,112 @@ precision mediump int; #endif uniform sampler2D colorTexture; +//uniform sampler2D historyTexture; uniform vec2 texcoordOffset; -in vec2 varTexcoord; -out vec4 outFragColor; +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; +//layout(location = 0) out vec4 outFragHistory; void main() { - // filter width limit for dependent "two-tap" texture samples - float FXAA_SPAN_MAX = 8.0; + outFragColor = vec4(texture(colorTexture, varTexCoord0).xyz, 1.0/8.0); - // local contrast multiplier for performing AA - // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail - // see "fxaaQualityEdgeThreshold" - float FXAA_REDUCE_MUL = 1.0 / 8.0; + // v2 + /* float ModulationFactor = 1.0 / 8.0; - // luminance threshold for processing dark colors - // see "fxaaQualityEdgeThresholdMin" - float FXAA_REDUCE_MIN = 1.0 / 128.0; + vec3 History = textureLod(historyTexture, varTexCoord0, 0.0).rgb; + vec3 CurrentSubpixel = textureLod(colorTexture, varTexCoord0, 0.0).rgb; + /* + vec3 NearColor0 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(1, 0)).xyz; + vec3 NearColor1 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(0, 1)).xyz; + vec3 NearColor2 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(-1, 0)).xyz; + vec3 NearColor3 = textureLodOffset(colorTexture, varTexCoord0, 0.0, ivec2(0, -1)).xyz; - // fetch raw RGB values for nearby locations - // sampling pattern is "five on a die" (each diagonal direction and the center) - // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed - vec3 rgbNW = texture(colorTexture, varTexcoord + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; - vec3 rgbNE = texture(colorTexture, varTexcoord + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; - vec3 rgbSW = texture(colorTexture, varTexcoord + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; - vec3 rgbSE = texture(colorTexture, varTexcoord + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; - vec3 rgbM = texture(colorTexture, varTexcoord).xyz; - - // convert RGB values to luminance - vec3 luma = vec3(0.299, 0.587, 0.114); - float lumaNW = dot(rgbNW, luma); - float lumaNE = dot(rgbNE, luma); - float lumaSW = dot(rgbSW, luma); - float lumaSE = dot(rgbSE, luma); - float lumaM = dot( rgbM, luma); - - // luma range of local neighborhood - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - - // direction perpendicular to local luma gradient - vec2 dir; - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + vec3 BoxMin = min(CurrentSubpixel, min(NearColor0, min(NearColor1, min(NearColor2, NearColor3)))); + vec3 BoxMax = max(CurrentSubpixel, max(NearColor0, max(NearColor1, max(NearColor2, NearColor3))));; - // compute clamped direction offset for additional "two-tap" samples - // longer vector = blurry, shorter vector = sharp - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), - max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; - - // perform additional texture sampling perpendicular to gradient - vec3 rgbA = (1.0 / 2.0) * ( - texture(colorTexture, varTexcoord + dir * (1.0 / 3.0 - 0.5)).xyz + - texture(colorTexture, varTexcoord + dir * (2.0 / 3.0 - 0.5)).xyz); - vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( - texture(colorTexture, varTexcoord + dir * (0.0 / 3.0 - 0.5)).xyz + - texture(colorTexture, varTexcoord + dir * (3.0 / 3.0 - 0.5)).xyz); - float lumaB = dot(rgbB, luma); + if (gl_FragCoord.x > 800) { + History = clamp(History, BoxMin, BoxMax); + } - // compare luma of new samples to the luma range of the original neighborhood - // if the new samples exceed this range, just use the first two samples instead of all four - if (lumaB < lumaMin || lumaB > lumaMax) { - outFragColor.xyz=rgbA; - } else { - outFragColor.xyz=rgbB; + History = mix(CurrentSubpixel, History, ModulationFactor); + + /* outFragHistory.xyz = History; + outFragHistory.w = ModulationFactor + + outFragColor.xyz = History; + outFragColor.w = 1.0;*/ + + + + /* } else { + outFragColor.xyz = CurrentSubpixel; + outFragColor.w = 1.0; + + }*/ + if (gl_FragCoord.x > 800) { + /* // filter width limit for dependent "two-tap" texture samples + float FXAA_SPAN_MAX = 8.0; + + // local contrast multiplier for performing AA + // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail + // see "fxaaQualityEdgeThreshold" + float FXAA_REDUCE_MUL = 1.0 / 8.0; + + // luminance threshold for processing dark colors + // see "fxaaQualityEdgeThresholdMin" + float FXAA_REDUCE_MIN = 1.0 / 128.0; + + // fetch raw RGB values for nearby locations + // sampling pattern is "five on a die" (each diagonal direction and the center) + // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed + vec3 rgbNW = texture(colorTexture, varTexCoord0 + (vec2(-1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbNE = texture(colorTexture, varTexCoord0 + (vec2(+1.0, -1.0) * texcoordOffset)).xyz; + vec3 rgbSW = texture(colorTexture, varTexCoord0 + (vec2(-1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbSE = texture(colorTexture, varTexCoord0 + (vec2(+1.0, +1.0) * texcoordOffset)).xyz; + vec3 rgbM = texture(colorTexture, varTexCoord0).xyz; + + // convert RGB values to luminance + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + + // luma range of local neighborhood + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + // direction perpendicular to local luma gradient + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + // compute clamped direction offset for additional "two-tap" samples + // longer vector = blurry, shorter vector = sharp + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texcoordOffset; + + // perform additional texture sampling perpendicular to gradient + vec3 rgbA = (1.0 / 2.0) * ( + texture(colorTexture, varTexCoord0 + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(colorTexture, varTexCoord0 + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( + texture(colorTexture, varTexCoord0 + dir * (0.0 / 3.0 - 0.5)).xyz + + texture(colorTexture, varTexCoord0 + dir * (3.0 / 3.0 - 0.5)).xyz); + float lumaB = dot(rgbB, luma); + + // compare luma of new samples to the luma range of the original neighborhood + // if the new samples exceed this range, just use the first two samples instead of all four + if (lumaB < lumaMin || lumaB > lumaMax) { + outFragColor.xyz = rgbA; + } + else { + outFragColor.xyz = rgbB; + }*/ + outFragColor.a = 1.0; } - outFragColor.a = 1.0; } diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index d5819cc9a6..7a10cecb94 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -14,11 +14,27 @@ <@include DeferredBufferWrite.slh@> -in vec2 varTexcoord; +in vec2 varTexCoord0; out vec4 outFragColor; uniform sampler2D colorTexture; +uniform float sharpenIntensity; void main(void) { - outFragColor = texture(colorTexture, varTexcoord); + vec4 pixels[9]; + vec4 sharpenedPixel; + pixels[0] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,-1), 0); + pixels[1] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(0,-1), 0); + pixels[2] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,-1), 0); + + pixels[3] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,0), 0); + pixels[4] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); + pixels[5] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,0), 0); + + pixels[6] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(-1,1), 0); + pixels[7] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(0,1), 0); + pixels[8] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,1), 0); + + sharpenedPixel = pixels[4]*7.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; + outFragColor = mix(pixels[4], sharpenedPixel, sharpenIntensity); } diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 06f6030e77..ccedff9b61 100644 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -27,7 +27,7 @@ out vec4 _position; out vec3 _normal; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_fade.slv b/libraries/render-utils/src/model_fade.slv index 4c6bc534a9..61b8e9e1b6 100644 --- a/libraries/render-utils/src/model_fade.slv +++ b/libraries/render-utils/src/model_fade.slv @@ -28,7 +28,7 @@ out vec3 _normal; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index 161ceed14c..e00fcb708e 100644 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -28,7 +28,7 @@ out vec3 _color; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap_fade.slv b/libraries/render-utils/src/model_lightmap_fade.slv index 561049d614..d1a1194de1 100644 --- a/libraries/render-utils/src/model_lightmap_fade.slv +++ b/libraries/render-utils/src/model_lightmap_fade.slv @@ -29,7 +29,7 @@ out vec4 _worldPosition; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); // and the texture coordinates TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index 5fb60d9227..3b1ecaab0c 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -29,7 +29,7 @@ out vec3 _color; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> diff --git a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv index 4049fb0077..5c1212b1b7 100644 --- a/libraries/render-utils/src/model_lightmap_normal_map_fade.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map_fade.slv @@ -30,7 +30,7 @@ out vec4 _worldPosition; void main(void) { // pass along the color in linear space - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); TexMapArray texMapArray = getTexMapArray(); <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index 9e674d93fc..a84f8c5e2a 100644 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -30,7 +30,7 @@ out float _alpha; void main(void) { // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_normal_map_fade.slv b/libraries/render-utils/src/model_normal_map_fade.slv index a71900d5c3..6a6142d317 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slv +++ b/libraries/render-utils/src/model_normal_map_fade.slv @@ -31,7 +31,7 @@ out float _alpha; void main(void) { // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv index 305aba06c3..7a57a4baec 100644 --- a/libraries/render-utils/src/model_translucent.slv +++ b/libraries/render-utils/src/model_translucent.slv @@ -28,7 +28,7 @@ out vec3 _normal; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/model_translucent_normal_map.slv b/libraries/render-utils/src/model_translucent_normal_map.slv index db824a3709..981a03627b 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slv +++ b/libraries/render-utils/src/model_translucent_normal_map.slv @@ -29,7 +29,7 @@ out vec3 _tangent; out vec3 _color; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/overlay3D.slv b/libraries/render-utils/src/overlay3D.slv index ee28367413..7184f923e4 100644 --- a/libraries/render-utils/src/overlay3D.slv +++ b/libraries/render-utils/src/overlay3D.slv @@ -23,7 +23,7 @@ out vec4 _position; out vec3 _normal; void main(void) { - _color = colorToLinearRGB(inColor.xyz); + _color = color_sRGBToLinear(inColor.xyz); _alpha = inColor.w; _texCoord0 = inTexCoord0.st; diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 64d3e24192..0ce6505a65 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -25,7 +25,7 @@ out vec2 _texCoord0; out vec4 _position; void main(void) { - _color = colorToLinearRGBA(inColor); + _color = color_sRGBAToLinear(inColor); _texCoord0 = inTexCoord0.st; _position = inPosition; _modelNormal = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_fade.slv b/libraries/render-utils/src/simple_fade.slv index 3d9eb2c812..85946045ac 100644 --- a/libraries/render-utils/src/simple_fade.slv +++ b/libraries/render-utils/src/simple_fade.slv @@ -29,7 +29,7 @@ out vec4 _position; out vec4 _worldPosition; void main(void) { - _color = colorToLinearRGBA(inColor); + _color = color_sRGBAToLinear(inColor); _texCoord0 = inTexCoord0.st; _position = inPosition; _modelNormal = inNormal.xyz; diff --git a/libraries/render-utils/src/simple_opaque_web_browser.slf b/libraries/render-utils/src/simple_opaque_web_browser.slf index 3acf104b55..af7ef78682 100644 --- a/libraries/render-utils/src/simple_opaque_web_browser.slf +++ b/libraries/render-utils/src/simple_opaque_web_browser.slf @@ -25,6 +25,6 @@ in vec2 _texCoord0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); packDeferredFragmentUnlit(normalize(_normal), 1.0, _color.rgb * texel.rgb); } diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 34fcbc77dc..4fd734aad5 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -28,7 +28,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf index 2061cabdfc..d378e7a5c1 100644 --- a/libraries/render-utils/src/simple_textured_fade.slf +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -40,7 +40,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_unlit.slf b/libraries/render-utils/src/simple_textured_unlit.slf index d261fb343a..1daea2f5c7 100644 --- a/libraries/render-utils/src/simple_textured_unlit.slf +++ b/libraries/render-utils/src/simple_textured_unlit.slf @@ -27,7 +27,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_textured_unlit_fade.slf b/libraries/render-utils/src/simple_textured_unlit_fade.slf index 6f03c6746f..b3c5a914b2 100644 --- a/libraries/render-utils/src/simple_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_textured_unlit_fade.slf @@ -39,7 +39,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 30c420233f..c6b0d83914 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -34,7 +34,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float opacity = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); opacity = -_color.a; } opacity *= texel.a; diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index a8a5875a4b..a5103660b9 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -46,7 +46,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float opacity = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); opacity = -_color.a; } opacity *= texel.a; diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit.slf b/libraries/render-utils/src/simple_transparent_textured_unlit.slf index 693d7be2db..e9c1104cf0 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit.slf @@ -29,7 +29,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } _fragColor0 = vec4(_color.rgb * texel.rgb, colorAlpha * texel.a); diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf index 1c42a1f724..093b70755f 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf @@ -40,7 +40,7 @@ void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); float colorAlpha = _color.a; if (_color.a <= 0.0) { - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); colorAlpha = -_color.a; } _fragColor0 = vec4(_color.rgb * texel.rgb+fadeEmissive, colorAlpha * texel.a); diff --git a/libraries/render-utils/src/simple_transparent_web_browser.slf b/libraries/render-utils/src/simple_transparent_web_browser.slf index 19079f5d92..414f3f683f 100644 --- a/libraries/render-utils/src/simple_transparent_web_browser.slf +++ b/libraries/render-utils/src/simple_transparent_web_browser.slf @@ -25,7 +25,7 @@ in vec2 _texCoord0; void main(void) { vec4 texel = texture(originalTexture, _texCoord0.st); - texel = colorToLinearRGBA(texel); + texel = color_sRGBAToLinear(texel); packDeferredFragmentTranslucent( normalize(_normal), _color.a, diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index bd1655fc40..480e48a5d4 100644 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -37,7 +37,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_dq.slv b/libraries/render-utils/src/skin_model_dq.slv index 96f9b4a713..6e369c7c82 100644 --- a/libraries/render-utils/src/skin_model_dq.slv +++ b/libraries/render-utils/src/skin_model_dq.slv @@ -37,7 +37,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv index b14bf1532e..4f459d75f3 100644 --- a/libraries/render-utils/src/skin_model_fade.slv +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -38,7 +38,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_fade_dq.slv b/libraries/render-utils/src/skin_model_fade_dq.slv index 4f8a923a03..232170d714 100644 --- a/libraries/render-utils/src/skin_model_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_fade_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_fade.vert +// skin_model_fade_dq.vert // vertex shader // // Created by Olivier Prat on 06/045/17. @@ -38,7 +38,7 @@ void main(void) { skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index 666bdf865f..b54c84e5b3 100644 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -39,7 +39,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_dq.slv b/libraries/render-utils/src/skin_model_normal_map_dq.slv index 02b3742f6f..b34f68d291 100644 --- a/libraries/render-utils/src/skin_model_normal_map_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_dq.slv @@ -39,7 +39,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv index d72e47702d..0e788b81b5 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -40,7 +40,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv index d6e07575b1..230077ba3b 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// skin_model_normal_map.vert +// skin_model_normal_map_fade_dq.vert // vertex shader // // Created by Andrzej Kapolka on 10/29/13. @@ -40,7 +40,7 @@ void main(void) { skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); // pass along the color - _color = colorToLinearRGB(inColor.rgb); + _color = color_sRGBToLinear(inColor.rgb); _alpha = inColor.a; TexMapArray texMapArray = getTexMapArray(); diff --git a/libraries/render-utils/src/standardTransformPNTC.slv b/libraries/render-utils/src/standardTransformPNTC.slv index 0ced5ba6e2..8ec685cea0 100644 --- a/libraries/render-utils/src/standardTransformPNTC.slv +++ b/libraries/render-utils/src/standardTransformPNTC.slv @@ -24,7 +24,7 @@ out vec4 varColor; void main(void) { varTexCoord0 = inTexCoord0.st; - varColor = colorToLinearRGBA(inColor); + varColor = color_sRGBAToLinear(inColor); // standard transform TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/taa.slf b/libraries/render-utils/src/taa.slf new file mode 100644 index 0000000000..8d172871d4 --- /dev/null +++ b/libraries/render-utils/src/taa.slf @@ -0,0 +1,51 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// taa.frag +// fragment shader +// +// Created by Sam Gateau on 8/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 +// + + +<@include taa.slh@> + +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; + +void main() { + vec2 fragUV = varTexCoord0; + + // Debug region before debug or fxaa region X + float distToRegionFXAA = fragUV.x - taa_getRegionFXAA().x; + if (distToRegionFXAA > 0.0) { + outFragColor = vec4(taa_evalFXAA(fragUV), 1.0); + return; + } + + vec2 fragVel = taa_fetchVelocityMapBest(fragUV).xy; + + vec3 sourceColor; + vec3 historyColor; + vec2 prevFragUV = taa_fetchSourceAndHistory(fragUV, fragVel, sourceColor, historyColor); + + vec3 nextColor = sourceColor; + + if (taa_constrainColor()) { + // clamp history to neighbourhood of current sample + historyColor = taa_evalConstrainColor(sourceColor, fragUV, fragVel, historyColor); + } + + if (taa_feedbackColor()) { + nextColor = taa_evalFeedbackColor(sourceColor, historyColor, params.blend); + } else { + nextColor = mix(historyColor, sourceColor, params.blend); + } + + outFragColor = vec4(taa_resolveColor(nextColor), 1.0); +} diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh new file mode 100644 index 0000000000..583e2978c4 --- /dev/null +++ b/libraries/render-utils/src/taa.slh @@ -0,0 +1,529 @@ +// Generated on <$_SCRIBE_DATE$> +// +// TAA.slh +// Common component needed by TemporalAntialiasing fragment shader +// +// Created by Sam Gateau on 8/17/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 DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +<@include gpu/Color.slh@> + +uniform sampler2D depthMap; +uniform sampler2D sourceMap; +uniform sampler2D historyMap; +uniform sampler2D velocityMap; +uniform sampler2D nextMap; + +struct TAAParams +{ + float none; + float blend; + float covarianceGamma; + float debugShowVelocityThreshold; + ivec4 flags; + vec4 pixelInfo_orbZoom; + vec4 regionInfo; +}; + +layout(std140) uniform taaParamsBuffer { + TAAParams params; +}; + +#define GET_BIT(bitfield, bitIndex) bool((bitfield) & (1 << (bitIndex))) + +bool taa_isDebugEnabled() { + return GET_BIT(params.flags.x, 0); +} + +bool taa_showDebugCursor() { + return GET_BIT(params.flags.x, 1); +} + +bool taa_showClosestFragment() { + return GET_BIT(params.flags.x, 3); +} + +bool taa_constrainColor() { + return GET_BIT(params.flags.y, 1); +} + +bool taa_feedbackColor() { + return GET_BIT(params.flags.y, 4); +} + +vec2 taa_getDebugCursorTexcoord() { + return params.pixelInfo_orbZoom.xy; +} + +float taa_getDebugOrbZoom() { + return params.pixelInfo_orbZoom.z; +} + +vec2 taa_getRegionDebug() { + return params.regionInfo.xy; +} + +vec2 taa_getRegionFXAA() { + return params.regionInfo.zw; +} +#define USE_YCOCG 1 + +vec4 taa_fetchColor(sampler2D map, vec2 uv) { +#if USE_YCOCG + vec4 c = texture(map, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(map, uv); +#endif +} + +vec3 taa_resolveColor(vec3 color) { +#if USE_YCOCG + return color_YCoCgToLinear(color); +#else + return color; +#endif +} + +vec4 taa_fetchSourceMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(sourceMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(sourceMap, uv); +#endif +} + +vec4 taa_fetchHistoryMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(historyMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(historyMap, uv); +#endif +} + +vec4 taa_fetchNextMap(vec2 uv) { +#if USE_YCOCG + vec4 c = texture(nextMap, uv); + return vec4(color_LinearToYCoCg(c.rgb), c.a); +#else + return texture(nextMap, uv); +#endif +} + +vec2 taa_fetchVelocityMap(vec2 uv) { + return texture(velocityMap, uv).xy; +} + +float taa_fetchDepth(vec2 uv) { + return -texture(depthMap, vec2(uv), 0).x; +} + + +#define ZCMP_GT(a, b) (a > b) + +vec2 taa_getImageSize() { + vec2 imageSize = getWidthHeight(0); + if (isStereo()) { + imageSize.x *= 2.0; + } + return imageSize; +} + +vec2 taa_getTexelSize() { + vec2 texelSize = getInvWidthHeight(); + if (isStereo()) { + texelSize.x *= 0.5; + } + return texelSize; +} + +vec3 taa_findClosestFragment3x3(vec2 uv) +{ + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); + + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + + vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); + vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); + vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); + + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + + vec3 dmin = dtl; + if (ZCMP_GT(dmin.z, dtc.z)) dmin = dtc; + if (ZCMP_GT(dmin.z, dtr.z)) dmin = dtr; + + if (ZCMP_GT(dmin.z, dml.z)) dmin = dml; + if (ZCMP_GT(dmin.z, dmc.z)) dmin = dmc; + if (ZCMP_GT(dmin.z, dmr.z)) dmin = dmr; + + if (ZCMP_GT(dmin.z, dbl.z)) dmin = dbl; + if (ZCMP_GT(dmin.z, dbc.z)) dmin = dbc; + if (ZCMP_GT(dmin.z, dbr.z)) dmin = dbr; + + return vec3(uv + dd.xy * dmin.xy, dmin.z); +} + +vec2 taa_fetchVelocityMapBest(vec2 uv) { + vec2 dd = abs(taa_getTexelSize()); + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); + + vec2 dtl = taa_fetchVelocityMap(uv - dv - du); + vec2 dtc = taa_fetchVelocityMap(uv - dv); + vec2 dtr = taa_fetchVelocityMap(uv - dv + du); + + vec2 dml = taa_fetchVelocityMap(uv - du); + vec2 dmc = taa_fetchVelocityMap(uv); + vec2 dmr = taa_fetchVelocityMap(uv + du); + + vec2 dbl = taa_fetchVelocityMap(uv + dv - du); + vec2 dbc = taa_fetchVelocityMap(uv + dv); + vec2 dbr = taa_fetchVelocityMap(uv + dv + du); + + vec3 best = vec3(dtl, dot(dtl,dtl)); + + float testSpeed = dot(dtc,dtc); + if (testSpeed > best.z) { best = vec3(dtc, testSpeed); } + testSpeed = dot(dtr,dtr); + if (testSpeed > best.z) { best = vec3(dtr, testSpeed); } + + testSpeed = dot(dml,dml); + if (testSpeed > best.z) { best = vec3(dml, testSpeed); } + testSpeed = dot(dmc,dmc); + if (testSpeed > best.z) { best = vec3(dmc, testSpeed); } + testSpeed = dot(dmr,dmr); + if (testSpeed > best.z) { best = vec3(dmr, testSpeed); } + + testSpeed = dot(dbl,dbl); + if (testSpeed > best.z) { best = vec3(dbl, testSpeed); } + testSpeed = dot(dbc,dbc); + if (testSpeed > best.z) { best = vec3(dbc, testSpeed); } + testSpeed = dot(dbr,dbr); + if (testSpeed > best.z) { best = vec3(dbr, testSpeed); } + + return best.xy; +} + +vec2 taa_fromFragUVToEyeUVAndSide(vec2 fragUV, out int stereoSide) { + vec2 eyeUV = fragUV; + stereoSide = 0; + if (isStereo()) { + if (eyeUV.x > 0.5) { + eyeUV.x -= 0.5; + stereoSide = 1; + } + eyeUV.x *= 2.0; + } + return eyeUV; +} + +vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { + vec2 fragUV = eyeUV; + if (isStereo()) { + fragUV.x *= 0.5; + fragUV.x += stereoSide*0.5; + } + return fragUV; +} + +vec2 taa_computePrevFragAndEyeUV(vec2 fragUV, vec2 fragVelocity, out vec2 prevEyeUV) { + int stereoSide = 0; + vec2 eyeUV = taa_fromFragUVToEyeUVAndSide(fragUV, stereoSide); + prevEyeUV = eyeUV - fragVelocity; + return taa_fromEyeUVToFragUV(prevEyeUV, stereoSide); +} + +vec2 taa_fetchSourceAndHistory(vec2 fragUV, vec2 fragVelocity, out vec3 sourceColor, out vec3 historyColor) { + vec2 prevEyeUV; + vec2 prevFragUV = taa_computePrevFragAndEyeUV(fragUV, fragVelocity, prevEyeUV); + sourceColor = taa_fetchSourceMap(fragUV).xyz; + + historyColor = sourceColor; + if (!(any(lessThan(prevEyeUV, vec2(0.0))) || any(greaterThan(prevEyeUV, vec2(1.0))))) { + historyColor = taa_fetchHistoryMap(prevFragUV).xyz; + } + return prevFragUV; +} + +float Luminance(vec3 rgb) { + return rgb.x/4.0 + rgb.y/2.0 + rgb.z/4.0; +} + +#define MINMAX_3X3_ROUNDED 1 + +mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity) { + vec2 texelSize = taa_getTexelSize(); + + + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); + + vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 sumSamples = sampleColor; + vec3 sumSamples2 = sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - dv).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - dv + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV - du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sampleColor isn't it ? + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv - du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + sampleColor = taa_fetchSourceMap(fragUV + dv + du).rgb; + sumSamples += sampleColor; + sumSamples2 += sampleColor * sampleColor; + + + vec3 mu = sumSamples / vec3(9.0); + vec3 sigma = sqrt(max(sumSamples2 / vec3(9.0) - mu * mu, vec3(0.0))); + + float gamma = params.covarianceGamma; + vec3 cmin = mu - gamma * sigma; + vec3 cmax = mu + gamma * sigma; + + return mat3(cmin, cmax, mu); +} + +mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity, float fragZe) { + vec2 imageSize = taa_getImageSize(); + vec2 texelSize = taa_getTexelSize(); + vec3 cmin, cmax, cavg; + + #if MINMAX_3X3_ROUNDED + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); + + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #elif + cavg = (cmin + cmax ) * 0.5; + #endif + + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); + #endif + #else + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; + + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); + cavg = (cmin + cmax ) * 0.5; + + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; + #elif + cavg = (cmin + cmax ) * 0.5; + #endif + #endif + + // shrink chroma min-max + #if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; + #endif + + return mat3(cmin, cmax, cavg); +} + +//#define USE_OPTIMIZATIONS 0 + +vec3 taa_clampColor(vec3 colorMin, vec3 colorMax, vec3 colorSource, vec3 color) { + const float eps = 0.00001; + vec3 p = colorSource; + vec3 q = color; + // note: only clips towards aabb center (but fast!) + vec3 p_clip = 0.5 * (colorMax + colorMin); + vec3 e_clip = 0.5 * (colorMax - colorMin) + vec3(eps); + + vec3 v_clip = q - p_clip; + vec3 v_unit = v_clip.xyz / e_clip; + vec3 a_unit = abs(v_unit); + float ma_unit = max(a_unit.x, max(a_unit.y, a_unit.z)); + + if (ma_unit > 1.0) + return p_clip + v_clip / ma_unit; + else + return q;// point inside aabb +} + +vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec3 candidateColor) { + mat3 colorMinMaxAvg; + + colorMinMaxAvg = taa_evalNeighbourColorVariance(sourceColor, sourceUV, sourceVel); + + // clamp history to neighbourhood of current sample + return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); +} + +vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { + const float _FeedbackMin = 0.1; + const float _FeedbackMax = 0.9; + // feedback weight from unbiased luminance diff (t.lottes) + #if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; + #else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); + #endif + float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); + float unbiased_weight = 1.0 - unbiased_diff; + float unbiased_weight_sqr = unbiased_weight * unbiased_weight; + float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); + + + vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; + return nextColor; +} + + +<$declareColorWheel()$> + +vec3 taa_getVelocityColorRelative(float velocityPixLength) { + return colorRamp(velocityPixLength/params.debugShowVelocityThreshold); +} + +vec3 taa_getVelocityColorAboveThreshold(float velocityPixLength) { + return colorRamp((velocityPixLength - params.debugShowVelocityThreshold)/params.debugShowVelocityThreshold); +} + + +vec3 taa_evalFXAA(vec2 fragUV) { + + // vec2 texelSize = getInvWidthHeight(); + vec2 texelSize = taa_getTexelSize(); + + // filter width limit for dependent "two-tap" texture samples + float FXAA_SPAN_MAX = 8.0; + + // local contrast multiplier for performing AA + // higher = sharper, but setting this value too high will cause near-vertical and near-horizontal edges to fail + // see "fxaaQualityEdgeThreshold" + float FXAA_REDUCE_MUL = 1.0 / 8.0; + + // luminance threshold for processing dark colors + // see "fxaaQualityEdgeThresholdMin" + float FXAA_REDUCE_MIN = 1.0 / 128.0; + + // fetch raw RGB values for nearby locations + // sampling pattern is "five on a die" (each diagonal direction and the center) + // computing the coordinates for these texture reads could be moved to the vertex shader for speed if needed + vec3 rgbNW = texture(sourceMap, fragUV + (vec2(-1.0, -1.0) * texelSize)).xyz; + vec3 rgbNE = texture(sourceMap, fragUV + (vec2(+1.0, -1.0) * texelSize)).xyz; + vec3 rgbSW = texture(sourceMap, fragUV + (vec2(-1.0, +1.0) * texelSize)).xyz; + vec3 rgbSE = texture(sourceMap, fragUV + (vec2(+1.0, +1.0) * texelSize)).xyz; + vec3 rgbM = texture(sourceMap, fragUV).xyz; + + // convert RGB values to luminance + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot( rgbM, luma); + + // luma range of local neighborhood + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + // direction perpendicular to local luma gradient + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + // compute clamped direction offset for additional "two-tap" samples + // longer vector = blurry, shorter vector = sharp + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * texelSize; + + // perform additional texture sampling perpendicular to gradient + vec3 rgbA = (1.0 / 2.0) * ( + texture(sourceMap, fragUV + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(sourceMap, fragUV + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * (1.0 / 2.0) + (1.0 / 4.0) * ( + texture(sourceMap, fragUV + dir * (0.0 / 3.0 - 0.5)).xyz + + texture(sourceMap, fragUV + dir * (3.0 / 3.0 - 0.5)).xyz); + float lumaB = dot(rgbB, luma); + + // compare luma of new samples to the luma range of the original neighborhood + // if the new samples exceed this range, just use the first two samples instead of all four + if (lumaB < lumaMin || lumaB > lumaMax) { + return rgbA; + } else { + return rgbB; + } +} \ No newline at end of file diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf new file mode 100644 index 0000000000..aca934ca71 --- /dev/null +++ b/libraries/render-utils/src/taa_blend.slf @@ -0,0 +1,156 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// taa_blend.frag +// fragment shader +// +// Created by Sam Gateau on 8/17/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 taa.slh@> + +in vec2 varTexCoord0; +layout(location = 0) out vec4 outFragColor; + +void main(void) { + vec3 nextColor = texture(nextMap, varTexCoord0).xyz; + outFragColor = vec4(nextColor, 1.0); + + + // Pixel being shaded + vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; + + vec2 imageSize = getWidthHeight(0); + vec2 texelSize = getInvWidthHeight(); + + vec2 pixPos = varTexCoord0 * imageSize; + vec2 pixVelocity = imageSize * texture(velocityMap, varTexCoord0).xy; + float pixVelocityLength = length(pixVelocity); + vec2 velocity = pixVelocity * texelSize; + int stereoSide = 0; + vec2 prevTexCoord = taa_fromFragUVToEyeUVAndSide(varTexCoord0, stereoSide) - velocity; + prevTexCoord = taa_fromEyeUVToFragUV(prevTexCoord, stereoSide); + vec2 prevPix = prevTexCoord * imageSize; + + // Pixel Debugged + if (taa_showDebugCursor()) { + vec2 cursorUVRaw = taa_getDebugCursorTexcoord(); + vec2 cursorPosRaw = floor(cursorUVRaw * imageSize) + vec2(0.5); + vec3 cursorFrag = taa_findClosestFragment3x3(cursorUVRaw); + vec2 cursorUV = cursorUVRaw; + vec2 cursorPos = cursorUV * imageSize; + vec2 cursorVelocity = texture(velocityMap, cursorUV).xy; + vec2 cursorPrevUV = taa_fromFragUVToEyeUVAndSide(cursorUV, stereoSide) - cursorVelocity; + cursorVelocity *= imageSize; + float cursorVelocityLength = length(cursorVelocity); + vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; + + vec2 cursorToFragVec = pixPos - cursorPos; + float cursorToFragLength = length(cursorToFragVec); + + if ((cursorToFragLength <= cursorVelocityLength)) { + vec2 cursorVelocityDir = cursorVelocity / cursorVelocityLength; + vec2 cursorVelocityNor = vec2(cursorVelocityDir.y, -cursorVelocityDir.x); + + if ((dot(cursorVelocityDir, cursorToFragVec) < 0) && abs(dot(cursorVelocityNor, cursorToFragVec)) < 1.0) { + + vec3 speedColor = taa_getVelocityColorRelative(cursorToFragLength); + + outFragColor = vec4(speedColor, 1.0); + return; + } + } + + float tenPercentHeight = 0.1 * imageSize.y; + float centerWidth = imageSize.x * 0.5; + + //vec2 nextOrbPos = vec2(centerWidth, imageSize.y - 3 * tenPercentHeight); + vec2 nextOrbPos = cursorPos; + vec2 nextOrbPosToPix = pixPos - nextOrbPos; + float nextOrbPosToPixLength = length(nextOrbPosToPix); + + vec2 prevOrbPos = nextOrbPos - cursorVelocityDir * 2.0 * tenPercentHeight; + vec2 prevOrbPosToPix = pixPos - prevOrbPos; + float prevOrbPosToPixLength = length(prevOrbPosToPix); + + float orbPixThreshold = 2.0 / taa_getDebugOrbZoom(); + + if ((prevOrbPosToPixLength < tenPercentHeight) && (cursorVelocityLength > 0.5)) { + vec2 prevOrbPosToPix_uv = cursorPrevUV + prevOrbPosToPix * texelSize / taa_getDebugOrbZoom(); + vec3 preOrbColor = vec3(0.0); + if (!(any(lessThan(prevOrbPosToPix_uv, vec2(0.0))) || any(greaterThan(prevOrbPosToPix_uv, vec2(1.0))))) { + preOrbColor = texture(historyMap, prevOrbPosToPix_uv).xyz; + } + if (prevOrbPosToPixLength < orbPixThreshold) { + preOrbColor = vec3(1.0, 0.0, 1.0); + } + float distanceToNext = length(imageSize * (cursorUV - prevOrbPosToPix_uv)); + if (distanceToNext < orbPixThreshold) { + preOrbColor = vec3(1.0, 0.5, 0.0); + } + outFragColor = vec4(preOrbColor, 1.0); + return; + } + if (nextOrbPosToPixLength < tenPercentHeight) { + vec2 nextOrbPosToPix_uv = cursorUV + nextOrbPosToPix * texelSize / taa_getDebugOrbZoom(); + vec3 nextOrbColor = vec3(0.0); + if (!(any(lessThan(nextOrbPosToPix_uv, vec2(0.0))) || any(greaterThan(nextOrbPosToPix_uv, vec2(1.0))))) { + nextOrbColor = texture(nextMap, nextOrbPosToPix_uv).xyz; + } + float distanceToPrev = length(imageSize * (cursorPrevUV - nextOrbPosToPix_uv)); + if (distanceToPrev < orbPixThreshold) { + nextOrbColor = vec3(1.0, 0.0, 1.0); + } + if (nextOrbPosToPixLength < orbPixThreshold) { + nextOrbColor = vec3(1.0, 0.5, 0.0); + } + + outFragColor = vec4(nextOrbColor, 1.0); + return; + } + } + + // Debug region before debug or fxaa region X + float distToRegionDebug = varTexCoord0.x - taa_getRegionDebug().x; + float distToRegionFXAA = varTexCoord0.x - taa_getRegionFXAA().x; + if ((distToRegionFXAA < 0.0) && (distToRegionDebug > 0.0)) { + return; + } + + // draw region splitter + if ((abs(distToRegionDebug) < getInvWidthHeight().x) || (abs(distToRegionFXAA) < getInvWidthHeight().x)) { + outFragColor.rgb = vec3(1.0, 1.0, 0.0); + return; + } + + if (distToRegionFXAA > 0.0) { + return; + } + + if (taa_showClosestFragment()) { + vec3 fragUV = taa_findClosestFragment3x3(varTexCoord0); + outFragColor = vec4((fragUV.xy - varTexCoord0) * imageSize * 0.5 + vec2(0.5), 0.0, 1.0); + return; + } + + outFragColor = vec4(nextColor, 1.0); + + vec3 prevColor = nextColor; + + if (!(any(lessThan(prevTexCoord, vec2(0.0))) || any(greaterThan(prevTexCoord, vec2(1.0))))) { + prevColor = texture(historyMap, prevTexCoord).xyz; + } + + outFragColor.xyz = mix(prevColor, vec3(1,0,1), clamp(distance(prevColor, nextColor) - 0.01, 0, 1)); + + if (pixVelocityLength > params.debugShowVelocityThreshold) { + vec3 speedColor = taa_getVelocityColorAboveThreshold(pixVelocityLength); + + outFragColor = vec4(0.0, 1.0, 1.0, 1.0); + } +} diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index d3d25431d0..cd171db855 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -243,7 +243,7 @@ void Font::setupGPU() { state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*state); + PrepareStencil::testMaskDrawShape(*state); _pipeline = gpu::Pipeline::create(program, state); auto transparentState = std::make_shared(); @@ -252,7 +252,7 @@ void Font::setupGPU() { transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShapeNoAA(*transparentState); + PrepareStencil::testMaskDrawShape(*transparentState); _transparentPipeline = gpu::Pipeline::create(programTransparent, transparentState); } diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf new file mode 100644 index 0000000000..22a95b55d1 --- /dev/null +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -0,0 +1,41 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gateau on 6/3/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + +in vec2 varTexCoord0; +out vec4 outFragColor; + +uniform sampler2D depthMap; + + +void main(void) { + // Pixel being shaded + ivec2 pixelPos; + vec2 texcoordPos; + ivec4 stereoSide; + ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); + + float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; + + // The position of the pixel fragment in Eye space then in world space + vec3 eyePos = evalEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); + vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; + + vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; + vec4 prevClipPos = (frameTransform._projection[stereoSide.x] * vec4(prevEyePos, 1.0)); + vec2 prevUV = 0.5 * (prevClipPos.xy / prevClipPos.w) + vec2(0.5); + + //vec2 imageSize = getWidthHeight(0); + vec2 imageSize = vec2(1.0, 1.0); + outFragColor = vec4( ((texcoordPos - prevUV) * imageSize), 0.0, 0.0); +} diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 0625179a6d..4c3c52e07b 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -286,7 +286,7 @@ void BlurGaussian::run(const RenderContextPointer& renderContext, const gpu::Fra _parameters->setWidthHeight(blurredFramebuffer->getWidth(), blurredFramebuffer->getHeight(), args->isStereo()); _parameters->setTexcoordTransform(gpu::Framebuffer::evalSubregionTexcoordTransformCoefficients(textureSize, viewport)); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("BlurGaussian::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(viewport); @@ -401,7 +401,7 @@ void BlurGaussianDepthAware::run(const RenderContextPointer& renderContext, cons _parameters->setDepthPerspective(args->getViewFrustum().getProjection()[1][1]); _parameters->setLinearDepthPosFar(args->getViewFrustum().getFarClip()); - gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { + gpu::doInBatch("BlurGaussianDepthAware::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(sourceViewport); diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 6a35623fa0..433851eec2 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -94,7 +94,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS std::static_pointer_cast(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells(); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); @@ -201,7 +201,7 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite RenderArgs* args = renderContext->args; auto& scene = renderContext->_scene; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawItemSelection::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index a11e9b1a88..56802a3239 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -163,7 +163,7 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const ItemBounds } // Allright, something to render let's do it - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawStatus::run", args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; args->getViewFrustum().evalProjectionMatrix(projMat); diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 88d38d1c66..bdff97c1c1 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -143,7 +143,7 @@ void DrawLight::run(const RenderContextPointer& renderContext, const ItemBounds& RenderArgs* args = renderContext->args; // render lights - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawLight::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; renderItems(renderContext, inLights, _maxDrawn); args->_batch = nullptr; @@ -191,7 +191,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, _drawBuffer->setData(numItems * sizeOfItemBound, (const gpu::Byte*) items.data()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawBounds::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; // Setup projection @@ -235,7 +235,7 @@ void DrawQuadVolume::run(const render::RenderContextPointer& renderContext, cons } RenderArgs* args = renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("DrawQuadVolume::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index 65c0ff45b9..07f7367582 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -66,7 +66,7 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F const auto bufferSize = resampledFrameBuffer->getSize(); glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("HalfDownsample::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(resampledFrameBuffer); diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index aaf1d2f1c8..0b4ee2f11a 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -435,44 +435,47 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& newStyle = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& transaction : transactions) { - const auto& selectionName = std::get<0>(transaction); - const auto& newStyle = std::get<1>(transaction); - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (HighlightStage::isIndexInvalid(outlineId)) { - outlineStage->addHighlight(selectionName, newStyle); - } else { - outlineStage->editHighlight(outlineId)._style = newStyle; + if (HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->addHighlight(selectionName, newStyle); + } else { + outlineStage->editHighlight(outlineId)._style = newStyle; + } } } } void Scene::removeHighlights(const Transaction::HighlightRemoves& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& selectionName : transactions) { + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& selectionName : transactions) { - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (!HighlightStage::isIndexInvalid(outlineId)) { - outlineStage->removeHighlight(outlineId); + if (!HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->removeHighlight(outlineId); + } } } } void Scene::queryHighlights(const Transaction::HighlightQueries& transactions) { auto outlineStage = getStage(HighlightStage::getName()); + if (outlineStage) { + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& func = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - for (auto& transaction : transactions) { - const auto& selectionName = std::get<0>(transaction); - const auto& func = std::get<1>(transaction); - auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); - - if (!HighlightStage::isIndexInvalid(outlineId)) { - func(&outlineStage->editHighlight(outlineId)._style); - } else { - func(nullptr); + if (!HighlightStage::isIndexInvalid(outlineId)) { + func(&outlineStage->editHighlight(outlineId)._style); + } else { + func(nullptr); + } } } } diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 23e8e3b896..68b4dcd408 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -35,7 +35,7 @@ * of gimbal lock. * @namespace Quat * @variation 0 - * @property IDENTITY {Quat} The identity rotation, i.e., no rotation. + * @property IDENTITY {Quat} The identity rotation, i.e., no rotation. Its value is { x: 0, y: 0, z: 0, w: 1 }. * @example Print the IDENTITY value. * print(JSON.stringify(Quat.IDENTITY)); // { x: 0, y: 0, z: 0, w: 1 } * print(JSON.stringify(Quat.safeEulerAngles(Quat.IDENTITY))); // { x: 0, y: 0, z: 0 } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 1e1cd917a3..7ed0fd9e8b 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -25,27 +25,36 @@ * A 2-dimensional vector. * * @typedef {object} Vec2 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. */ /**jsdoc * A 3-dimensional vector. * * @typedef {object} Vec3 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. - * @property {float} z Z-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. + * @property {number} z - Z-coordinate of the vector. */ /**jsdoc * A 4-dimensional vector. * * @typedef {object} Vec4 - * @property {float} x X-coordinate of the vector. - * @property {float} y Y-coordinate of the vector. - * @property {float} z Z-coordinate of the vector. - * @property {float} w W-coordinate of the vector. + * @property {number} x - X-coordinate of the vector. + * @property {number} y - Y-coordinate of the vector. + * @property {number} z - Z-coordinate of the vector. + * @property {number} w - W-coordinate of the vector. + */ + +/**jsdoc + * A color vector. + * + * @typedef {object} Vec3Color + * @property {number} x - Red component value. Integer in the range 0 - 255. + * @property {number} y - Green component value. Integer in the range 0 - 255. + * @property {number} z - Blue component value. Integer in the range 0 - 255. */ /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index cb3c0d07b2..49927a325b 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -92,12 +91,13 @@ void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { void LogHandler::flushRepeatedMessages() { QMutexLocker lock(&_mutex); - QHash::iterator message = _repeatMessageCountHash.begin(); - while (message != _repeatMessageCountHash.end()) { + for(auto& message: _repeatedMessages) { - if (message.value() > 0) { + if (message.messageCount > 1) { QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") - .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); + .arg(message.messageCount - 1) + .arg(message.regexp.pattern()) + .arg(message.lastMessage); QMessageLogContext emptyContext; lock.unlock(); @@ -105,8 +105,7 @@ void LogHandler::flushRepeatedMessages() { lock.relock(); } - _lastRepeatedMessage.remove(message.key()); - message = _repeatMessageCountHash.erase(message); + message.messageCount = 0; } } @@ -118,36 +117,24 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont if (type == LogDebug) { // for debug messages, check if this matches any of our regexes for repeated log messages - foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { - QRegExp repeatRegex(regexString); - if (repeatRegex.indexIn(message) != -1) { - - if (!_repeatMessageCountHash.contains(regexString)) { - // we have a match but didn't have this yet - output the first one - _repeatMessageCountHash[regexString] = 0; - - // break the foreach so we output the first match + for (auto& repeatRegex : _repeatedMessages) { + if (repeatRegex.regexp.indexIn(message) != -1) { + // If we've printed the first one then return out. + if (repeatRegex.messageCount++ == 0) { break; - } else { - // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message - _repeatMessageCountHash[regexString] += 1; - _lastRepeatedMessage[regexString] = message; - - // return out, we're not printing this one - return QString(); } + repeatRegex.lastMessage = message; + return QString(); } } } + if (type == LogDebug) { // see if this message is one we should only print once - foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) { - QRegExp onlyOnceRegex(regexString); - if (onlyOnceRegex.indexIn(message) != -1) { - if (!_onlyOnceMessageCountHash.contains(message)) { + for (auto& onceOnly : _onetimeMessages) { + if (onceOnly.regexp.indexIn(message) != -1) { + if (onceOnly.messageCount++ == 0) { // we have a match and haven't yet printed this message. - _onlyOnceMessageCountHash[message] = 1; - // break the foreach so we output the first match break; } else { // We've already printed this message, don't print it again. @@ -217,10 +204,16 @@ const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) { QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher"); QMutexLocker lock(&_mutex); - return *_repeatedMessageRegexes.insert(regexString); + RepeatedMessage repeatRecord; + repeatRecord.regexp = QRegExp(regexString); + _repeatedMessages.push_back(repeatRecord); + return regexString; } const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) { QMutexLocker lock(&_mutex); - return *_onlyOnceMessageRegexes.insert(regexString); + OnceOnlyMessage onetimeMessage; + onetimeMessage.regexp = QRegExp(regexString); + _onetimeMessages.push_back(onetimeMessage); + return regexString; } diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index ea961a8d4c..2e64f16c1e 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -13,11 +13,12 @@ #ifndef hifi_LogHandler_h #define hifi_LogHandler_h -#include #include -#include #include +#include #include +#include +#include const int VERBOSE_LOG_INTERVAL_SECONDS = 5; @@ -66,12 +67,19 @@ private: bool _shouldOutputProcessID { false }; bool _shouldOutputThreadID { false }; bool _shouldDisplayMilliseconds { false }; - QSet _repeatedMessageRegexes; - QHash _repeatMessageCountHash; - QHash _lastRepeatedMessage; - QSet _onlyOnceMessageRegexes; - QHash _onlyOnceMessageCountHash; + struct RepeatedMessage { + QRegExp regexp; + int messageCount { 0 }; + QString lastMessage; + }; + std::vector _repeatedMessages; + + struct OnceOnlyMessage { + QRegExp regexp; + int messageCount { 0 }; + }; + std::vector _onetimeMessages; static QMutex _mutex; }; diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index 794f338dc5..edec61dc67 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -64,6 +64,25 @@ const int16_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT // COLLISIONLESS gets an empty mask. const int16_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; +/**jsdoc + *

An entity may collide with the following types of items:

+ * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
1Static entities — non-dynamic entities with no velocity.
2Dynamic entities — entities that have their dynamic property set to + * true.
4Kinematic entities — non-dynamic entities with velocity.
8My avatar.
16Other avatars.
+ *

The values for the collision types that are enabled are added together to give the CollisionMask value. For example, a + * value of 31 means that an entity will collide with all item types.

+ * @typedef {number} Entities.CollisionMask + */ // The USER collision groups are exposed to script and can be used to generate per-object collision masks. // They are not necessarily the same as the BULLET_COLLISION_GROUPS, but we start them off with matching numbers. diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 7b455beae5..a3d312b9c1 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -689,6 +689,15 @@ QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { return object; } +/**jsdoc + * An axis-aligned cube, defined as the bottom right near (minimum axes values) corner of the cube plus the dimension of its + * sides. + * @typedef {object} AACube + * @property {number} x - X coordinate of the brn corner of the cube. + * @property {number} y - Y coordinate of the brn corner of the cube. + * @property {number} z - Z coordinate of the brn corner of the cube. + * @property {number} scale - The dimensions of each side of the cube. + */ QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube) { QScriptValue obj = engine->newObject(); const glm::vec3& corner = aaCube.getCorner(); @@ -765,6 +774,15 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { } } +/**jsdoc + * @typedef {object} Collision + * @property {ContactEventType} type - The contact type of the collision event. + * @property {Uuid} idA - The ID of one of the entities in the collision. + * @property {Uuid} idB - The ID of the other of the entities in the collision. + * @property {Vec3} penetration - The amount of penetration between the two entities. + * @property {Vec3} contactPoint - The point of contact. + * @property {Vec3} velocityChange - The change in relative velocity of the two entities, in m/s. + */ QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { QScriptValue obj = engine->newObject(); obj.setProperty("type", collision.type); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 4dbbd190ff..78e368748b 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -140,7 +140,7 @@ public: * * @typedef {object} PickRay * @property {Vec3} origin - The starting position of the PickRay. - * @property {Quat} direction - The direction that the PickRay travels. + * @property {Vec3} direction - The direction that the PickRay travels. */ class PickRay : public MathPick { public: @@ -265,6 +265,20 @@ namespace std { }; } +/**jsdoc + *

The type of a collision contact event. + * + * + * + * + * + * + * + * + * + *
ValueDescription
0Start of the collision.
1Continuation of the collision.
2End of the collision.
+ * @typedef {number} ContactEventType + */ enum ContactEventType { CONTACT_EVENT_TYPE_START, CONTACT_EVENT_TYPE_CONTINUE, @@ -328,14 +342,34 @@ namespace graphics { using MeshPointer = std::shared_ptr; - +/**jsdoc + * A handle for a mesh in an entity, such as returned by {@link Entities.getMeshes}. + * @class MeshProxy + * @deprecated Use the {@link Graphics} API instead. + */ class MeshProxy : public QObject { Q_OBJECT public: virtual MeshPointer getMeshPointer() const = 0; + + /**jsdoc + * Get the number of vertices in the mesh. + * @function MeshProxy#getNumVertices + * @returns {number} Integer number of vertices in the mesh. + * @deprecated Use the {@link Graphics} API instead. + */ Q_INVOKABLE virtual int getNumVertices() const = 0; - Q_INVOKABLE virtual glm::vec3 getPos3(int index) const = 0; + + /**jsdoc + * Get the position of a vertex in the mesh. + * @function MeshProxy#getPos + * @param {number} index - Integer index of the mesh vertex. + * @returns {Vec3} Local position of the vertex relative to the mesh. + * @deprecated Use the {@link Graphics} API instead. + */ + Q_INVOKABLE virtual glm::vec3 getPos(int index) const = 0; + Q_INVOKABLE virtual glm::vec3 getPos3(int index) const { return getPos(index); } // deprecated }; Q_DECLARE_METATYPE(MeshProxy*); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 8cdc4bcf14..968292da87 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -1,6 +1,6 @@ // // ShapeInfo.cpp -// libraries/physics/src +// libraries/shared/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. @@ -15,6 +15,33 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER +/**jsdoc + *

A ShapeType defines the shape used for collisions or zones.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"none"No shape.
"box"A cube.
"sphere"A sphere.
"capsule-x"A capsule (cylinder with spherical ends) oriented on the x-axis.
"capsule-y"A capsule (cylinder with spherical ends) oriented on the y-axis.
"capsule-z"A capsule (cylinder with spherical ends) oriented on the z-axis.
"cylinder-x"A cylinder oriented on the x-axis.
"cylinder-y"A cylinder oriented on the y-axis.
"cylinder-z"A cylinder oriented on the z-axis.
"hull"Not used.
"compound"A compound convex hull specified in an OBJ file.
"simple-hull"A convex hull automatically generated from the model.
"simple-compound"A compound convex hull automatically generated from the model, using + * sub-meshes.
"static-mesh"The exact shape of the model.
"plane"A plane.
+ * @typedef {string} ShapeType + */ // Originally within EntityItemProperties.cpp const char* shapeTypeNames[] = { "none", diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index 683903a4e8..8fe1454242 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -85,7 +85,7 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) { qCDebug(storagelogging) << "Failed to map file, falling back to memory storage " << filename; _fallback = _file.readAll(); _mapped = (uint8_t*)_fallback.data(); - } + } _valid = true; } else { qCWarning(storagelogging) << "Failed to open file " << filename; diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index 20f994e63d..091fd850af 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -107,12 +107,12 @@ void MainWindow::changeEvent(QEvent* event) { QWindowStateChangeEvent* stateChangeEvent = static_cast(event); if ((stateChangeEvent->oldState() == Qt::WindowNoState || stateChangeEvent->oldState() == Qt::WindowMaximized) && - windowState() == Qt::WindowMinimized) { + (windowState() & Qt::WindowMinimized) == Qt::WindowMinimized) { emit windowShown(false); emit windowMinimizedChanged(true); } else { emit windowShown(true); - if (stateChangeEvent->oldState() == Qt::WindowMinimized) { + if ((stateChangeEvent->oldState() & Qt::WindowMinimized) == Qt::WindowMinimized) { emit windowMinimizedChanged(false); } } diff --git a/libraries/ui/src/VirtualPadManager.cpp b/libraries/ui/src/VirtualPadManager.cpp index 486a6c2c71..cb3ef20e67 100644 --- a/libraries/ui/src/VirtualPadManager.cpp +++ b/libraries/ui/src/VirtualPadManager.cpp @@ -34,6 +34,11 @@ namespace VirtualPad { return _currentTouch; } + const float Manager::DPI = 534.0f; + const float Manager::PIXEL_SIZE = 512.0f; + const float Manager::STICK_RADIUS = 105.0f; + const float Manager::BASE_MARGIN = 59.0f; + Manager::Manager() { } diff --git a/libraries/ui/src/VirtualPadManager.h b/libraries/ui/src/VirtualPadManager.h index 3563d333f8..6b68af3acd 100644 --- a/libraries/ui/src/VirtualPadManager.h +++ b/libraries/ui/src/VirtualPadManager.h @@ -44,6 +44,12 @@ namespace VirtualPad { void hide(bool hide); int extraBottomMargin(); void setExtraBottomMargin(int margin); + + static const float DPI; + static const float PIXEL_SIZE; + static const float STICK_RADIUS; + static const float BASE_MARGIN; + private: Instance _leftVPadInstance; bool _enabled; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 34827117f0..a8c8ddd9c8 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -78,7 +78,7 @@ public: QObject* getFlags(); signals: - /** jsdoc + /**jsdoc * Signaled when a tablet message or dialog is created * @function TabletProxy#tabletNotification * @returns {Signal} @@ -205,7 +205,7 @@ public: - /** jsdoc + /**jsdoc * Check if the tablet has a message dialog open * @function TabletProxy#isMessageDialogOpen */ @@ -291,7 +291,7 @@ signals: */ void screenChanged(QVariant type, QVariant url); - /** jsdoc + /**jsdoc * Signaled when the tablet becomes visible or becomes invisible * @function TabletProxy#isTabletShownChanged * @returns {Signal} diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index e9262c9308..db089f09ee 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -186,7 +186,7 @@ var prevEntityID = null; for (var i = 0; i < 7; i++) { var newID = Entities.addEntity({ - name: "hinge test " + i, + name: "slider test " + i, type: "Box", color: { blue: 128, green: 40 * i, red: 20 }, dimensions: { x: 0.2, y: 0.1, z: 0.2 }, diff --git a/scripts/developer/tests/messagesTests.js b/scripts/developer/tests/messagesTests.js index 18beafa4cc..094ad6f1c0 100644 --- a/scripts/developer/tests/messagesTests.js +++ b/scripts/developer/tests/messagesTests.js @@ -5,7 +5,7 @@ Messages.subscribe(channelName); //messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly); Messages.messageReceived.connect(function(channel, message, sender, local) { - print("message recieved on ", channel, " message:", message, " from:", sender, " local:", local); + print("message received on ", channel, " message:", message, " from:", sender, " local:", local); }); Messages.dataReceived.connect(function(channel, data, sender, local) { @@ -17,7 +17,7 @@ Messages.dataReceived.connect(function(channel, data, sender, local) { } dataAsString += int8data[i]; } - print("data recieved on ", channel, " from:", sender, " local:", local, "length of data:", int8data.length, " data:", dataAsString); + print("data received on ", channel, " from:", sender, " local:", local, "length of data:", int8data.length, " data:", dataAsString); }); var counter = 0; diff --git a/scripts/developer/utilities/render/antialiasing.js b/scripts/developer/utilities/render/antialiasing.js new file mode 100644 index 0000000000..e915d75e93 --- /dev/null +++ b/scripts/developer/utilities/render/antialiasing.js @@ -0,0 +1,99 @@ +"use strict"; + +// +// gemstoneMagicMaker.js +// tablet-sample-app +// +// Created by Faye Li on Feb 6 2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var TABLET_BUTTON_NAME = "TAA"; + var QMLAPP_URL = Script.resolvePath("./antialiasing.qml"); + + + var onLuciScreen = false; + + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + var moveDebugCursor = false; + Controller.mousePressEvent.connect(function (e) { + if (e.isMiddleButton) { + moveDebugCursor = true; + setDebugCursor(e.x, e.y); + } + }); + Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); + Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + function setDebugCursor(x, y) { + nx = ((x + 0.5) / Window.innerWidth); + ny = 1.0 - ((y + 0.5) / (Window.innerHeight)); + + Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; + } + +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml new file mode 100644 index 0000000000..e8034c48bd --- /dev/null +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -0,0 +1,177 @@ +// +// Antialiasing.qml +// +// Created by Sam Gateau on 8/14/2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +import "configSlider" +import "../lib/plotperf" + +Item { +Rectangle { + id: root; + + HifiConstants { id: hifi; } + color: hifi.colors.baseGray; + + Column { + id: antialiasing + spacing: 20 + padding: 10 + + Column{ + spacing: 10 + + Row { + spacing: 10 + id: fxaaOnOff + property bool debugFXAA: false + HifiControls.Button { + text: { + if (fxaaOnOff.debugFXAA) { + return "FXAA" + } else { + return "TAA" + } + } + onClicked: { + fxaaOnOff.debugFXAA = !fxaaOnOff.debugFXAA + if (fxaaOnOff.debugFXAA) { + Render.getConfig("RenderMainView.JitterCam").none(); + Render.getConfig("RenderMainView.Antialiasing").debugFXAAX = 0; + } else { + Render.getConfig("RenderMainView.JitterCam").play(); + Render.getConfig("RenderMainView.Antialiasing").debugFXAAX = 1.0; + } + + } + } + } + Separator {} + Row { + spacing: 10 + + HifiControls.Button { + text: { + var state = 2 - (Render.getConfig("RenderMainView.JitterCam").freeze * 1 - Render.getConfig("RenderMainView.JitterCam").stop * 2); + if (state === 2) { + return "Jitter" + } else if (state === 1) { + return "Paused at " + Render.getConfig("RenderMainView.JitterCam").index + "" + } else { + return "No Jitter" + } + } + onClicked: { Render.getConfig("RenderMainView.JitterCam").cycleStopPauseRun(); } + } + HifiControls.Button { + text: "<" + onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); } + } + HifiControls.Button { + text: ">" + onClicked: { Render.getConfig("RenderMainView.JitterCam").next(); } + } + } + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Constrain color" + checked: Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] = checked } + } + ConfigSlider { + label: qsTr("Covariance gamma") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "covarianceGamma" + max: 1.5 + min: 0.5 + } + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Feedback history color" + checked: Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] = checked } + } + + ConfigSlider { + label: qsTr("Source blend") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "blend" + max: 1.0 + min: 0.0 + } + + ConfigSlider { + label: qsTr("Post sharpen") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "sharpen" + max: 1.0 + min: 0.0 + } + Separator {} + Row { + + spacing: 10 + HifiControls.CheckBox { + boxSize: 20 + text: "Debug" + checked: Render.getConfig("RenderMainView.Antialiasing")["debug"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["debug"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Debug Cursor" + checked: Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"] = checked } + } + } + ConfigSlider { + label: qsTr("Debug Region <") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugX" + max: 1.0 + min: 0.0 + } + HifiControls.CheckBox { + boxSize: 20 + text: "Closest Fragment" + checked: Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] + onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] = checked } + } + ConfigSlider { + label: qsTr("Debug Velocity Threshold [pix]") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugShowVelocityThreshold" + max: 50 + min: 0.0 + } + ConfigSlider { + label: qsTr("Debug Orb Zoom") + integral: false + config: Render.getConfig("RenderMainView.Antialiasing") + property: "debugOrbZoom" + max: 32.0 + min: 1.0 + } + } + } +} +} diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 1da7871172..189d23c44f 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -200,6 +200,7 @@ Rectangle { ListElement { text: "Debug Scattering"; color: "White" } ListElement { text: "Ambient Occlusion"; color: "White" } ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Velocity"; color: "White" } ListElement { text: "Custom"; color: "White" } } width: 200 diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js index be58f61ac2..2b28fe2c9b 100644 --- a/scripts/system/+android/avatarSelection.js +++ b/scripts/system/+android/avatarSelection.js @@ -34,6 +34,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See App.openUrl("https://metaverse.highfidelity.com/marketplace?category=avatars"); break; case 'hide': + Controller.setVPadHidden(false); module.exports.onHidden(); break; default: @@ -114,26 +115,27 @@ module.exports = { qml: "hifi/avatarSelection.qml", visible: false }); - /*, - visible: false*/ if (window) { window.fromQml.connect(fromQml); } init(); }, show: function() { + Controller.setVPadHidden(true); if (window) { window.setVisible(true); isVisible = true; } }, hide: function() { + Controller.setVPadHidden(false); if (window) { window.setVisible(false); } isVisible = false; }, destroy: function() { + Controller.setVPadHidden(false); if (window) { window.fromQml.disconnect(fromQml); window.close(); @@ -155,5 +157,7 @@ module.exports = { refreshSelectedAvatar: function(currentAvatarURL) { refreshSelected(currentAvatarURL); }, - onHidden: function() { } + onHidden: function() { + Controller.setVPadHidden(false); + } }; diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index b29548094f..097798e393 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -17,7 +17,7 @@ var currentSelectedBtn; var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; -var DEFAULT_MODE = MODE_RADAR; +var DEFAULT_MODE = MODE_MY_VIEW; var logEnabled = true; var radar = Script.require('./radar.js'); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 7389442649..82a450bedd 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -313,7 +313,7 @@ -
+
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index b3ca300022..32dbe0a43b 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -9,7 +9,8 @@ // /* global Tablet, Script, HMD, UserActivityLogger, Entities, Account, Wallet, ContextOverlay, Settings, Camera, Vec3, - Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow */ + Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow, Overlays, SoundCache, + DesktopPreviewProvider */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ var selectionDisplay = null; // for gridTool.js to ignore @@ -116,6 +117,24 @@ var selectionDisplay = null; // for gridTool.js to ignore var onWalletScreen = false; var onCommerceScreen = false; + var tabletShouldBeVisibleInSecondaryCamera = false; + + function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { + if (visibleInSecondaryCam) { + // if we're potentially showing the tablet, only do so if it was visible before + if (!tabletShouldBeVisibleInSecondaryCamera) { + return; + } + } else { + // if we're hiding the tablet, check to see if it was visible in the first place + tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); + } + + Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightIDtabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + } function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; @@ -127,6 +146,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (isHmdPreviewDisabledBySecurity) { DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", false); + setTabletVisibleInSecondaryCamera(true); isHmdPreviewDisabledBySecurity = false; } } @@ -245,7 +265,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var wearableDimensions = null; if (itemType === "contentSet") { - console.log("Item is a content set; codepath shouldn't go here.") + console.log("Item is a content set; codepath shouldn't go here."); return; } @@ -575,6 +595,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (!isHmdPreviewDisabled) { DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); + setTabletVisibleInSecondaryCamera(false); isHmdPreviewDisabledBySecurity = true; } break; @@ -582,6 +603,7 @@ var selectionDisplay = null; // for gridTool.js to ignore if (isHmdPreviewDisabledBySecurity) { DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", false); + setTabletVisibleInSecondaryCamera(true); isHmdPreviewDisabledBySecurity = false; } break; diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 62aef37af6..1fa4862e53 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -114,14 +114,14 @@ void TestWindow::beginFrame() { // the rest of the renderDeferred inputs can be omitted #else - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::beginFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); batch.clearDepthFramebuffer(1e4); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); }); #endif - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::beginFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(_renderArgs->_viewport); batch.setStateScissorRect(_renderArgs->_viewport); batch.setProjectionTransform(_projectionMatrix); @@ -131,7 +131,7 @@ void TestWindow::beginFrame() { void TestWindow::endFrame() { #ifdef DEFERRED_LIGHTING RenderArgs* args = _renderContext->args; - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::begin", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; auto deferredFboColorDepthStencil = _prepareDeferredOutputs.get0()->getDeferredFramebufferDepthColor(); batch.setViewportTransform(args->_viewport); @@ -144,7 +144,7 @@ void TestWindow::endFrame() { _renderDeferred.run(_renderContext, _renderDeferredInputs); - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::blit", _renderArgs->_context, [&](gpu::Batch& batch) { PROFILE_RANGE_BATCH(batch, "blit"); // Blit to screen auto framebufferCache = DependencyManager::get(); @@ -154,7 +154,7 @@ void TestWindow::endFrame() { }); #endif - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("TestWindow::endFrame::finish", _renderArgs->_context, [&](gpu::Batch& batch) { batch.resetStages(); }); _glContext.swapBuffers(this); diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 77ce015e3f..41d84ca026 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -147,7 +147,7 @@ class MyTestWindow : public TestWindow { return; } - gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + gpu::doInBatch("main::renderFrame", _renderArgs->_context, [&](gpu::Batch& batch) { batch.setViewTransform(_camera); _renderArgs->_batch = &batch; _currentTest->renderTest(_currentTestId, _renderArgs); diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index ec56555f68..93672cc5a2 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -919,7 +919,7 @@ private: void render(RenderArgs* renderArgs) { auto& gpuContext = renderArgs->_context; gpuContext->beginFrame(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("QTestWindow::render", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); PROFILE_RANGE(render, __FUNCTION__); diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 62c970cab5..ce666065e3 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -563,12 +563,12 @@ private: void render() { auto& gpuContext = _renderThread._gpuContext; gpuContext->beginFrame(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderThread::render::begin", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); auto framebuffer = DependencyManager::get()->getFramebuffer(); - gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + gpu::doInBatch("RenderThread::render", gpuContext, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(framebuffer); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(1, 0, 0, 1)); diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index a875f5676a..a2589bb760 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -5,7 +5,7 @@ project(${TARGET_NAME}) SET (CMAKE_AUTOUIC ON) SET (CMAKE_AUTOMOC ON) -setup_hifi_project (Core Widgets) +setup_hifi_project (Core Widgets Network) link_hifi_libraries () # FIX: Qt was built with -reduce-relocations diff --git a/tools/auto-tester/src/Downloader.cpp b/tools/auto-tester/src/Downloader.cpp new file mode 100644 index 0000000000..030aa95a19 --- /dev/null +++ b/tools/auto-tester/src/Downloader.cpp @@ -0,0 +1,32 @@ +// +// Downloader.cpp +// +// Created by Nissim Hadar on 1 Mar 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Downloader.h" + +Downloader::Downloader(QUrl imageUrl, QObject *parent) : QObject(parent) { + connect( + &_networkAccessManager, SIGNAL (finished(QNetworkReply*)), + this, SLOT (fileDownloaded(QNetworkReply*)) + ); + + QNetworkRequest request(imageUrl); + _networkAccessManager.get(request); +} + +void Downloader::fileDownloaded(QNetworkReply* reply) { + _downloadedData = reply->readAll(); + + //emit a signal + reply->deleteLater(); + emit downloaded(); +} + +QByteArray Downloader::downloadedData() const { + return _downloadedData; +} \ No newline at end of file diff --git a/tools/auto-tester/src/Downloader.h b/tools/auto-tester/src/Downloader.h new file mode 100644 index 0000000000..b0ad58fac5 --- /dev/null +++ b/tools/auto-tester/src/Downloader.h @@ -0,0 +1,48 @@ +// +// Downloader.h +// +// Created by Nissim Hadar on 1 Mar 2018. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_downloader_h +#define hifi_downloader_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class Downloader : public QObject { +Q_OBJECT +public: + explicit Downloader(QUrl imageUrl, QObject *parent = 0); + + QByteArray downloadedData() const; + +signals: + void downloaded(); + + private slots: + void fileDownloaded(QNetworkReply* pReply); + +private: + QNetworkAccessManager _networkAccessManager; + QByteArray _downloadedData; +}; + +#endif // hifi_downloader_h \ No newline at end of file diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 94b95a5ab6..99f7d22c5f 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -17,13 +17,13 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { // Make sure the image is 8 bits per colour QImage::Format format = expectedImage.format(); - if (format != QImage::Format::Format_RGB32) { + if (format != QImage::Format::Format_ARGB32) { throw -1; } const int L = 255; // (2^number of bits per pixel) - 1 - const double K1{ 0.01 }; - const double K2{ 0.03 }; + const double K1 { 0.01 }; + const double K2 { 0.03 }; const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 816eac7fbd..347cfd90dc 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -12,33 +12,32 @@ #include #include #include +#include +#include #include #include -Test::Test() { - snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.*-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); +#include "ui/AutoTester.h" +extern AutoTester* autoTester; - expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg"); +#include + +Test::Test() { + QString regex(EXPECTED_IMAGE_PREFIX + QString("\\\\d").repeated(NUM_DIGITS) + ".png"); + + expectedImageFilenameFormat = QRegularExpression(regex); mismatchWindow.setModal(true); } -bool Test::createTestResultsFolderPathIfNeeded(QString directory) { - // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) - if (testResultsFolderPath == "") { - testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; - QDir testResultsFolder(testResultsFolderPath); +bool Test::createTestResultsFolderPath(QString directory) { + QDateTime now = QDateTime::currentDateTime(); + testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT); + QDir testResultsFolder(testResultsFolderPath); - if (testResultsFolder.exists()) { - testResultsFolder.removeRecursively(); - } - - // Create a new test results folder - return QDir().mkdir(testResultsFolderPath); - } else { - return true; - } + // Create a new test results folder + return QDir().mkdir(testResultsFolderPath); } void Test::zipAndDeleteTestResultsFolder() { @@ -60,9 +59,9 @@ void Test::zipAndDeleteTestResultsFolder() { index = 1; } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { +bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) { progressBar->setMinimum(0); - progressBar->setMaximum(expectedImages.length() - 1); + progressBar->setMaximum(expectedImagesFullFilenames.length() - 1); progressBar->setValue(0); progressBar->setVisible(true); @@ -71,12 +70,13 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage const double THRESHOLD { 0.999 }; bool success{ true }; bool keepOn{ true }; - for (int i = 0; keepOn && i < expectedImages.length(); ++i) { + for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { // First check that images are the same size - QImage resultImage(resultImages[i]); - QImage expectedImage(expectedImages[i]); + QImage resultImage(resultImagesFullFilenames[i]); + QImage expectedImage(expectedImagesFullFilenames[i]); + if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { - messageBox.critical(0, "Internal error", "Images are not the same size"); + messageBox.critical(0, "Internal error #1", "Images are not the same size"); exit(-1); } @@ -84,21 +84,21 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage try { similarityIndex = imageComparer.compareImages(resultImage, expectedImage); } catch (...) { - messageBox.critical(0, "Internal error", "Image not in expected format"); + messageBox.critical(0, "Internal error #2", "Image not in expected format"); exit(-1); } if (similarityIndex < THRESHOLD) { TestFailure testFailure = TestFailure{ (float)similarityIndex, - expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) - QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image - QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image + expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image }; mismatchWindow.setTestFailure(testFailure); - if (!interactiveMode) { + if (!isInteractiveMode) { appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); success = false; } else { @@ -131,20 +131,20 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { if (!QDir().exists(testResultsFolderPath)) { - messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); + messageBox.critical(0, "Internal error #3", "Folder " + testResultsFolderPath + " not found"); exit(-1); } QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; if (!QDir().mkdir(failureFolderPath)) { - messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); + messageBox.critical(0, "Internal error #4", "Failed to create folder " + failureFolderPath); exit(-1); } ++index; QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); if (!descriptionFile.open(QIODevice::ReadWrite)) { - messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); + messageBox.critical(0, "Internal error #5", "Failed to create file " + TEST_RESULTS_FILENAME); exit(-1); } @@ -164,60 +164,91 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te sourceFile = testFailure._pathname + testFailure._expectedImageFilename; destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error #6", "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } sourceFile = testFailure._pathname + testFailure._actualImageFilename; destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; if (!QFile::copy(sourceFile, destinationFile)) { - messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + messageBox.critical(0, "Internal error #7", "Failed to copy " + sourceFile + " to " + destinationFile); exit(-1); } comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); } -void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { +void Test::startTestsEvaluation() { // Get list of JPEG images in folder, sorted by name - QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); - if (pathToImageDirectory == "") { + pathToTestResultsDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToTestResultsDirectory == "") { return; } - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { + // Quit if test results folder could not be created + if (!createTestResultsFolderPath(pathToTestResultsDirectory)) { return; } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + // Before any processing - all images are converted to PNGs, as this is the format stored on GitHub + QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", pathToTestResultsDirectory); + foreach(QString filename, sortedSnapshotFilenames) { + QStringList stringParts = filename.split("."); + copyJPGtoPNG( + pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg", + pathToTestResultsDirectory + "/" + stringParts[0] + ".png" + ); - // Separate images into two lists. The first is the expected images, the second is the test results + QFile::remove(pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg"); + } + + // Create two lists. The first is the test results, the second is the expected images + // The expected images are represented as a URL to enable download from GitHub // Images that are in the wrong format are ignored. - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - expectedImages << fullCurrentFilename; - } else if (isInSnapshotFilenameFormat(currentFilename)) { - resultImages << fullCurrentFilename; + + QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory); + QStringList expectedImagesURLs; + + const QString URLPrefix("https://raw.githubusercontent.com"); + const QString githubUser("NissimHadar"); + const QString testsRepo("hifi_tests"); + const QString branch("addRecursionToAutotester"); + + resultImagesFullFilenames.clear(); + expectedImagesFilenames.clear(); + expectedImagesFullFilenames.clear(); + + foreach(QString currentFilename, sortedTestResultsFilenames) { + QString fullCurrentFilename = pathToTestResultsDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("png", currentFilename)) { + resultImagesFullFilenames << fullCurrentFilename; + + QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename); + + // Images are stored on GitHub as ExpectedImage_ddddd.png + // Extract the digits at the end of the filename (exluding the file extension) + QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); + QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; + + QString imageURLString(URLPrefix + "/" + githubUser + "/" + testsRepo + "/" + branch + "/" + + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); + + expectedImagesURLs << imageURLString; + + // The image retrieved from Github needs a unique name + QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI."); + + expectedImagesFilenames << expectedImageFilename; + expectedImagesFullFilenames << pathToTestResultsDirectory + "/" + expectedImageFilename; } } - // The number of images in each list should be identical - if (expectedImages.length() != resultImages.length()) { - messageBox.critical(0, - "Test failed", - "Found " + QString::number(resultImages.length()) + " images in directory" + - "\nExpected to find " + QString::number(expectedImages.length()) + " images" - ); - - exit(-1); - } - - bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); + autoTester->downloadImages(expectedImagesURLs, pathToTestResultsDirectory, expectedImagesFilenames); +} +void Test::finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar) { + bool success = compareImageLists(interactiveMode, progressBar); + if (success) { messageBox.information(0, "Success", "All images are as expected"); } else { @@ -242,72 +273,25 @@ bool Test::isAValidDirectory(QString pathname) { return true; } -// Two criteria are used to decide if a folder contains valid test results. -// 1) a 'test'js' file exists in the folder -// 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { - // Select folder to start recursing from - QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); - if (topLevelDirectory == "") { - return; - } - - // Leave if test results folder could not be created - if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { - return; - } - - bool success{ true }; - QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); - - if (!isAValidDirectory(directory)) { - continue; - } - - const QString testPathname{ directory + "/" + TEST_FILENAME }; - QFileInfo fileInfo(testPathname); - if (!fileInfo.exists()) { - // Folder does not contain 'test.js' - continue; - } - - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); - - // Separate images into two lists. The first is the expected images, the second is the test results - // Images that are in the wrong format are ignored. - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = directory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - expectedImages << fullCurrentFilename; - } else if (isInSnapshotFilenameFormat(currentFilename)) { - resultImages << fullCurrentFilename; - } - } - - if (expectedImages.length() != resultImages.length()) { - // Number of images doesn't match - continue; - } - - // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); - } - - if (success) { - messageBox.information(0, "Success", "All images are as expected"); - } else { - messageBox.information(0, "Failure", "One or more images are not as expected"); - } - - zipAndDeleteTestResultsFolder(); -} - void Test::importTest(QTextStream& textStream, const QString& testPathname) { - textStream << "Script.include(\"" << "file:///" << testPathname + "?raw=true\");" << endl; + // `testPathname` includes the full path to the test. We need the portion below (and including) `tests` + QStringList filenameParts = testPathname.split('/'); + int i{ 0 }; + while (i < filenameParts.length() && filenameParts[i] != "tests") { + ++i; + } + + if (i == filenameParts.length()) { + messageBox.critical(0, "Internal error #10", "Bad testPathname"); + exit(-1); + } + + QString filename; + for (int j = i; j < filenameParts.length(); ++j) { + filename += "/" + filenameParts[j]; + } + + textStream << "Script.include(\"" << "https://raw.githubusercontent.com/" << user << "/hifi_tests/" << branch << filename + "\");" << endl; } // Creates a single script in a user-selected folder. @@ -319,11 +303,58 @@ void Test::createRecursiveScript() { return; } - QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js"); + createRecursiveScript(topLevelDirectory, true); +} + +// This method creates a `testRecursive.js` script in every sub-folder. +void Test::createRecursiveScriptsRecursively() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + createRecursiveScript(topLevelDirectory, false); + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir; + if (!isAValidDirectory(directory)) { + continue; + } + + // Only process directories that have sub-directories + bool hasNoSubDirectories{ true }; + QDirIterator it2(directory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it2.hasNext()) { + QString directory2 = it2.next(); + + // Only process directories + QDir dir; + if (isAValidDirectory(directory2)) { + hasNoSubDirectories = false; + break; + } + } + + if (!hasNoSubDirectories) { + createRecursiveScript(directory, false); + } + } + + messageBox.information(0, "Success", "Scripts have been created"); +} + +void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode) { + const QString recursiveTestsFilename("testRecursive.js"); + QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename); if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, - "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" + "Internal Error #8", + "Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\"" ); exit(-1); @@ -332,12 +363,9 @@ void Test::createRecursiveScript() { QTextStream textStream(&allTestsFilename); textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl; - textStream << "var autoTester = Script.require(\"https://github.com/highfidelity/hifi_tests/blob/master/tests/utils/autoTester.js?raw=true\");" << endl; + textStream << "var autoTester = Script.require(\"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/autoTester.js\");" << endl; textStream << "autoTester.enableRecursive();" << endl << endl; - // The main will call each test after the previous test is completed - // This is implemented with an interval timer that periodically tests if a - // running test has increment a testNumber variable that it received as an input. QVector testPathnames; // First test if top-level folder has a test.js file @@ -360,7 +388,7 @@ void Test::createRecursiveScript() { continue; } - const QString testPathname{ directory + "/" + TEST_FILENAME }; + const QString testPathname { directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -370,8 +398,8 @@ void Test::createRecursiveScript() { } } - if (testPathnames.length() <= 0) { - messageBox.information(0, "Failure", "No \"test.js\" files found"); + if (interactiveMode && testPathnames.length() <= 0) { + messageBox.information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found"); allTestsFilename.close(); return; } @@ -380,50 +408,45 @@ void Test::createRecursiveScript() { textStream << "autoTester.runRecursive();" << endl; allTestsFilename.close(); - messageBox.information(0, "Success", "Script has been created"); + + if (interactiveMode) { + messageBox.information(0, "Success", "Script has been created"); + } } void Test::createTest() { - // Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on + // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on // Any existing expected result images will be deleted - QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); - if (pathToImageDirectory == "") { + QString imageSourceDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (imageSourceDirectory == "") { return; } - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + QString imageDestinationDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images", ".", QFileDialog::ShowDirsOnly); + if (imageDestinationDirectory == "") { + return; + } - int i = 1; + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", imageSourceDirectory); + + int i = 1; + const int maxImages = pow(10, NUM_DIGITS); foreach (QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; - if (isInExpectedImageFilenameFormat(currentFilename)) { - if (!QFile::remove(fullCurrentFilename)) { + QString fullCurrentFilename = imageSourceDirectory + "/" + currentFilename; + if (isInSnapshotFilenameFormat("jpg", currentFilename)) { + if (i >= maxImages) { + messageBox.critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); + exit(-1); + } + QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png"; + QString fullNewFileName = imageDestinationDirectory + "/" + newFilename; + + try { + copyJPGtoPNG(fullCurrentFilename, fullNewFileName); + } catch (...) { messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); exit(-1); } - } else if (isInSnapshotFilenameFormat(currentFilename)) { - const int MAX_IMAGES = 100000; - if (i >= MAX_IMAGES) { - messageBox.critical(0, "Error", "More than 100,000 images not supported"); - exit(-1); - } - QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg"; - QString fullNewFileName = pathToImageDirectory + "/" + newFilename; - - if (!imageDirectory.rename(fullCurrentFilename, newFilename)) { - if (!QFile::exists(fullCurrentFilename)) { - messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" - + fullCurrentFilename + " not found" - + "\nTest creation aborted" - ); - exit(-1); - } else { - messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" - + "unknown error" + "\nTest creation aborted" - ); - exit(-1); - } - } ++i; } } @@ -431,54 +454,87 @@ void Test::createTest() { messageBox.information(0, "Success", "Test images have been created"); } -void Test::deleteOldSnapshots() { - // Select folder to start recursing from - QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select root folder for snapshot deletion", ".", QFileDialog::ShowDirsOnly); - if (topLevelDirectory == "") { - return; - } +void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) { + QFile::remove(destinationPNGFullFilename); - // Recurse over folders - QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); - while (it.hasNext()) { - QString directory = it.next(); + QImageReader reader; + reader.setFileName(sourceJPGFullFilename); - // Only process directories - QDir dir(directory); - if (!isAValidDirectory(directory)) { - continue; - } + QImage image = reader.read(); - QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); - - // Delete any file that is a snapshot (NOT the Expected Images) - QStringList expectedImages; - QStringList resultImages; - foreach(QString currentFilename, sortedImageFilenames) { - QString fullCurrentFilename = directory + "/" + currentFilename; - if (isInSnapshotFilenameFormat(currentFilename)) { - if (!QFile::remove(fullCurrentFilename)) { - messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nSnapshot deletion aborted"); - exit(-1); - } - } - } - } + QImageWriter writer; + writer.setFileName(destinationPNGFullFilename); + writer.write(image); } -QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { +QStringList Test::createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; - nameFilters << "*.jpg"; + nameFilters << "*." + imageFormat; return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); } -// Use regular expressions to check if files are in specific format -bool Test::isInSnapshotFilenameFormat(QString filename) { - return (snapshotFilenameFormat.match(filename).hasMatch()); +// Snapshots are files in the following format: +// Filename contains no periods (excluding period before exception +// Filename (i.e. without extension) contains _tests_ (this is based on all test scripts being within the tests folder +// Last 5 characters in filename are digits +// Extension is jpg +bool Test::isInSnapshotFilenameFormat(QString imageFormat, QString filename) { + QStringList filenameParts = filename.split("."); + + bool filnameHasNoPeriods = (filenameParts.size() == 2); + bool contains_tests = filenameParts[0].contains("_tests_"); + + bool last5CharactersAreDigits; + filenameParts[0].right(5).toInt(&last5CharactersAreDigits, 10); + + bool extensionIsIMAGE_FORMAT = (filenameParts[1] == imageFormat); + + return (filnameHasNoPeriods && contains_tests && last5CharactersAreDigits && extensionIsIMAGE_FORMAT); } -bool Test::isInExpectedImageFilenameFormat(QString filename) { - return (expectedImageFilenameFormat.match(filename).hasMatch()); -} \ No newline at end of file +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the test directory is +// D:/GitHub/hifi-tests/tests/content/entity/zone/create +// This method assumes the filename is in the correct format +QString Test::getExpectedImageDestinationDirectory(QString filename) { + QString filenameWithoutExtension = filename.split(".")[0]; + QStringList filenameParts = filenameWithoutExtension.split("_"); + + QString result = filenameParts[0] + ":"; + + for (int i = 1; i < filenameParts.length() - 1; ++i) { + result += "/" + filenameParts[i]; + } + + return result; +} + +// For a file named "D_GitHub_hifi-tests_tests_content_entity_zone_create_0.jpg", the source directory on GitHub +// is ...tests/content/entity/zone/create +// This is used to create the full URL +// This method assumes the filename is in the correct format +QString Test::getExpectedImagePartialSourceDirectory(QString filename) { + QString filenameWithoutExtension = filename.split(".")[0]; + QStringList filenameParts = filenameWithoutExtension.split("_"); + + // Note that the bottom-most "tests" folder is assumed to be the root + // This is required because the tests folder is named hifi_tests + int i { filenameParts.length() - 1 }; + while (i >= 0 && filenameParts[i] != "tests") { + --i; + } + + if (i < 0) { + messageBox.critical(0, "Internal error #9", "Bad filename"); + exit(-1); + } + + QString result = filenameParts[i]; + + for (int j = i + 1; j < filenameParts.length() - 1; ++j) { + result += "/" + filenameParts[j]; + } + + return result; +} diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 3177df4d47..cd5075002a 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -23,28 +23,36 @@ class Test { public: Test(); - void evaluateTests(bool interactiveMode, QProgressBar* progressBar); - void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); + void startTestsEvaluation(); + void finishTestsEvaluation(bool interactiveMode, QProgressBar* progressBar); + void createRecursiveScript(); + void createRecursiveScriptsRecursively(); + void createRecursiveScript(QString topLevelDirectory, bool interactiveMode); + void createTest(); void deleteOldSnapshots(); - bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); + bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar); - QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); + QStringList createListOfAll_imagesInDirectory(QString imageFormat, QString pathToImageDirectory); - bool isInSnapshotFilenameFormat(QString filename); - bool isInExpectedImageFilenameFormat(QString filename); + bool isInSnapshotFilenameFormat(QString imageFormat, QString filename); void importTest(QTextStream& textStream, const QString& testPathname); void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); - bool createTestResultsFolderPathIfNeeded(QString directory); + bool createTestResultsFolderPath(QString directory); void zipAndDeleteTestResultsFolder(); bool isAValidDirectory(QString pathname); + QString getExpectedImageDestinationDirectory(QString filename); + QString getExpectedImagePartialSourceDirectory(QString filename); + + void copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename); + private: const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; @@ -54,16 +62,28 @@ private: QDir imageDirectory; - QRegularExpression snapshotFilenameFormat; QRegularExpression expectedImageFilenameFormat; MismatchWindow mismatchWindow; ImageComparer imageComparer; - QString testResultsFolderPath { "" }; int index { 1 }; + + // Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit) + const int NUM_DIGITS { 5 }; + const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" }; + + QString pathToTestResultsDirectory; + QStringList expectedImagesFilenames; + QStringList expectedImagesFullFilenames; + QStringList resultImagesFullFilenames; + + // Used for accessing GitHub + const QString user { "NissimHadar" }; + const QString branch { "addRecursionToAutotester" }; + const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" }; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 6e5e06b732..cd0ce22b13 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -10,11 +10,13 @@ #include #include "ui/AutoTester.h" +AutoTester* autoTester; + int main(int argc, char *argv[]) { QApplication application(argc, argv); - AutoTester autoTester; - autoTester.show(); + autoTester = new AutoTester(); + autoTester->show(); return application.exec(); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 2834ff81e0..a5e13331dd 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,32 +12,79 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); - ui.checkBoxInteractiveMode->setChecked(true); - ui.progressBar->setVisible(false); + + test = new Test(); + + signalMapper = new QSignalMapper(); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); -} - -void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + test->startTestsEvaluation(); } void AutoTester::on_createRecursiveScriptButton_clicked() { - test.createRecursiveScript(); + test->createRecursiveScript(); +} + +void AutoTester::on_createRecursiveScriptsRecursivelyButton_clicked() { + test->createRecursiveScriptsRecursively(); } void AutoTester::on_createTestButton_clicked() { - test.createTest(); -} - -void AutoTester::on_deleteOldSnapshotsButton_clicked() { - test.deleteOldSnapshots(); + test->createTest(); } void AutoTester::on_closeButton_clicked() { exit(0); -} \ No newline at end of file +} + +void AutoTester::downloadImage(const QUrl& url) { + downloaders.emplace_back(new Downloader(url, this)); + connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map())); + + signalMapper->setMapping(downloaders[_index], _index); + + ++_index; +} + +void AutoTester::downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames) { + _directoryName = directoryName; + _filenames = filenames; + + _numberOfImagesToDownload = URLs.size(); + _numberOfImagesDownloaded = 0; + _index = 0; + + ui.progressBar->setMinimum(0); + ui.progressBar->setMaximum(_numberOfImagesToDownload - 1); + ui.progressBar->setValue(0); + ui.progressBar->setVisible(true); + + for (int i = 0; i < _numberOfImagesToDownload; ++i) { + QUrl imageURL(URLs[i]); + downloadImage(imageURL); + } + + connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); +} + +void AutoTester::saveImage(int index) { + QPixmap pixmap; + pixmap.loadFromData(downloaders[index]->downloadedData()); + + QImage image = pixmap.toImage(); + image = image.convertToFormat(QImage::Format_ARGB32); + + QString fullPathname = _directoryName + "/" + _filenames[index]; + image.save(fullPathname, 0, 100); + + ++_numberOfImagesDownloaded; + + if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { + test->finishTestsEvaluation(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); + } else { + ui.progressBar->setValue(_numberOfImagesDownloaded); + } +} diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 35f609a89d..938e7ca2d2 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -11,7 +11,10 @@ #define hifi_AutoTester_h #include +#include #include "ui_AutoTester.h" + +#include "../Downloader.h" #include "../Test.h" class AutoTester : public QMainWindow { @@ -19,19 +22,34 @@ class AutoTester : public QMainWindow { public: AutoTester(QWidget *parent = Q_NULLPTR); + void downloadImage(const QUrl& url); + void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); private slots: void on_evaluateTestsButton_clicked(); - void on_evaluateTestsRecursivelyButton_clicked(); void on_createRecursiveScriptButton_clicked(); + void on_createRecursiveScriptsRecursivelyButton_clicked(); void on_createTestButton_clicked(); - void on_deleteOldSnapshotsButton_clicked(); void on_closeButton_clicked(); + void saveImage(int index); + private: Ui::AutoTesterClass ui; + Test* test; - Test test; + std::vector downloaders; + + // local storage for parameters - folder to store downloaded files in, and a list of their names + QString _directoryName; + QStringList _filenames; + + // Used to enable passing a parameter to slots + QSignalMapper* signalMapper; + + int _numberOfImagesToDownload; + int _numberOfImagesDownloaded; + int _index; }; #endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index d06255acf6..55c3897e58 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -17,7 +17,7 @@ - 190 + 20 300 220 40 @@ -30,8 +30,8 @@ - 360 - 130 + 20 + 30 220 40 @@ -44,7 +44,7 @@ 20 - 75 + 135 220 40 @@ -66,24 +66,11 @@ Create Recursive Script - - - - 20 - 130 - 220 - 40 - - - - Evaluate Tests Recursively - - 23 - 40 + 100 131 20 @@ -108,17 +95,17 @@ 24 - + 360 - 240 + 140 220 40 - Delete Old Snapshots + Create Recursive Scripts Recursively @@ -145,4 +132,4 @@ - \ No newline at end of file + diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index bb556814e8..1f73f14b2b 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -24,13 +24,16 @@ exports.handlers = { '../../libraries/animation/src', '../../libraries/avatars/src', '../../libraries/controllers/src/controllers/', + '../../libraries/graphics-scripting/src/graphics-scripting/', '../../libraries/entities/src', + '../../libraries/model-networking/src/model-networking/', + '../../libraries/octree/src', '../../libraries/networking/src', + '../../libraries/physics/src', '../../libraries/pointers/src', - '../../libraries/render-utils/src', + '../../libraries/script-engine/src', '../../libraries/shared/src', '../../libraries/shared/src/shared', - '../../libraries/script-engine/src', ]; var exts = ['.h', '.cpp']; diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 28ede982a0..3c6799db88 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "DomainBaker.h" + #include #include #include @@ -17,10 +19,9 @@ #include #include "Gzip.h" - #include "Oven.h" - -#include "DomainBaker.h" +#include "FBXBaker.h" +#include "OBJBaker.h" DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, const QString& baseOutputPath, const QUrl& destinationPath, @@ -167,15 +168,18 @@ void DomainBaker::enumerateEntities() { // check if the file pointed to by this URL is a bakeable model, by comparing extensions auto modelFileName = modelURL.fileName(); - static const QString BAKEABLE_MODEL_EXTENSION { ".fbx" }; + static const QString BAKEABLE_MODEL_FBX_EXTENSION { ".fbx" }; + static const QString BAKEABLE_MODEL_OBJ_EXTENSION { ".obj" }; static const QString BAKED_MODEL_EXTENSION = ".baked.fbx"; - bool isBakedFBX = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive); - bool isUnbakedFBX = modelFileName.endsWith(BAKEABLE_MODEL_EXTENSION, Qt::CaseInsensitive) && !isBakedFBX; + bool isBakedModel = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive); + bool isBakeableFBX = modelFileName.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive); + bool isBakeableOBJ = modelFileName.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive); + bool isBakeable = isBakeableFBX || isBakeableOBJ; - if (isUnbakedFBX || (_shouldRebakeOriginals && isBakedFBX)) { + if (isBakeable || (_shouldRebakeOriginals && isBakedModel)) { - if (isBakedFBX) { + if (isBakedModel) { // grab a URL to the original, that we assume is stored a directory up, in the "original" folder // with just the fbx extension qDebug() << "Re-baking original for" << modelURL; @@ -190,7 +194,7 @@ void DomainBaker::enumerateEntities() { modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); } - // setup an FBXBaker for this URL, as long as we don't already have one + // setup a ModelBaker for this URL, as long as we don't already have one if (!_modelBakers.contains(modelURL)) { auto filename = modelURL.fileName(); auto baseName = filename.left(filename.lastIndexOf('.')); @@ -199,12 +203,23 @@ void DomainBaker::enumerateEntities() { while (QDir(_contentOutputPath + subDirName).exists()) { subDirName = "/" + baseName + "-" + QString::number(i++); } - QSharedPointer baker { - new FBXBaker(modelURL, []() -> QThread* { - return Oven::instance().getNextWorkerThread(); - }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), - &FBXBaker::deleteLater - }; + + QSharedPointer baker; + if (isBakeableFBX) { + baker = { + new FBXBaker(modelURL, []() -> QThread* { + return Oven::instance().getNextWorkerThread(); + }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), + &FBXBaker::deleteLater + }; + } else { + baker = { + new OBJBaker(modelURL, []() -> QThread* { + return Oven::instance().getNextWorkerThread(); + }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), + &OBJBaker::deleteLater + }; + } // make sure our handler is called when the baker is done connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker); @@ -299,16 +314,16 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { } void DomainBaker::handleFinishedModelBaker() { - auto baker = qobject_cast(sender()); + auto baker = qobject_cast(sender()); if (baker) { if (!baker->hasErrors()) { // this FBXBaker is done and everything went according to plan - qDebug() << "Re-writing entity references to" << baker->getFBXUrl(); + qDebug() << "Re-writing entity references to" << baker->getModelURL(); // enumerate the QJsonRef values for the URL of this FBX from our multi hash of // entity objects needing a URL re-write - for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getFBXUrl())) { + for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getModelURL())) { // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL auto entity = entityValue.toObject(); @@ -317,7 +332,7 @@ void DomainBaker::handleFinishedModelBaker() { QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; // setup a new URL using the prefix we were passed - auto relativeFBXFilePath = baker->getBakedFBXFilePath().remove(_contentOutputPath); + auto relativeFBXFilePath = baker->getBakedModelFilePath().remove(_contentOutputPath); if (relativeFBXFilePath.startsWith("/")) { relativeFBXFilePath = relativeFBXFilePath.right(relativeFBXFilePath.length() - 1); } @@ -370,10 +385,10 @@ void DomainBaker::handleFinishedModelBaker() { } // remove the baked URL from the multi hash of entities needing a re-write - _entitiesNeedingRewrite.remove(baker->getFBXUrl()); + _entitiesNeedingRewrite.remove(baker->getModelURL()); // drop our shared pointer to this baker so that it gets cleaned up - _modelBakers.remove(baker->getFBXUrl()); + _modelBakers.remove(baker->getModelURL()); // emit progress to tell listeners how many models we have baked emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 6426af0710..e0286a51ff 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -61,7 +61,7 @@ private: QJsonArray _entities; - QHash> _modelBakers; + QHash> _modelBakers; QHash> _skyboxBakers; QMultiHash _entitiesNeedingRewrite; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 650683e1f0..a9aa6907f1 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -9,12 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Oven.h" + #include #include #include -#include "Oven.h" +#include +#include +#include Oven* Oven::_staticInstance { nullptr }; @@ -29,6 +33,10 @@ Oven::Oven() { // setup our worker threads setupWorkerThreads(QThread::idealThreadCount()); + + // Initialize dependencies for OBJ Baker + DependencyManager::set(); + DependencyManager::set(false); } Oven::~Oven() { diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index effebb472e..c1acc07efb 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -14,6 +14,9 @@ #include #include +#include + +class QThread; class Oven { @@ -31,7 +34,7 @@ private: std::vector> _workerThreads; - std::atomic _nextWorkerThreadIndex; + std::atomic _nextWorkerThreadIndex; int _numWorkerThreads; static Oven* _staticInstance; diff --git a/tools/oven/src/ui/BakeWidget.cpp b/tools/oven/src/ui/BakeWidget.cpp index 22e1b1aaf9..43f4c50328 100644 --- a/tools/oven/src/ui/BakeWidget.cpp +++ b/tools/oven/src/ui/BakeWidget.cpp @@ -41,5 +41,5 @@ void BakeWidget::cancelButtonClicked() { auto stackedWidget = qobject_cast(parentWidget()); stackedWidget->removeWidget(this); - this->deleteLater(); + deleteLater(); } diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 61eea55917..f80185df0f 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -21,18 +21,21 @@ #include #include +#include "../Oven.h" #include "../OvenGUIApplication.h" - +#include "OvenMainWindow.h" +#include "FBXBaker.h" +#include "OBJBaker.h" #include "ModelBakeWidget.h" + static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory"; static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : BakeWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) -{ + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } @@ -113,7 +116,7 @@ void ModelBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx)"); + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx *.obj)"); if (!selectedFiles.isEmpty()) { // set the contents of the model file text box to be the path to the selected file @@ -189,7 +192,7 @@ void ModelBakeWidget::bakeButtonClicked() { subFolderName = modelName + "-" + QString::number(++iteration) + "/"; } - outputDirectory.mkdir(subFolderName); + outputDirectory.mkpath(subFolderName); if (!outputDirectory.exists()) { QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory."); @@ -200,16 +203,25 @@ void ModelBakeWidget::bakeButtonClicked() { QDir bakedOutputDirectory = outputDirectory.absoluteFilePath("baked"); QDir originalOutputDirectory = outputDirectory.absoluteFilePath("original"); - + bakedOutputDirectory.mkdir("."); originalOutputDirectory.mkdir("."); - // everything seems to be in place, kick off a bake for this model now - auto baker = std::unique_ptr { - new FBXBaker(modelToBakeURL, []() -> QThread* { - return Oven::instance().getNextWorkerThread(); - }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) + std::unique_ptr baker; + auto getWorkerThreadCallback = []() -> QThread* { + return Oven::instance().getNextWorkerThread(); }; + // everything seems to be in place, kick off a bake for this model now + if (modelToBakeURL.fileName().endsWith(".fbx")) { + baker.reset(new FBXBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(), + originalOutputDirectory.absolutePath())); + } else if (modelToBakeURL.fileName().endsWith(".obj")) { + baker.reset(new OBJBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(), + originalOutputDirectory.absolutePath())); + } else { + qWarning() << "Unknown model type: " << modelToBakeURL.fileName(); + continue; + } // move the baker to the FBX baker thread baker->moveToThread(Oven::instance().getNextWorkerThread()); @@ -218,7 +230,7 @@ void ModelBakeWidget::bakeButtonClicked() { QMetaObject::invokeMethod(baker.get(), "bake"); // make sure we hear about the results of this baker when it is done - connect(baker.get(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); + connect(baker.get(), &Baker::finished, this, &ModelBakeWidget::handleFinishedBaker); // add a pending row to the results window to show that this bake is in process auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); @@ -231,27 +243,31 @@ void ModelBakeWidget::bakeButtonClicked() { } void ModelBakeWidget::handleFinishedBaker() { - if (auto baker = qobject_cast(sender())) { - // add the results of this bake to the results window - auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { - return value.first.get() == baker; - }); + Baker* baker = dynamic_cast(sender()); + if (!baker) { + qWarning() << "Received signal from unexpected sender"; + return; + } - for (auto& file : baker->getOutputFiles()) { - qDebug() << "Baked file: " << file; + // add the results of this bake to the results window + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + for (auto& file : baker->getOutputFiles()) { + qDebug() << "Baked file: " << file; + } + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); + + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); } - if (it != _bakers.end()) { - auto resultRow = it->second; - auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow(); - - if (baker->hasErrors()) { - resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); - } else { - resultsWindow->changeStatusForRow(resultRow, "Success"); - } - - _bakers.erase(it); - } + _bakers.erase(it); } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index b42b8725f6..fad623bf24 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -16,8 +16,6 @@ #include -#include - #include "BakeWidget.h" class QLineEdit; diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index dd40fb1f8f..bebc2fa7dc 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -46,7 +46,7 @@ ResultsWindow* OvenMainWindow::showResultsWindow(bool shouldRaise) { _resultsWindow->show(); // place the results window initially below our window - _resultsWindow->move(_resultsWindow->x(), this->frameGeometry().bottom()); + _resultsWindow->move(_resultsWindow->x(), frameGeometry().bottom()); } // show the results window and make sure it is in front diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 0b12cb64ed..a52e948f01 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -41,18 +41,17 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } try { QByteArray fbxContents = fbx.readAll(); - FBXGeometry* geom; + FBXGeometry::Pointer geom; if (filename.toLower().endsWith(".obj")) { bool combineParts = false; geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); } else if (filename.toLower().endsWith(".fbx")) { - geom = readFBX(fbxContents, QVariantHash(), filename); + geom.reset(readFBX(fbxContents, QVariantHash(), filename)); } else { qWarning() << "file has unknown extension" << filename; return false; } result = *geom; - delete geom; reSortFBXGeometryMeshes(result); } catch (const QString& error) {