diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000000..ac639c5ae7 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xms2g -Xmx4g diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 4545f48c41..7e1420ef60 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -497,7 +497,7 @@ void AudioMixerClientData::processStreamPacket(ReceivedMessage& message, Concurr if (newStream) { // whenever a stream is added, push it to the concurrent vector of streams added this frame - addedStreams.emplace_back(getNodeID(), getNodeLocalID(), matchingStream->getStreamIdentifier(), matchingStream.get()); + addedStreams.push_back(AddedStream(getNodeID(), getNodeLocalID(), matchingStream->getStreamIdentifier(), matchingStream.get())); } } diff --git a/interface/resources/html/img/tablet-help-keyboard.jpg b/interface/resources/html/img/tablet-help-keyboard.jpg index 7045abed75..f19c399f3a 100644 Binary files a/interface/resources/html/img/tablet-help-keyboard.jpg and b/interface/resources/html/img/tablet-help-keyboard.jpg differ diff --git a/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-1.jpg b/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-1.jpg index ce0d87363f..5632467c32 100644 Binary files a/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-1.jpg and b/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-1.jpg differ diff --git a/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-2.jpg b/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-2.jpg index 40bed974af..38ebf08162 100644 Binary files a/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-2.jpg and b/interface/resources/qml/hifi/commerce/common/images/Purchase-First-Run-2.jpg differ diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index a515c8031f..0a5c3e8053 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -39,6 +39,7 @@ Item { property string sendingPubliclyEffectImage; property var http; property var listModelName; + property var keyboardContainer: nil; // This object is always used in a popup or full-screen Wallet section. // This MouseArea is used to prevent a user from being @@ -1125,8 +1126,7 @@ Item { checked: Settings.getValue("sendAssetsNearbyPublicly", true); text: "Show Effect" // Anchors - anchors.top: messageContainer.bottom; - anchors.topMargin: 16; + anchors.verticalCenter: bottomBarContainer.verticalCenter; anchors.left: parent.left; anchors.leftMargin: 20; width: 130; @@ -1168,6 +1168,9 @@ Item { lightboxPopup.visible = false; } lightboxPopup.visible = true; + if (keyboardContainer) { + keyboardContainer.keyboardRaised = false; + } } } } @@ -1178,8 +1181,8 @@ Item { anchors.leftMargin: 20; anchors.right: parent.right; anchors.rightMargin: 20; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 20; + anchors.top: messageContainer.bottom; + anchors.topMargin: 20; height: 60; // "CANCEL" button @@ -1187,11 +1190,11 @@ Item { id: cancelButton_sendAssetStep; color: root.assetName === "" ? hifi.buttons.noneBorderlessWhite : hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.dark; - anchors.left: parent.left; - anchors.leftMargin: 24; + anchors.right: sendButton.left; + anchors.rightMargin: 24; anchors.verticalCenter: parent.verticalCenter; height: 40; - width: 150; + width: 100; text: "CANCEL"; onClicked: { resetSendAssetData(); @@ -1205,10 +1208,10 @@ Item { color: hifi.buttons.blue; colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; anchors.right: parent.right; - anchors.rightMargin: 24; + anchors.rightMargin: 0; anchors.verticalCenter: parent.verticalCenter; height: 40; - width: 150; + width: 100; text: "SUBMIT"; onClicked: { if (root.assetName === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 2435678e77..015ec3a172 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -158,6 +158,7 @@ Rectangle { listModelName: "Gift Connections"; z: 998; visible: root.activeView === "giftAsset"; + keyboardContainer: root; anchors.fill: parent; parentAppTitleBarHeight: 70; parentAppNavBarHeight: 0; @@ -585,7 +586,7 @@ Rectangle { visible: purchasesModel.count !== 0; clip: true; model: purchasesModel; - snapMode: ListView.SnapToItem; + snapMode: ListView.NoSnap; // Anchors anchors.top: separator.bottom; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 588b80c435..cbb77883df 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -354,6 +354,7 @@ Rectangle { listModelName: "Send Money Connections"; z: 997; visible: root.activeView === "sendMoney"; + keyboardContainer: root; anchors.fill: parent; parentAppTitleBarHeight: titleBarContainer.height; parentAppNavBarHeight: tabButtonsContainer.height; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2dc3645be0..1bd6af2eba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -8476,6 +8476,16 @@ QUuid Application::getTabletFrameID() const { return HMD->getCurrentTabletFrameID(); } +QVector Application::getTabletIDs() const { + // Most important overlays first. + QVector result; + auto HMD = DependencyManager::get(); + result << HMD->getCurrentTabletScreenID(); + result << HMD->getCurrentHomeButtonID(); + result << HMD->getCurrentTabletFrameID(); + return result; +} + void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; diff --git a/interface/src/Application.h b/interface/src/Application.h index 75260b910f..c75c2e9efc 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -298,6 +298,7 @@ public: OverlayID getTabletScreenID() const; OverlayID getTabletHomeButtonID() const; QUuid getTabletFrameID() const; // may be an entity or an overlay + QVector getTabletIDs() const; // In order of most important IDs first. void setAvatarOverrideUrl(const QUrl& url, bool save); void clearAvatarOverrideUrl() { _avatarOverrideUrl = QUrl(); _saveAvatarOverrideUrl = false; } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 5c79bedc9a..ce21c68c3d 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -78,8 +78,10 @@ void addAvatarEntities(const QVariantList& avatarEntities) { } entity->setLastBroadcast(usecTimestampNow()); - // since we're creating this object we will immediately volunteer to own its simulation - entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); + if (entityProperties.getDynamic()) { + // since we're creating a dynamic object we volunteer immediately to own its simulation + entity->upgradeScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); + } entityProperties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; @@ -108,6 +110,9 @@ AvatarBookmarks::AvatarBookmarks() { if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) { qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename; + } else { + QFile bookmarksFile(_bookmarksFilename); + bookmarksFile.setPermissions(bookmarksFile.permissions() | QFile::WriteUser); } } readFromFile(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16b765711a..980aca59ce 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -629,8 +629,6 @@ public: const MyHead* getMyHead() const; - Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); }; - /**jsdoc * Get the current position of the avatar's "Head" joint. * @function MyAvatar.getHeadPosition diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 3084542472..c1a49d7a10 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { } glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState())) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && myAvatar->getHMDLeanRecenterEnabled()) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index e05c44c264..f4c98bb9e0 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -532,6 +532,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay bool visibleOnly, bool collidableOnly) { float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; + bool bestIsTablet = false; + auto tabletIDs = qApp->getTabletIDs(); QMutexLocker locker(&_mutex); RayToOverlayIntersectionResult result; @@ -554,10 +556,11 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { bool isDrawInFront = thisOverlay->getDrawInFront(); - if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) - || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { - + bool isTablet = tabletIDs.contains(thisID); + if ((isDrawInFront && !bestIsFront && !bestIsTablet) + || ((isTablet || isDrawInFront || !bestIsFront) && thisDistance < bestDistance)) { bestIsFront = isDrawInFront; + bestIsTablet = isTablet; bestDistance = thisDistance; result.intersects = true; result.distance = thisDistance; @@ -828,40 +831,12 @@ PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay } -RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRay ray) { - QVector overlaysToInclude; - QVector overlaysToDiscard; - RayToOverlayIntersectionResult rayPickResult; - - // first priority is tablet screen - overlaysToInclude << qApp->getTabletScreenID(); - rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); - if (rayPickResult.intersects) { - return rayPickResult; - } - // then tablet home button - overlaysToInclude.clear(); - overlaysToInclude << qApp->getTabletHomeButtonID(); - rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); - if (rayPickResult.intersects) { - return rayPickResult; - } - // then tablet frame - overlaysToInclude.clear(); - overlaysToInclude << OverlayID(qApp->getTabletFrameID()); - rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); - if (rayPickResult.intersects) { - return rayPickResult; - } - // then whatever - return findRayIntersection(ray); -} - bool Overlays::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mousePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), + QVector()); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; @@ -901,7 +876,8 @@ bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), + QVector()); if (rayPickResult.intersects) { _currentClickingOnOverlayID = rayPickResult.overlayID; @@ -964,7 +940,8 @@ bool Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), + QVector()); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); @@ -993,7 +970,8 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionVector(ray, true, QVector(), + QVector()); if (rayPickResult.intersects) { auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 21b9e93648..208fc8d78d 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -44,8 +44,7 @@ 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}. + * The result of a {@link PickRay} search using {@link Overlays.findRayIntersection|findRayIntersection}. * @typedef {object} Overlays.RayToOverlayIntersectionResult * @property {boolean} intersects - true if the {@link PickRay} intersected with a 3D overlay, otherwise * false. @@ -383,7 +382,11 @@ public slots: OverlayPropertyResult getOverlaysProperties(const QVariant& overlaysProperties); /**jsdoc - * Find the closest 3D overlay intersected by a {@link PickRay}. + * Find the closest 3D overlay intersected by a {@link PickRay}. Overlays with their drawInFront property set + * to true have priority over overlays that don't, except that tablet overlays have priority over any + * drawInFront overlays behind them. I.e., if a drawInFront overlay is behind one that isn't + * drawInFront, the drawInFront overlay is returned, but if a tablet overlay is in front of a + * drawInFront overlay, the tablet overlay is returned. * @function Overlays.findRayIntersection * @param {PickRay} pickRay - The PickRay to use for finding overlays. * @param {boolean} [precisionPicking=false] - Unused; exists to match Entity API. @@ -750,8 +753,6 @@ private: OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; - RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); - private slots: void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 91d4e0f9d3..341b554949 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1253,7 +1253,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const bool ENABLE_POLE_VECTORS = true; - const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; if (leftHandEnabled) { @@ -1279,33 +1278,16 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - - if (_smoothPoleVectors) { - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevLeftHandPoleVectorValid) { - _prevLeftHandPoleVectorValid = true; - _prevLeftHandPoleVector = sensorPoleVector; - } - glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; - } else { - _prevLeftHandPoleVector = sensorPoleVector; - } _animVars.set("leftHandPoleVectorEnabled", true); _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); + _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, sensorPoleVector)); } else { - _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); } - } else { - _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); } } else { - _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); _animVars.unset("leftHandPosition"); @@ -1344,33 +1326,16 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); if (usePoleVector) { glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - - if (_smoothPoleVectors) { - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevRightHandPoleVectorValid) { - _prevRightHandPoleVectorValid = true; - _prevRightHandPoleVector = sensorPoleVector; - } - glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; - } else { - _prevRightHandPoleVector = sensorPoleVector; - } - _animVars.set("rightHandPoleVectorEnabled", true); _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); - _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); + _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, sensorPoleVector)); } else { - _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); } } else { - _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); } } else { - _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); _animVars.unset("rightHandPosition"); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 48f00d4e5d..ed0b70d4b6 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -227,7 +227,6 @@ public: const AnimVariantMap& getAnimVars() const { return _lastAnimVars; } const AnimContext::DebugStateMachineMap& getStateMachineMap() const { return _lastContext.getStateMachineMap(); } - void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: void onLoadComplete(); @@ -381,14 +380,6 @@ protected: glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevLeftFootPoleVectorValid { false }; - glm::vec3 _prevRightHandPoleVector{ -Vectors::UNIT_Z }; // sensor space - bool _prevRightHandPoleVectorValid{ false }; - - glm::vec3 _prevLeftHandPoleVector{ -Vectors::UNIT_Z }; // sensor space - bool _prevLeftHandPoleVectorValid{ false }; - - bool _smoothPoleVectors { false }; - int _rigId; bool _headEnabled { false }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f154746707..6770cd7f96 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -173,7 +173,6 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; if (_isTransiting) { float nextTime = _currentTime + deltaTime; - glm::vec3 newPosition; if (nextTime >= _totalTime) { _currentPosition = _endPosition; _isTransiting = false; @@ -704,6 +703,19 @@ static TextRenderer3D* textRenderer(TextRendererType type) { return displayNameRenderer; } +void Avatar::metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, + const render::ItemIDs& subItemIDs) { + render::Transaction transaction; + transaction.updateItem(_renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, + subItemIDs](AvatarData& avatar) { + auto avatarPtr = dynamic_cast(&avatar); + if (avatarPtr) { + avatarPtr->setBlendedVertices(blendshapeNumber, blendshapeOffsets, blendedMeshSizes, subItemIDs); + } + }); + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); +} + void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); auto avatarPayloadPointer = std::shared_ptr>(avatarPayload); @@ -713,7 +725,8 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc // INitialize the _render bound as we are creating the avatar render item _renderBound = getBounds(); transaction.resetItem(_renderItemID, avatarPayloadPointer); - _skeletonModel->addToScene(scene, transaction); + using namespace std::placeholders; + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); @@ -792,7 +805,7 @@ void Avatar::updateRenderItem(render::Transaction& transaction) { avatarPtr->_renderBound = renderBound; } } - ); + ); } } @@ -936,7 +949,8 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { render::Transaction transaction; if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); - _skeletonModel->addToScene(scene, transaction); + using namespace std::placeholders; + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1087f74c07..3f4cf7b8c2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -28,6 +28,8 @@ #include "Rig.h" #include +#include "MetaModelPayload.h" + namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar); @@ -108,7 +110,7 @@ private: float _scale { 1.0f }; }; -class Avatar : public AvatarData, public scriptable::ModelProvider { +class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaModelPayload { Q_OBJECT // This property has JSDoc in MyAvatar.h. @@ -620,6 +622,9 @@ protected: static const float ATTACHMENT_LOADING_PRIORITY; LoadingStatus _loadingStatus { LoadingStatus::NoModel }; + + void metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, + const render::ItemIDs& subItemIDs); }; #endif // hifi_Avatar_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b5f9d9f833..2d31549872 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -24,8 +24,6 @@ #include "RenderableEntityItem.h" - - class Model; class EntityTreeRenderer; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6279f0bae0..36ffc68fd3 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -673,7 +673,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const QUuid& myNodeID = nodeList->getSessionUUID(); bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); - // pack SimulationOwner and terse update properties near each other + // pack SimulationOwner, transform, and velocity properties near each other // NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data // even when we would otherwise ignore the rest of the packet. @@ -1358,8 +1358,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire return properties; } -void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const { - // a TerseUpdate includes the transform and its derivatives +void EntityItem::getTransformAndVelocityProperties(EntityItemProperties& properties) const { if (!properties._positionChanged) { properties._position = getLocalPosition(); } @@ -1383,8 +1382,11 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c properties._accelerationChanged = true; } -void EntityItem::setScriptSimulationPriority(uint8_t priority) { - uint8_t newPriority = stillHasGrabActions() ? glm::max(priority, SCRIPT_GRAB_SIMULATION_PRIORITY) : priority; +void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) { + uint8_t newPriority = glm::max(priority, _scriptSimulationPriority); + if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasGrabActions()) { + newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; + } if (newPriority != _scriptSimulationPriority) { // set the dirty flag to trigger a bid or ownership update markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); @@ -1419,7 +1421,7 @@ bool EntityItem::stillWaitingToTakeOwnership(uint64_t timestamp) const { bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - // these affect TerseUpdate properties + // these affect transform and velocity properties SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); @@ -3256,3 +3258,26 @@ void EntityItem::setScriptHasFinishedPreload(bool value) { bool EntityItem::isScriptPreloadFinished() { return _scriptPreloadFinished; } + +void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority) { + if (dynamicDataNeedsTransmit()) { + setDynamicDataNeedsTransmit(false); + properties.setActionData(getDynamicData()); + } + + if (updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(getQueryAACube()); + } + + // set the LastEdited of the properties but NOT the entity itself + properties.setLastEdited(now); + + clearScriptSimulationPriority(); + properties.setSimulationOwner(Physics::getSessionUUID(), priority); + setPendingOwnershipPriority(priority); + + properties.setClientOnly(getClientOnly()); + properties.setOwningAvatarID(getOwningAvatarID()); + setLastBroadcast(now); // for debug/physics status icons +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index e4063eac3b..c49017b2e0 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -325,7 +325,7 @@ public: // TODO: move this "ScriptSimulationPriority" and "PendingOwnership" stuff into EntityMotionState // but first would need to do some other cleanup. In the meantime these live here as "scratch space" // to allow libs that don't know about each other to communicate. - void setScriptSimulationPriority(uint8_t priority); + void upgradeScriptSimulationPriority(uint8_t priority); void clearScriptSimulationPriority(); uint8_t getScriptSimulationPriority() const { return _scriptSimulationPriority; } void setPendingOwnershipPriority(uint8_t priority); @@ -420,7 +420,7 @@ public: quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); } - void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + void getTransformAndVelocityProperties(EntityItemProperties& properties) const; void flagForMotionStateChange() { _flags |= Simulation::DIRTY_MOTION_TYPE; } @@ -535,6 +535,8 @@ public: const GrabPropertyGroup& getGrabProperties() const { return _grabProperties; } + void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority); + signals: void requestRenderUpdate(); void spaceUpdate(std::pair data); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index dd5c2020c8..c90d1f6635 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3244,9 +3244,12 @@ AABox EntityItemProperties::getAABox() const { return AABox(rotatedExtentsRelativeToRegistrationPoint); } -bool EntityItemProperties::hasTerseUpdateChanges() const { - // a TerseUpdate includes the transform and its derivatives - return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged; +bool EntityItemProperties::hasTransformOrVelocityChanges() const { + return _positionChanged ||_localPositionChanged + || _rotationChanged || _localRotationChanged + || _velocityChanged || _localVelocityChanged + || _angularVelocityChanged || _localAngularVelocityChanged + || _accelerationChanged; } bool EntityItemProperties::hasMiscPhysicsChanges() const { @@ -3255,6 +3258,56 @@ bool EntityItemProperties::hasMiscPhysicsChanges() const { _compoundShapeURLChanged || _dynamicChanged || _collisionlessChanged || _collisionMaskChanged; } +bool EntityItemProperties::hasSimulationRestrictedChanges() const { + return _positionChanged || _localPositionChanged + || _rotationChanged || _localRotationChanged + || _velocityChanged || _localVelocityChanged + || _angularVelocityChanged || _localAngularVelocityChanged + || _accelerationChanged + || _parentIDChanged || _parentJointIndexChanged; +} + +void EntityItemProperties::copySimulationRestrictedProperties(const EntityItemPointer& entity) { + if (!_parentIDChanged) { + setParentID(entity->getParentID()); + } + if (!_parentJointIndexChanged) { + setParentJointIndex(entity->getParentJointIndex()); + } + if (!_localPositionChanged && !_positionChanged) { + setPosition(entity->getWorldPosition()); + } + if (!_localRotationChanged && !_rotationChanged) { + setRotation(entity->getWorldOrientation()); + } + if (!_localVelocityChanged && !_velocityChanged) { + setVelocity(entity->getWorldVelocity()); + } + if (!_localAngularVelocityChanged && !_angularVelocityChanged) { + setAngularVelocity(entity->getWorldAngularVelocity()); + } + if (!_accelerationChanged) { + setAcceleration(entity->getAcceleration()); + } + if (!_localDimensionsChanged && !_dimensionsChanged) { + setDimensions(entity->getScaledDimensions()); + } +} + +void EntityItemProperties::clearSimulationRestrictedProperties() { + _positionChanged = false; + _localPositionChanged = false; + _rotationChanged = false; + _localRotationChanged = false; + _velocityChanged = false; + _localVelocityChanged = false; + _angularVelocityChanged = false; + _localAngularVelocityChanged = false; + _accelerationChanged = false; + _parentIDChanged = false; + _parentJointIndexChanged = false; +} + void EntityItemProperties::clearSimulationOwner() { _simulationOwner.clear(); _simulationOwnerChanged = true; @@ -3273,6 +3326,20 @@ void EntityItemProperties::setSimulationOwner(const QByteArray& data) { } } +uint8_t EntityItemProperties::computeSimulationBidPriority() const { + uint8_t priority = 0; + if (_parentIDChanged || _parentJointIndexChanged) { + // we need higher simulation ownership priority to chang parenting info + priority = SCRIPT_GRAB_SIMULATION_PRIORITY; + } else if ( _positionChanged || _localPositionChanged + || _rotationChanged || _localRotationChanged + || _velocityChanged || _localVelocityChanged + || _angularVelocityChanged || _localAngularVelocityChanged) { + priority = SCRIPT_POKE_SIMULATION_PRIORITY; + } + return priority; +} + QList EntityItemProperties::listChangedProperties() { QList out; if (containsPositionChange()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 8527b471c3..ae2c402d22 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -349,13 +349,18 @@ public: void setCreated(QDateTime& v); - bool hasTerseUpdateChanges() const; + bool hasTransformOrVelocityChanges() const; bool hasMiscPhysicsChanges() const; + bool hasSimulationRestrictedChanges() const; + void copySimulationRestrictedProperties(const EntityItemPointer& entity); + void clearSimulationRestrictedProperties(); + void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); void setSimulationPriority(uint8_t priority) { _simulationOwner.setPriority(priority); } + uint8_t computeSimulationBidPriority() const; void setActionDataDirty() { _actionDataChanged = true; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index a8fce0d5fb..f8b22fdbae 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -174,6 +174,7 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti } +// TODO: this method looks expensive and should take properties by reference, update it, and return void EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProperties& scriptSideProperties, bool scalesWithParent) { // convert position and rotation properties from world-space to local, unless localPosition and localRotation @@ -242,13 +243,12 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _activityTracking.addedEntityCount++; auto nodeList = DependencyManager::get(); - auto sessionID = nodeList->getSessionUUID(); + const auto sessionID = nodeList->getSessionUUID(); EntityItemProperties propertiesWithSimID = properties; if (clientOnly) { - const QUuid myNodeID = sessionID; propertiesWithSimID.setClientOnly(clientOnly); - propertiesWithSimID.setOwningAvatarID(myNodeID); + propertiesWithSimID.setOwningAvatarID(sessionID); } propertiesWithSimID.setLastEditedBy(sessionID); @@ -290,7 +290,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation - entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); + entity->upgradeScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); properties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; @@ -530,54 +530,86 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& _activityTracking.editedEntityCount++; - auto nodeList = DependencyManager::get(); - auto sessionID = nodeList->getSessionUUID(); + const auto sessionID = DependencyManager::get()->getSessionUUID(); EntityItemProperties properties = scriptSideProperties; - properties.setLastEditedBy(sessionID); EntityItemID entityID(id); if (!_entityTree) { + properties.setLastEditedBy(sessionID); queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } - // If we have a local entity tree set, then also update it. - bool updatedEntity = false; - _entityTree->withWriteLock([&] { - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + EntityItemPointer entity(nullptr); + SimulationOwner simulationOwner; + _entityTree->withReadLock([&] { + // make a copy of entity for local logic outside of tree lock + entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { return; } - if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + if (entity->getClientOnly() && entity->getOwningAvatarID() != sessionID) { // don't edit other avatar's avatarEntities + properties = EntityItemProperties(); return; } + // make a copy of simulationOwner for local logic outside of tree lock + simulationOwner = entity->getSimulationOwner(); + }); - if (scriptSideProperties.parentRelatedPropertyChanged()) { - // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. - // If any of these changed, pull any missing properties from the entity. + if (entity) { + if (properties.hasSimulationRestrictedChanges()) { + if (_bidOnSimulationOwnership) { + // flag for simulation ownership, or upgrade existing ownership priority + // (actual bids for simulation ownership are sent by the PhysicalEntitySimulation) + entity->upgradeScriptSimulationPriority(properties.computeSimulationBidPriority()); + if (simulationOwner.getID() == sessionID) { + // we own the simulation --> copy ALL restricted properties + properties.copySimulationRestrictedProperties(entity); + } else { + // we don't own the simulation but think we would like to - if (!scriptSideProperties.parentIDChanged()) { - properties.setParentID(entity->getParentID()); - } - if (!scriptSideProperties.parentJointIndexChanged()) { - properties.setParentJointIndex(entity->getParentJointIndex()); - } - if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) { - properties.setPosition(entity->getWorldPosition()); - } - if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) { - properties.setRotation(entity->getWorldOrientation()); - } - if (!scriptSideProperties.localDimensionsChanged() && !scriptSideProperties.dimensionsChanged()) { - properties.setDimensions(entity->getScaledDimensions()); + uint8_t desiredPriority = entity->getScriptSimulationPriority(); + if (desiredPriority < simulationOwner.getPriority()) { + // the priority at which we'd like to own it is not high enough + // --> assume failure and clear all restricted property changes + properties.clearSimulationRestrictedProperties(); + } else { + // the priority at which we'd like to own it is high enough to win. + // --> assume success and copy ALL restricted properties + properties.copySimulationRestrictedProperties(entity); + } + } + } else if (!simulationOwner.getID().isNull()) { + // someone owns this but not us + // clear restricted properties + properties.clearSimulationRestrictedProperties(); } + // clear the cached simulationPriority level + entity->upgradeScriptSimulationPriority(0); } + + // set these to make EntityItemProperties::getScalesWithParent() work correctly properties.setClientOnly(entity->getClientOnly()); properties.setOwningAvatarID(entity->getOwningAvatarID()); - properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); + + // make sure the properties has a type, so that the encode can know which properties to include + properties.setType(entity->getType()); + } else if (_bidOnSimulationOwnership) { + // bail when simulation participants don't know about entity + return QUuid(); + } + // TODO: it is possible there is no remaining useful changes in properties and we should bail early. + // How to check for this cheaply? + + properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); + properties.setLastEditedBy(sessionID); + + // done reading and modifying properties --> start write + bool updatedEntity = false; + _entityTree->withWriteLock([&] { updatedEntity = _entityTree->updateEntity(entityID, properties); }); @@ -590,63 +622,37 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // return QUuid(); // } - bool entityFound { false }; + bool hasQueryAACubeRelatedChanges = properties.queryAACubeRelatedPropertyChanged(); + // done writing, send update _entityTree->withReadLock([&] { - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + // find the entity again: maybe it was removed since we last found it + entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { - entityFound = true; - // make sure the properties has a type, so that the encode can know which properties to include - properties.setType(entity->getType()); - bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); - bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; - if (_bidOnSimulationOwnership && hasPhysicsChanges) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); + uint64_t now = usecTimestampNow(); + entity->setLastBroadcast(now); - if (entity->getSimulatorID() == myNodeID) { - // we think we already own the simulation, so make sure to send ALL TerseUpdate properties - if (hasTerseUpdateChanges) { - entity->getAllTerseUpdateProperties(properties); - } - // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object - // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update - // and instead let the physics simulation decide when to send a terse update. This would remove - // the "slide-no-rotate" glitch (and typical double-update) that we see during the "poke rolling - // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible - // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's - // simulation. - - if (entity->getSimulationPriority() < SCRIPT_POKE_SIMULATION_PRIORITY) { - // we re-assert our simulation ownership at a higher priority - properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); - } - } else { - // we make a bid for simulation ownership - properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); - entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); - } - } - if (properties.queryAACubeRelatedPropertyChanged()) { + if (hasQueryAACubeRelatedChanges) { properties.setQueryAACube(entity->getQueryAACube()); - } - entity->setLastBroadcast(usecTimestampNow()); - properties.setLastEdited(entity->getLastEdited()); - // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server - // if they've changed. - entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - if (descendant->getNestableType() == NestableType::Entity) { - if (descendant->updateQueryAACube()) { - EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); - EntityItemProperties newQueryCubeProperties; - newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); - newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(usecTimestampNow()); + // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server + // if they've changed. + entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + if (descendant->getNestableType() == NestableType::Entity) { + if (descendant->updateQueryAACube()) { + EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); + EntityItemProperties newQueryCubeProperties; + newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); + newQueryCubeProperties.setLastEdited(properties.getLastEdited()); + queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(now); + } } - } - }); - } else { + }); + } + } + }); + if (!entity) { + if (hasQueryAACubeRelatedChanges) { // Sometimes ESS don't have the entity they are trying to edit in their local tree. In this case, // convertPropertiesFromScriptSemantics doesn't get called and local* edits will get dropped. // This is because, on the script side, "position" is in world frame, but in the network @@ -668,8 +674,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& properties.setDimensions(properties.getLocalDimensions()); } } - }); - if (!entityFound) { // we've made an edit to an entity we don't know about, or to a non-entity. If it's a known non-entity, // print a warning and don't send an edit packet to the entity-server. QSharedPointer parentFinder = DependencyManager::get(); @@ -1449,7 +1453,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, } action->setIsMine(true); success = entity->addAction(simulation, action); - entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->upgradeScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); return false; // Physics will cause a packet to be sent, so don't send from here. }); if (success) { @@ -1465,7 +1469,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { - entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->upgradeScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); } return success; }); @@ -1479,7 +1483,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& success = entity->removeAction(simulation, actionID); if (success) { // reduce from grab to poke - entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); + entity->upgradeScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } return false; // Physics will cause a packet to be sent, so don't send from here. }); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 13c21d89e7..1203e65685 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -67,6 +67,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_clearFramebuffer), (&::gpu::gl::GLBackend::do_blit), (&::gpu::gl::GLBackend::do_generateTextureMips), + (&::gpu::gl::GLBackend::do_generateTextureMipsWithPipeline), (&::gpu::gl::GLBackend::do_advance), @@ -166,6 +167,10 @@ GLBackend::GLBackend() { GLBackend::~GLBackend() {} void GLBackend::shutdown() { + if (_mipGenerationFramebufferId) { + glDeleteFramebuffers(1, &_mipGenerationFramebufferId); + _mipGenerationFramebufferId = 0; + } killInput(); killTransform(); killTextureManagementStage(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 0b76ef17de..267c2a97ad 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -288,6 +288,7 @@ public: virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final; virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final; + virtual void do_generateTextureMipsWithPipeline(const Batch& batch, size_t paramOffset) final; // Transform Stage virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; @@ -407,6 +408,8 @@ public: protected: virtual GLint getRealUniformLocation(GLint location) const; + virtual void draw(GLenum mode, uint32 numVertices, uint32 startVertex) = 0; + void recycle() const override; // FIXME instead of a single flag, create a features struct similar to @@ -696,6 +699,8 @@ protected: virtual void initTextureManagementStage(); virtual void killTextureManagementStage(); + GLuint _mipGenerationFramebufferId{ 0 }; + typedef void (GLBackend::*CommandCall)(const Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; friend class GLState; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp index f4fb3fcf2c..b74ff079d7 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTexture.cpp @@ -79,3 +79,55 @@ void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { object->generateMips(); } + +void GLBackend::do_generateTextureMipsWithPipeline(const Batch& batch, size_t paramOffset) { + TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); + if (!resourceTexture) { + return; + } + + // Always make sure the GLObject is in sync + GLTexture* object = syncGPUObject(resourceTexture); + if (object) { + GLuint to = object->_texture; + glActiveTexture(GL_TEXTURE0 + gpu::slot::texture::MipCreationInput); + glBindTexture(object->_target, to); + (void)CHECK_GL_ERROR(); + } else { + return; + } + + auto numMips = batch._params[paramOffset + 1]._int; + if (numMips < 0) { + numMips = resourceTexture->getNumMips(); + } else { + numMips = std::min(numMips, (int)resourceTexture->getNumMips()); + } + + if (_mipGenerationFramebufferId == 0) { + glGenFramebuffers(1, &_mipGenerationFramebufferId); + Q_ASSERT(_mipGenerationFramebufferId > 0); + } + + glBindFramebuffer(GL_FRAMEBUFFER, _mipGenerationFramebufferId); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0); + + for (int level = 1; level < numMips; level++) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, object->_id, level); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, level - 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level - 1); + + const auto mipDimensions = resourceTexture->evalMipDimensions(level); + glViewport(0, 0, mipDimensions.x, mipDimensions.y); + draw(GL_TRIANGLE_STRIP, 4, 0); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, numMips - 1); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + resetOutputStage(); + // Restore viewport + ivec4& vp = _transform._viewport; + glViewport(vp.x, vp.y, vp.z, vp.w); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp index 88cf89ad99..43ae4691b9 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp @@ -20,6 +20,29 @@ using namespace gpu::gl41; const std::string GL41Backend::GL41_VERSION { "GL41" }; +void GL41Backend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) { + if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, 2); +#else + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); +#endif + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) { Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index e5f7415107..5d691d032a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -130,6 +130,9 @@ public: }; protected: + + void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override; + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index bbe011d237..e86eae2c2d 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -42,6 +42,30 @@ void GL45Backend::recycle() const { Parent::recycle(); } +void GL45Backend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) { + if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, 2); +#else + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); +#endif + + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) { Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 30656b47c7..77095375af 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -229,6 +229,7 @@ public: protected: + void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override; void recycle() const override; GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp index b277889771..cb40b4fc9b 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp @@ -20,6 +20,29 @@ using namespace gpu::gles; const std::string GLESBackend::GLES_VERSION { "GLES" }; +void GLESBackend::draw(GLenum mode, uint32 numVertices, uint32 startVertex) { + if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, 2); +#else + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); +#endif + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + void GLESBackend::do_draw(const Batch& batch, size_t paramOffset) { Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 56ae41da31..7f6765c129 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -126,6 +126,9 @@ public: }; protected: + + void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override; + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 018ca8f02f..e3ea210ecb 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -426,6 +426,13 @@ void Batch::generateTextureMips(const TexturePointer& texture) { _params.emplace_back(_textures.cache(texture)); } +void Batch::generateTextureMipsWithPipeline(const TexturePointer& texture, int numMips) { + ADD_COMMAND(generateTextureMipsWithPipeline); + + _params.emplace_back(_textures.cache(texture)); + _params.emplace_back(numMips); +} + void Batch::beginQuery(const QueryPointer& query) { ADD_COMMAND(beginQuery); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 0f9c2f554b..96a45d3111 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -226,6 +226,8 @@ public: // Generate the mips for a texture void generateTextureMips(const TexturePointer& texture); + // Generate the mips for a texture using the current pipeline + void generateTextureMipsWithPipeline(const TexturePointer& destTexture, int numMips = -1); // Query Section void beginQuery(const QueryPointer& query); @@ -326,6 +328,7 @@ public: COMMAND_clearFramebuffer, COMMAND_blit, COMMAND_generateTextureMips, + COMMAND_generateTextureMipsWithPipeline, COMMAND_advance, diff --git a/libraries/gpu/src/gpu/MipGeneration.slh b/libraries/gpu/src/gpu/MipGeneration.slh new file mode 100644 index 0000000000..bc8dd39042 --- /dev/null +++ b/libraries/gpu/src/gpu/MipGeneration.slh @@ -0,0 +1,20 @@ + +<@if not MIP_GENERATION_SLH@> +<@def MIP_GENERATION_SLH@> + +<@include gpu/ShaderConstants.h@> + +layout(binding=GPU_TEXTURE_MIP_CREATION_INPUT) uniform sampler2D texMap; + +in vec2 varTexCoord0; + +<@endif@> \ No newline at end of file diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h index 13dfd1be9c..0724b4eb40 100644 --- a/libraries/gpu/src/gpu/ShaderConstants.h +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -21,6 +21,9 @@ #define GPU_TEXTURE_TRANSFORM_OBJECT 31 +// Mip creation +#define GPU_TEXTURE_MIP_CREATION_INPUT 30 + #define GPU_STORAGE_TRANSFORM_OBJECT 7 #define GPU_ATTR_POSITION 0 @@ -67,7 +70,8 @@ enum Buffer { namespace texture { enum Texture { ObjectTransforms = GPU_TEXTURE_TRANSFORM_OBJECT, -}; + MipCreationInput = GPU_TEXTURE_MIP_CREATION_INPUT, +}; } // namespace texture namespace storage { diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 615546b410..182a79ec4b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -140,8 +140,10 @@ void DomainHandler::hardReset() { } bool DomainHandler::isHardRefusal(int reasonCode) { - return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch || reasonCode == (int)ConnectionRefusedReason::NotAuthorized || - reasonCode == (int)ConnectionRefusedReason::TimedOut); + return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch || + reasonCode == (int)ConnectionRefusedReason::TooManyUsers || + reasonCode == (int)ConnectionRefusedReason::NotAuthorized || + reasonCode == (int)ConnectionRefusedReason::TimedOut); } bool DomainHandler::getInterstitialModeEnabled() const { @@ -473,7 +475,7 @@ bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::NotAuthorized: return true; - + default: case ConnectionRefusedReason::Unknown: case ConnectionRefusedReason::ProtocolMismatch: diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8162bf4e18..c920665279 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -502,36 +502,18 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s properties.setVelocity(linearVelocity); properties.setAcceleration(_entity->getAcceleration()); properties.setAngularVelocity(angularVelocity); - if (_entity->dynamicDataNeedsTransmit()) { - _entity->setDynamicDataNeedsTransmit(false); - properties.setActionData(_entity->getDynamicData()); - } - - if (_entity->updateQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); - } - - // set the LastEdited of the properties but NOT the entity itself - quint64 now = usecTimestampNow(); - properties.setLastEdited(now); // we don't own the simulation for this entity yet, but we're sending a bid for it + quint64 now = usecTimestampNow(); uint8_t finalBidPriority = computeFinalBidPriority(); - _entity->clearScriptSimulationPriority(); - properties.setSimulationOwner(Physics::getSessionUUID(), finalBidPriority); - _entity->setPendingOwnershipPriority(finalBidPriority); + _entity->prepareForSimulationOwnershipBid(properties, now, finalBidPriority); EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; - properties.setClientOnly(_entity->getClientOnly()); - properties.setOwningAvatarID(_entity->getOwningAvatarID()); - EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast(packetSender); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); - _entity->setLastBroadcast(now); // for debug/physics status icons // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 8ea4cdcd60..81a017a46d 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -96,7 +96,7 @@ void CauterizedModel::createRenderItemSet() { shapeID++; } } - _blendshapeBuffersInitialized = true; + _blendshapeOffsetsInitialized = true; } else { Model::createRenderItemSet(); } @@ -175,7 +175,7 @@ void CauterizedModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (_blendshapeBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 2e51013bc5..2d4aee7880 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -39,10 +39,10 @@ namespace gr { extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); -HighlightRessources::HighlightRessources() { +HighlightResources::HighlightResources() { } -void HighlightRessources::update(const gpu::FramebufferPointer& primaryFrameBuffer) { +void HighlightResources::update(const gpu::FramebufferPointer& primaryFrameBuffer) { auto newFrameSize = glm::ivec2(primaryFrameBuffer->getSize()); // If the buffer size changed, we need to delete our FBOs and recreate them at the @@ -58,32 +58,37 @@ void HighlightRessources::update(const gpu::FramebufferPointer& primaryFrameBuff if (!_colorFrameBuffer) { allocateColorBuffer(primaryFrameBuffer); } + + // The primaryFrameBuffer render buffer can change + if (_colorFrameBuffer->getRenderBuffer(0) != primaryFrameBuffer->getRenderBuffer(0)) { + _colorFrameBuffer->setRenderBuffer(0, primaryFrameBuffer->getRenderBuffer(0)); + } } } -void HighlightRessources::allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { +void HighlightResources::allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { _colorFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithStencil")); _colorFrameBuffer->setRenderBuffer(0, primaryFrameBuffer->getRenderBuffer(0)); _colorFrameBuffer->setStencilBuffer(_depthStencilTexture, _depthStencilTexture->getTexelFormat()); } -void HighlightRessources::allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { +void HighlightResources::allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); _depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, _frameSize.x, _frameSize.y)); _depthFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("highlightDepth")); _depthFrameBuffer->setDepthStencilBuffer(_depthStencilTexture, depthFormat); } -gpu::FramebufferPointer HighlightRessources::getDepthFramebuffer() { +gpu::FramebufferPointer HighlightResources::getDepthFramebuffer() { assert(_depthFrameBuffer); return _depthFrameBuffer; } -gpu::TexturePointer HighlightRessources::getDepthTexture() { +gpu::TexturePointer HighlightResources::getDepthTexture() { return _depthStencilTexture; } -gpu::FramebufferPointer HighlightRessources::getColorFramebuffer() { +gpu::FramebufferPointer HighlightResources::getColorFramebuffer() { assert(_colorFrameBuffer); return _colorFrameBuffer; } @@ -97,25 +102,21 @@ float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& } PrepareDrawHighlight::PrepareDrawHighlight() { - _ressources = std::make_shared(); + _resources = std::make_shared(); } void PrepareDrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { auto destinationFrameBuffer = inputs; - _ressources->update(destinationFrameBuffer); - outputs = _ressources; + _resources->update(destinationFrameBuffer); + outputs = _resources; } gpu::PipelinePointer DrawHighlightMask::_stencilMaskPipeline; gpu::PipelinePointer DrawHighlightMask::_stencilMaskFillPipeline; -DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex, - render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters) : - _highlightPassIndex{ highlightIndex }, - _shapePlumber { shapePlumber }, - _sharedParameters{ parameters } { -} +DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, + HighlightSharedParametersPointer parameters) : _highlightPassIndex(highlightIndex), _shapePlumber(shapePlumber), _sharedParameters(parameters) {} void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { assert(renderContext->args); @@ -126,13 +127,13 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c const int PARAMETERS_SLOT = 0; if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) { - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + gpu::StatePointer state = std::make_shared(); state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO, gpu::State::STENCIL_OP_REPLACE)); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); state->setColorWriteMask(false, false, false, false); state->setCullMode(gpu::State::CULL_FRONT); - gpu::StatePointer fillState = gpu::StatePointer(new gpu::State()); + gpu::StatePointer fillState = std::make_shared(); fillState->setDepthTest(false, false, gpu::LESS_EQUAL); fillState->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); fillState->setColorWriteMask(false, false, false, false); @@ -151,7 +152,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex]; if (!inShapes.empty() && !render::HighlightStage::isIndexInvalid(highlightId)) { - auto ressources = inputs.get1(); + auto resources = inputs.get1(); auto& highlight = highlightStage->getHighlight(highlightId); RenderArgs* args = renderContext->args; @@ -165,15 +166,11 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c // while stereo is enabled triggers a warning gpu::doInBatch("DrawHighlightMask::run::begin", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); - batch.setFramebuffer(ressources->getDepthFramebuffer()); + batch.setFramebuffer(resources->getDepthFramebuffer()); batch.clearDepthStencilFramebuffer(1.0f, 0); }); - glm::mat4 projMat; - Transform viewMat; const auto jitter = inputs.get2(); - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); render::ItemBounds itemBounds; @@ -185,6 +182,10 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto maskDeformedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withDeformed().withDualQuatSkinned()); // Setup camera, projection and viewport for all items + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); batch.setProjectionJitter(jitter.x, jitter.y); @@ -233,7 +234,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c const auto securityMargin = 2.0f; const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w); - const auto framebufferSize = ressources->getSourceFrameSize(); + const auto framebufferSize = resources->getSourceFrameSize(); const glm::vec2 highlightWidth = { blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y }; if (highlightWidth != _outlineWidth.get()) { @@ -241,11 +242,6 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c } 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); - batch.setViewTransform(viewMat); - // Draw stencil mask with object bounding boxes auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline; batch.setPipeline(stencilPipeline); @@ -264,15 +260,14 @@ gpu::PipelinePointer DrawHighlight::_pipeline; gpu::PipelinePointer DrawHighlight::_pipelineFilled; DrawHighlight::DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters) : - _highlightPassIndex{ highlightIndex }, - _sharedParameters{ parameters } { + _highlightPassIndex(highlightIndex), _sharedParameters(parameters) { } void DrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { auto highlightFrameBuffer = inputs.get1(); auto highlightRect = inputs.get3(); - if (highlightFrameBuffer && highlightRect.z>0 && highlightRect.w>0) { + if (highlightFrameBuffer && highlightRect.z > 0 && highlightRect.w > 0) { auto sceneDepthBuffer = inputs.get2(); const auto frameTransform = inputs.get0(); auto highlightedDepthTexture = highlightFrameBuffer->getDepthTexture(); @@ -334,10 +329,11 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightStyle& style) { if (!_pipeline) { - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + gpu::StatePointer state = std::make_shared(); state->setDepthTest(gpu::State::DepthTest(false, false)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + state->setColorWriteMask(true, true, true, true); auto program = gpu::Shader::createProgram(shader::render_utils::program::highlight); _pipeline = gpu::Pipeline::create(program, state); @@ -364,10 +360,10 @@ void DebugHighlight::configure(const Config& config) { } void DebugHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& input) { - const auto highlightRessources = input.get0(); + const auto highlightResources = input.get0(); const auto highlightRect = input.get1(); - if (_isDisplayEnabled && highlightRessources && highlightRect.z>0 && highlightRect.w>0) { + if (_isDisplayEnabled && highlightResources && highlightRect.z > 0 && highlightRect.w > 0) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; @@ -376,7 +372,7 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons auto primaryFramebuffer = input.get3(); gpu::doInBatch("DebugHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); - batch.setFramebuffer(highlightRessources->getColorFramebuffer()); + batch.setFramebuffer(highlightResources->getColorFramebuffer()); const auto geometryBuffer = DependencyManager::get(); @@ -386,13 +382,13 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat, true); + batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); batch.setPipeline(getDepthPipeline()); - batch.setResourceTexture(0, highlightRessources->getDepthTexture()); + batch.setResourceTexture(0, highlightResources->getDepthTexture()); const glm::vec2 bottomLeft(-1.0f, -1.0f); const glm::vec2 topRight(1.0f, 1.0f); geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId); @@ -415,6 +411,7 @@ void DebugHighlight::initializePipelines() { auto state = std::make_shared(); state->setDepthTest(gpu::State::DepthTest(false, false)); state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + state->setColorWriteMask(true, true, true, true); const auto vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); @@ -512,7 +509,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren const auto highlightSelectionNames = task.addJob("SelectionToHighlight", sharedParameters); // Prepare for highlight group rendering. - const auto highlightRessources = task.addJob("PrepareHighlight", primaryFramebuffer); + const auto highlightResources = task.addJob("PrepareHighlight", primaryFramebuffer); render::Varying highlight0Rect; for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { @@ -532,7 +529,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren stream << "HighlightMask" << i; name = stream.str(); } - const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightRessources, jitter).asVarying(); + const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightResources, jitter).asVarying(); const auto highlightedRect = task.addJob(name, drawMaskInputs, i, shapePlumber, sharedParameters); if (i == 0) { highlight0Rect = highlightedRect; @@ -544,12 +541,12 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren stream << "HighlightEffect" << i; name = stream.str(); } - const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect, primaryFramebuffer).asVarying(); + const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightResources, sceneFrameBuffer, highlightedRect, primaryFramebuffer).asVarying(); task.addJob(name, drawHighlightInputs, i, sharedParameters); } // Debug highlight - const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast(highlight0Rect), jitter, primaryFramebuffer).asVarying(); + const auto debugInputs = DebugHighlight::Inputs(highlightResources, const_cast(highlight0Rect), jitter, primaryFramebuffer).asVarying(); task.addJob("HighlightDebug", debugInputs); } diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index 32668c1ab6..933503fdb5 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -19,9 +19,9 @@ #include "DeferredFramebuffer.h" #include "DeferredFrameTransform.h" -class HighlightRessources { +class HighlightResources { public: - HighlightRessources(); + HighlightResources(); gpu::FramebufferPointer getDepthFramebuffer(); gpu::TexturePointer getDepthTexture(); @@ -44,7 +44,7 @@ protected: void allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer); }; -using HighlightRessourcesPointer = std::shared_ptr; +using HighlightResourcesPointer = std::shared_ptr; class HighlightSharedParameters { public: @@ -65,7 +65,7 @@ using HighlightSharedParametersPointer = std::shared_ptr; PrepareDrawHighlight(); @@ -74,7 +74,7 @@ public: private: - HighlightRessourcesPointer _ressources; + HighlightResourcesPointer _resources; }; @@ -112,8 +112,7 @@ private: class DrawHighlightMask { public: - - using Inputs = render::VaryingSet3; + using Inputs = render::VaryingSet3; using Outputs = glm::ivec4; using JobModel = render::Job::ModelIO; @@ -122,7 +121,6 @@ public: void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); protected: - unsigned int _highlightPassIndex; render::ShapePlumberPointer _shapePlumber; HighlightSharedParametersPointer _sharedParameters; @@ -136,7 +134,7 @@ protected: class DrawHighlight { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet5; using Config = render::Job::Config; using JobModel = render::Job::ModelI; @@ -163,11 +161,10 @@ private: class DebugHighlightConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(bool viewMask MEMBER viewMask NOTIFY dirty) + Q_PROPERTY(bool viewMask MEMBER viewMask NOTIFY dirty) public: - - bool viewMask{ false }; + bool viewMask { false }; signals: void dirty(); @@ -175,7 +172,7 @@ signals: class DebugHighlight { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet4; using Config = DebugHighlightConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index c5df2c3e01..2fe0368db2 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -209,6 +209,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); + _meshNumVertices = (int)modelMesh->getNumVertices(); const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); @@ -238,19 +239,12 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in initCache(model); - if (_isBlendShaped) { - auto buffer = model->_blendshapeBuffers.find(meshIndex); - if (buffer != model->_blendshapeBuffers.end()) { - _blendshapeBuffer = buffer->second; - } - } - #ifdef Q_OS_MAC - // On mac AMD, we specifically need to have a _blendshapeBuffer bound when using a deformed mesh pipeline + // On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline // it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh - if ((_isBlendShaped || _isSkinned) && !_blendshapeBuffer) { + if ((_isBlendShaped || _isSkinned)) { glm::vec4 data; - _blendshapeBuffer = std::make_shared(sizeof(glm::vec4), reinterpret_cast(&data)); + _meshBlendshapeBuffer = std::make_shared(sizeof(glm::vec4), reinterpret_cast(&data)); } #endif @@ -292,8 +286,7 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clu if (!_clusterBuffer) { _clusterBuffer = std::make_shared(clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) clusterMatrices.data()); - } - else { + } else { _clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) clusterMatrices.data()); } @@ -313,8 +306,7 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector(clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), (const gpu::Byte*) clusterDualQuaternions.data()); - } - else { + } else { _clusterBuffer->setSubData(0, clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), (const gpu::Byte*) clusterDualQuaternions.data()); } @@ -403,8 +395,8 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); batch.setInputFormat((_drawMesh->getVertexFormat())); - if (_blendshapeBuffer) { - batch.setResourceBuffer(0, _blendshapeBuffer); + if (_meshBlendshapeBuffer) { + batch.setResourceBuffer(0, _meshBlendshapeBuffer); } batch.setInputStream(0, _drawMesh->getVertexStream()); } @@ -431,7 +423,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // IF deformed pass the mesh key - auto drawcallInfo = (uint16_t) (((_isBlendShaped && args->_enableBlendshape) << 0) | ((_isSkinned && args->_enableSkinning) << 1)); + auto drawcallInfo = (uint16_t) (((_isBlendShaped && _meshBlendshapeBuffer && args->_enableBlendshape) << 0) | ((_isSkinned && args->_enableSkinning) << 1)); if (drawcallInfo) { batch.setDrawcallUniform(drawcallInfo); } @@ -483,3 +475,12 @@ void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& blendshapeBuffers, const QVector& blendedMeshSizes) { + if (_meshIndex < blendedMeshSizes.length() && blendedMeshSizes.at(_meshIndex) == _meshNumVertices) { + auto blendshapeBuffer = blendshapeBuffers.find(_meshIndex); + if (blendshapeBuffer != blendshapeBuffers.end()) { + _meshBlendshapeBuffer = blendshapeBuffer->second; + } + } +} diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index ceed4b330b..4d41d1d93e 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -134,10 +134,13 @@ public: bool _isBlendShaped { false }; bool _hasTangents { false }; + void setBlendshapeBuffer(const std::unordered_map& blendshapeBuffers, const QVector& blendedMeshSizes); + private: void initCache(const ModelPointer& model); - gpu::BufferPointer _blendshapeBuffer; + gpu::BufferPointer _meshBlendshapeBuffer; + int _meshNumVertices; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; }; diff --git a/libraries/render-utils/src/MetaModelPayload.cpp b/libraries/render-utils/src/MetaModelPayload.cpp new file mode 100644 index 0000000000..510972d86a --- /dev/null +++ b/libraries/render-utils/src/MetaModelPayload.cpp @@ -0,0 +1,55 @@ +// +// MetaModelPayload.cpp +// +// Created by Sam Gondelman on 10/9/18. +// Copyright 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 +// +#include "MetaModelPayload.h" + +#include "AbstractViewStateInterface.h" +#include "MeshPartPayload.h" + +void MetaModelPayload::setBlendedVertices(int blendNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subRenderItems) { + PROFILE_RANGE(render, __FUNCTION__); + if (blendNumber < _appliedBlendNumber) { + return; + } + _appliedBlendNumber = blendNumber; + + // We have fewer meshes than before. Invalidate everything + if (blendedMeshSizes.length() < (int)_blendshapeBuffers.size()) { + _blendshapeBuffers.clear(); + } + + int index = 0; + for (int i = 0; i < blendedMeshSizes.size(); i++) { + int numVertices = blendedMeshSizes.at(i); + + // This mesh isn't blendshaped + if (numVertices == 0) { + _blendshapeBuffers.erase(i); + continue; + } + + const auto& buffer = _blendshapeBuffers.find(i); + const auto blendShapeBufferSize = numVertices * sizeof(BlendshapeOffset); + if (buffer == _blendshapeBuffers.end()) { + _blendshapeBuffers[i] = std::make_shared(blendShapeBufferSize, (gpu::Byte*) blendshapeOffsets.constData() + index * sizeof(BlendshapeOffset), blendShapeBufferSize); + } else { + buffer->second->setData(blendShapeBufferSize, (gpu::Byte*) blendshapeOffsets.constData() + index * sizeof(BlendshapeOffset)); + } + + index += numVertices; + } + + render::Transaction transaction; + for (auto& id : subRenderItems) { + transaction.updateItem(id, [this, blendedMeshSizes](ModelMeshPartPayload& data) { + data.setBlendshapeBuffer(_blendshapeBuffers, blendedMeshSizes); + }); + } + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); +} diff --git a/libraries/render-utils/src/MetaModelPayload.h b/libraries/render-utils/src/MetaModelPayload.h new file mode 100644 index 0000000000..83731f3039 --- /dev/null +++ b/libraries/render-utils/src/MetaModelPayload.h @@ -0,0 +1,30 @@ +// +// MetaModelPayload.h +// +// Created by Sam Gondelman on 10/9/18. +// Copyright 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_MetaModelPayload_h +#define hifi_MetaModelPayload_h + +#include + +#include "Model.h" + +#include "gpu/Buffer.h" + +class MetaModelPayload { +public: + void setBlendedVertices(int blendNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subRenderItems); + +private: + std::unordered_map _blendshapeBuffers; + int _appliedBlendNumber { 0 }; + +}; + +#endif \ No newline at end of file diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2bc9d25aff..da50fd6ff0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -62,8 +62,6 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), - _blendNumber(0), - _appliedBlendNumber(0), _isWireframe(false), _renderItemKeyGlobalFlags(render::ItemKey::Builder().withVisible().withTagBits(render::hifi::TAG_ALL_VIEWS).build()) { @@ -326,7 +324,7 @@ bool Model::updateGeometry() { initializeBlendshapes(mesh, i); i++; } - _blendshapeBuffersInitialized = true; + _blendshapeOffsetsInitialized = true; needFullUpdate = true; emit rigReady(); } @@ -1010,7 +1008,8 @@ const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, - render::Item::Status::Getters& statusGetters) { + render::Item::Status::Getters& statusGetters, + BlendShapeOperator modelBlendshapeOperator) { if (!_addedToScene && isLoaded()) { updateClusterMatrices(); if (_modelMeshRenderItems.empty()) { @@ -1018,10 +1017,11 @@ bool Model::addToScene(const render::ScenePointer& scene, } } + _modelBlendshapeOperator = modelBlendshapeOperator; + bool somethingAdded = false; if (_modelMeshRenderItemsMap.empty()) { - bool hasTransparent = false; size_t verticesCount = 0; foreach(auto renderItem, _modelMeshRenderItems) { @@ -1063,9 +1063,8 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); - _blendshapeBuffers.clear(); _blendshapeOffsets.clear(); - _blendshapeBuffersInitialized = false; + _blendshapeOffsetsInitialized = false; _addedToScene = false; @@ -1468,7 +1467,7 @@ void Model::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (_blendshapeBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } @@ -1476,9 +1475,8 @@ void Model::updateClusterMatrices() { void Model::deleteGeometry() { _deleteGeometryCounter++; - _blendshapeBuffers.clear(); _blendshapeOffsets.clear(); - _blendshapeBuffersInitialized = false; + _blendshapeOffsetsInitialized = false; _meshStates.clear(); _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); @@ -1556,7 +1554,7 @@ void Model::createRenderItemSet() { shapeID++; } } - _blendshapeBuffersInitialized = true; + _blendshapeOffsetsInitialized = true; } bool Model::isRenderable() const { @@ -1685,6 +1683,7 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe void Blender::run() { QVector blendshapeOffsets; + QVector blendedMeshSizes; if (_model && _model->isLoaded()) { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int offset = 0; @@ -1693,12 +1692,22 @@ void Blender::run() { foreach(const FBXMesh& mesh, meshes) { auto modelMeshBlendshapeOffsets = _model->_blendshapeOffsets.find(meshIndex++); if (mesh.blendshapes.isEmpty() || modelMeshBlendshapeOffsets == _model->_blendshapeOffsets.end()) { + // Not blendshaped or not initialized + blendedMeshSizes.push_back(0); + continue; + } + + if (mesh.vertices.size() != modelMeshBlendshapeOffsets->second.size()) { + // Mesh sizes don't match. Something has gone wrong + blendedMeshSizes.push_back(0); continue; } blendshapeOffsets += modelMeshBlendshapeOffsets->second; BlendshapeOffset* meshBlendshapeOffsets = blendshapeOffsets.data() + offset; - offset += modelMeshBlendshapeOffsets->second.size(); + int numVertices = modelMeshBlendshapeOffsets->second.size(); + blendedMeshSizes.push_back(numVertices); + offset += numVertices; std::vector unpackedBlendshapeOffsets(modelMeshBlendshapeOffsets->second.size()); const float NORMAL_COEFFICIENT_SCALE = 0.01f; @@ -1742,7 +1751,7 @@ void Blender::run() { } // post the result to the ModelBlender, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", - Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, blendshapeOffsets)); + Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, blendshapeOffsets), Q_ARG(QVector, blendedMeshSizes)); } bool Model::maybeStartBlender() { @@ -1753,43 +1762,18 @@ bool Model::maybeStartBlender() { return false; } -void Model::setBlendedVertices(int blendNumber, const QVector& blendshapeOffsets) { - PROFILE_RANGE(render, __FUNCTION__); - if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendshapeBuffersInitialized) { - return; - } - _appliedBlendNumber = blendNumber; - const FBXGeometry& fbxGeometry = getFBXGeometry(); - int index = 0; - for (int i = 0; i < fbxGeometry.meshes.size(); i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); - auto meshBlendshapeOffsets = _blendshapeOffsets.find(i); - const auto& buffer = _blendshapeBuffers.find(i); - if (mesh.blendshapes.isEmpty() || meshBlendshapeOffsets == _blendshapeOffsets.end() || buffer == _blendshapeBuffers.end()) { - continue; - } - - const auto blendshapeOffsetSize = meshBlendshapeOffsets->second.size() * sizeof(BlendshapeOffset); - buffer->second->setSubData(0, blendshapeOffsetSize, (gpu::Byte*) blendshapeOffsets.constData() + index * sizeof(BlendshapeOffset)); - - index += meshBlendshapeOffsets->second.size(); - } -} - void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { if (mesh.blendshapes.empty()) { // mesh doesn't have blendshape, did we allocate one though ? - if (_blendshapeBuffers.find(index) != _blendshapeBuffers.end()) { - qWarning() << "Mesh does not have Blendshape yet a blendshapeOffset buffer is allocated ?"; + if (_blendshapeOffsets.find(index) != _blendshapeOffsets.end()) { + qWarning() << "Mesh does not have Blendshape yet the blendshapeOffsets are allocated ?"; } return; } // Mesh has blendshape, let s allocate the local buffer if not done yet - if (_blendshapeBuffers.find(index) == _blendshapeBuffers.end()) { + if (_blendshapeOffsets.find(index) == _blendshapeOffsets.end()) { QVector blendshapeOffset; blendshapeOffset.fill(BlendshapeOffset(), mesh.vertices.size()); - const auto blendshapeOffsetsSize = blendshapeOffset.size() * sizeof(BlendshapeOffset); - _blendshapeBuffers[index] = std::make_shared(blendshapeOffsetsSize, (const gpu::Byte*) blendshapeOffset.constData(), blendshapeOffsetsSize); _blendshapeOffsets[index] = blendshapeOffset; } } @@ -1822,10 +1806,14 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector blendshapeOffsets) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector blendshapeOffsets, QVector blendedMeshSizes) { if (model) { - model->setBlendedVertices(blendNumber, blendshapeOffsets); + auto blendshapeOperator = model->getModelBlendshapeOperator(); + if (blendshapeOperator) { + blendshapeOperator(blendNumber, blendshapeOffsets, blendedMeshSizes, model->fetchRenderItemIDs()); + } } + { Lock lock(_mutex); _pendingBlenders--; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 954cc57e0b..6988e870a5 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -86,6 +86,7 @@ struct BlendshapeOffsetUnpacked { }; using BlendshapeOffset = BlendshapeOffsetPacked; +using BlendShapeOperator = std::function&, const QVector&, const render::ItemIDs&)>; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public std::enable_shared_from_this, public scriptable::ModelProvider { @@ -144,7 +145,14 @@ public: } bool addToScene(const render::ScenePointer& scene, render::Transaction& transaction, - render::Item::Status::Getters& statusGetters); + BlendShapeOperator modelBlendshapeOperator) { + auto getters = render::Item::Status::Getters(0); + return addToScene(scene, transaction, getters, modelBlendshapeOperator); + } + bool addToScene(const render::ScenePointer& scene, + render::Transaction& transaction, + render::Item::Status::Getters& statusGetters, + BlendShapeOperator modelBlendshapeOperator = nullptr); void removeFromScene(const render::ScenePointer& scene, render::Transaction& transaction); bool isRenderable() const; @@ -158,9 +166,6 @@ public: bool maybeStartBlender(); - /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(int blendNumber, const QVector& blendshapeOffsets); - bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -342,6 +347,7 @@ public: uint32_t getGeometryCounter() const { return _deleteGeometryCounter; } const QMap& getRenderItems() const { return _modelMeshRenderItemsMap; } + BlendShapeOperator getModelBlendshapeOperator() const { return _modelBlendshapeOperator; } void renderDebugMeshBoxes(gpu::Batch& batch); @@ -435,18 +441,13 @@ protected: virtual void deleteGeometry(); - QVector _blendshapeCoefficients; - QUrl _url; - std::unordered_map _blendshapeBuffers; - bool _blendshapeBuffersInitialized{ false }; - - QVector>> _dilatedTextures; - + BlendShapeOperator _modelBlendshapeOperator { nullptr }; + QVector _blendshapeCoefficients; QVector _blendedBlendshapeCoefficients; - int _blendNumber; - int _appliedBlendNumber; + int _blendNumber { 0 }; + bool _blendshapeOffsetsInitialized { false }; mutable QMutex _mutex{ QMutex::Recursive }; @@ -463,7 +464,6 @@ protected: // debug rendering support int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; - static AbstractViewStateInterface* _viewState; QVector> _modelMeshRenderItems; @@ -536,7 +536,7 @@ public: bool shouldComputeBlendshapes() { return _computeBlendshapes; } public slots: - void setBlendedVertices(ModelPointer model, int blendNumber, QVector blendshapeOffsets); + void setBlendedVertices(ModelPointer model, int blendNumber, QVector blendshapeOffsets, QVector blendedMeshSizes); void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; } private: diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 114ccab712..90015768d0 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -78,7 +78,7 @@ void SoftAttachmentModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get(); - if (_blendshapeBuffersInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index 6181414084..efbca66d72 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -24,7 +24,7 @@ this.reticleMinY = MARGIN; this.reticleMaxY; this.parameters = ControllerDispatcherUtils.makeDispatcherModuleParameters( - 540, + 160, // Same as webSurfaceLaserInput. this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100, @@ -63,7 +63,6 @@ this.processLaser = function(controllerData) { var controllerLocation = controllerData.controllerLocations[this.hand]; - // var otherModuleRunning = this.getOtherModule().running; if ((controllerData.triggerValues[this.hand] < ControllerDispatcherUtils.TRIGGER_ON_VALUE || !controllerLocation.valid) || this.pointingAtTablet(controllerData)) { return false; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 4b6eb7e313..1917505bd8 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -28,7 +28,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY = null; this.parameters = makeDispatcherModuleParameters( - 160, + 165, // Lower priority than webSurfaceLaserInput and hudOverlayPointer. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, @@ -127,29 +127,41 @@ Script.include("/~/system/libraries/utils.js"); }; this.run = function(controllerData) { - var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTabletStylusInput" : "LeftTabletStylusInput"); + var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTabletStylusInput" : "LeftTabletStylusInput"); if (tabletStylusInput) { var tabletReady = tabletStylusInput.isReady(controllerData); - if (tabletReady.active) { return this.exitModule(); } } - var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); - if (overlayLaser) { - var overlayLaserReady = overlayLaser.isReady(controllerData); + var webLaser = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput"); + if (webLaser) { + var webLaserReady = webLaser.isReady(controllerData); var target = controllerData.rayPicks[this.hand].objectID; this.sendPointingAtData(controllerData); - if (overlayLaserReady.active && this.pointingAtTablet(target)) { + if (webLaserReady.active && this.pointingAtTablet(target)) { return this.exitModule(); } } - var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + if (!controllerData.triggerClicks[this.hand]) { // Don't grab if trigger pressed when laser starts intersecting. + var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHudOverlayPointer" : "LeftHudOverlayPointer"); + if (hudLaser) { + var hudLaserReady = hudLaser.isReady(controllerData); + if (hudLaserReady.active) { + return this.exitModule(); + } + } + } + + var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); if (nearOverlay) { var nearOverlayReady = nearOverlay.isReady(controllerData); - if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { return this.exitModule(); } diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index ef02e41ef3..65b6744646 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -19,12 +19,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); function InVREditMode(hand) { this.hand = hand; this.disableModules = false; - var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. + this.running = false; + var NO_HAND_LASER = -1; // Invalid hand parameter so that standard laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 200, // Not too high otherwise the tablet laser doesn't work. - this.hand === RIGHT_HAND ? - ["rightHand", "rightHandEquip", "rightHandTrigger"] : - ["leftHand", "leftHandEquip", "leftHandTrigger"], + 166, // Slightly lower priority than inEditMode. + this.hand === RIGHT_HAND + ? ["rightHand", "rightHandEquip", "rightHandTrigger"] + : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, makeLaserParams(NO_HAND_LASER, false) @@ -35,6 +36,35 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (HMD.homeButtonID && objectID === HMD.homeButtonID); }; + // The Shapes app has a non-standard laser: in particular, the laser end dot displays on its own when the laser is + // pointing at the Shapes UI. The laser on/off is controlled by this module but the laser is implemented in the Shapes + // app. + // If, in the future, the Shapes app laser interaction is adopted as a standard UI style then the laser could be + // implemented in the controller modules along side the other laser styles. + var INVREDIT_MODULE_RUNNING = "Hifi-InVREdit-Module-Running"; + + this.runModule = function () { + if (!this.running) { + Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({ + hand: this.hand, + running: true + })); + this.running = true; + } + return makeRunningValues(true, [], []); + }; + + this.exitModule = function () { + if (this.running) { + Messages.sendLocalMessage(INVREDIT_MODULE_RUNNING, JSON.stringify({ + hand: this.hand, + running: false + })); + this.running = false; + } + return makeRunningValues(false, [], []); + }; + this.isReady = function (controllerData) { if (this.disableModules) { return makeRunningValues(true, [], []); @@ -45,7 +75,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.run = function (controllerData) { // Default behavior if disabling is not enabled. if (!this.disableModules) { - return makeRunningValues(false, [], []); + return this.exitModule(); } // Tablet stylus. @@ -55,7 +85,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (tabletStylusInput) { var tabletReady = tabletStylusInput.isReady(controllerData); if (tabletReady.active) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } @@ -67,7 +97,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var overlayLaserReady = overlayLaser.isReady(controllerData); var target = controllerData.rayPicks[this.hand].objectID; if (overlayLaserReady.active && this.pointingAtTablet(target)) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } @@ -78,7 +108,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (nearOverlay) { var nearOverlayReady = nearOverlay.isReady(controllerData); if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { - return makeRunningValues(false, [], []); + return this.exitModule(); + } + } + + // HUD overlay. + if (!controllerData.triggerClicks[this.hand]) { + var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightHudOverlayPointer" + : "LeftHudOverlayPointer"); + if (hudLaser) { + var hudLaserReady = hudLaser.isReady(controllerData); + if (hudLaserReady.active) { + return this.exitModule(); + } } } @@ -89,12 +132,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (teleporter) { var teleporterReady = teleporter.isReady(controllerData); if (teleporterReady.active) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } // Other behaviors are disabled. - return makeRunningValues(true, [], []); + return this.runModule(); }; } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b4684a0fd4..686a516504 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1409,7 +1409,6 @@ Script.scriptEnding.connect(function () { Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); @@ -1710,7 +1709,7 @@ function onPromptTextChanged(prompt) { } } -function handeMenuEvent(menuItem) { +function handleMenuEvent(menuItem) { if (menuItem === "Allow Selecting of Small Models") { allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); } else if (menuItem === "Allow Selecting of Large Models") { @@ -1750,6 +1749,8 @@ function handeMenuEvent(menuItem) { entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); } tooltip.show(false); } @@ -1875,7 +1876,7 @@ function importSVO(importURL) { } Window.svoImportRequested.connect(importSVO); -Menu.menuItemEvent.connect(handeMenuEvent); +Menu.menuItemEvent.connect(handleMenuEvent); var keyPressEvent = function (event) { if (isActive) { diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index fdc00c1ebf..a2f0d074f0 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -23,12 +23,17 @@ miniState = null, // Hands. + NO_HAND = -1, LEFT_HAND = 0, RIGHT_HAND = 1, HAND_NAMES = ["LeftHand", "RightHand"], - // Miscellaneous. + // Track controller grabbing state. HIFI_OBJECT_MANIPULATION_CHANNEL = "Hifi-Object-Manipulation", + grabbingHand = NO_HAND, + grabbedItem = null, + + // Miscellaneous. tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"), DEBUG = false; @@ -737,37 +742,11 @@ setState(MINI_VISIBLE); } - function enterMiniShowing(hand) { - miniHand = hand; - ui.show(miniHand); - miniScaleStart = Date.now(); - miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT); - } - - function updateMiniShowing() { - if (HMD.showTablet) { - setState(MINI_HIDDEN); - } - } - - function exitMiniShowing() { - if (miniScaleTimer) { - Script.clearTimeout(miniScaleTimer); - miniScaleTimer = null; - } - } - - function updateMiniVisible() { + function checkMiniVisibility() { var showLeft, showRight; - // Hide mini tablet if tablet proper has been displayed by other means. - if (HMD.showTablet) { - setState(MINI_HIDDEN); - return; - } - - // Check that the mini tablet should still be visible and if so then ensure it's on the hand that the camera is + // Check that the mini tablet should still be visible and if so then ensure it's on the hand that the camera is // gazing at. showLeft = shouldShowMini(LEFT_HAND); showRight = shouldShowMini(RIGHT_HAND); @@ -790,8 +769,47 @@ setState(MINI_HIDING); } } else { - setState(MINI_HIDING); + if (grabbedItem === null || grabbingHand !== miniHand) { + setState(MINI_HIDING); + } else { + setState(MINI_HIDDEN); + } } + } + + function enterMiniShowing(hand) { + miniHand = hand; + ui.show(miniHand); + miniScaleStart = Date.now(); + miniScaleTimer = Script.setTimeout(scaleMiniUp, MINI_SCALE_TIMEOUT); + } + + function updateMiniShowing() { + // Hide mini tablet if tablet proper has been displayed by other means. + if (HMD.showTablet) { + setState(MINI_HIDDEN); + } + + // Hide mini tablet if it should no longer be visible. + checkMiniVisibility(); + } + + function exitMiniShowing() { + if (miniScaleTimer) { + Script.clearTimeout(miniScaleTimer); + miniScaleTimer = null; + } + } + + function updateMiniVisible() { + // Hide mini tablet if tablet proper has been displayed by other means. + if (HMD.showTablet) { + setState(MINI_HIDDEN); + return; + } + + // Hide mini tablet if it should no longer be visible. + checkMiniVisibility(); // If state hasn't changed, update mini tablet rotation. if (miniState === MINI_VISIBLE) { @@ -973,6 +991,21 @@ return; } + // Track grabbed state and item. + switch (message.action) { + case "grab": + grabbingHand = HAND_NAMES.indexOf(message.joint); + grabbedItem = message.grabbedEntity; + break; + case "release": + grabbingHand = NO_HAND; + grabbedItem = null; + break; + default: + error("Unexpected grab message!"); + return; + } + if (message.grabbedEntity !== HMD.tabletID && message.grabbedEntity !== ui.getMiniTabletID()) { return; } diff --git a/scripts/system/redirectOverlays.js b/scripts/system/redirectOverlays.js index b1180e0cd0..bb537bee0e 100644 --- a/scripts/system/redirectOverlays.js +++ b/scripts/system/redirectOverlays.js @@ -5,14 +5,20 @@ "Oops! Protocol version mismatch.", "Oops! Not authorized to join domain.", "Oops! Connection timed out.", + "Oops! The domain is full.", "Oops! Something went wrong." ]; var PROTOCOL_VERSION_MISMATCH = 1; var NOT_AUTHORIZED = 3; + var DOMAIN_FULL = 4; var TIMEOUT = 5; - var hardRefusalErrors = [PROTOCOL_VERSION_MISMATCH, - NOT_AUTHORIZED, TIMEOUT]; + var hardRefusalErrors = [ + PROTOCOL_VERSION_MISMATCH, + NOT_AUTHORIZED, + TIMEOUT, + DOMAIN_FULL + ]; var timer = null; var isErrorState = false; @@ -26,7 +32,7 @@ return ERROR_MESSAGE_MAP[errorMessageMapIndex]; } else { // some other text. - return ERROR_MESSAGE_MAP[4]; + return ERROR_MESSAGE_MAP[ERROR_MESSAGE_MAP.length - 1]; } }; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 09f3e5fbfe..ed6ff80398 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -44,7 +44,10 @@ try { } function removeFromStoryIDsToMaybeDelete(story_id) { - storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); + story_id = parseInt(story_id); + if (storyIDsToMaybeDelete.indexOf(story_id) > -1) { + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); + } print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); } @@ -258,6 +261,7 @@ function onMessage(message) { } break; case 'removeFromStoryIDsToMaybeDelete': + console.log("Facebook OR Twitter button clicked for story_id " + message.story_id); removeFromStoryIDsToMaybeDelete(message.story_id); break; default: @@ -333,11 +337,13 @@ function fillImageDataFromPrevious() { var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); var previousAnimatedSnapBlastingDisabled = Settings.getValue("previousAnimatedSnapBlastingDisabled"); var previousAnimatedSnapHifiSharingDisabled = Settings.getValue("previousAnimatedSnapHifiSharingDisabled"); + snapshotOptions = { containsGif: previousAnimatedSnapPath !== "", processingGif: false, shouldUpload: false, - canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID"), + canBlast: snapshotDomainID === Settings.getValue("previousSnapshotDomainID") && + snapshotDomainID === location.domainID, isLoggedIn: isLoggedIn }; imageData = []; @@ -369,7 +375,8 @@ function snapshotUploaded(isError, reply) { isGif = fileExtensionMatches(imageURL, "gif"), ignoreGifSnapshotData = false, ignoreStillSnapshotData = false; - storyIDsToMaybeDelete.push(storyID); + storyIDsToMaybeDelete.push(parseInt(storyID)); + print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); if (isGif) { if (mostRecentGifSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { ignoreGifSnapshotData = true; diff --git a/server-console/src/main.js b/server-console/src/main.js index c26938745b..5c7913d775 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -338,13 +338,15 @@ const HifiNotificationType = hfNotifications.NotificationType; var pendingNotifications = {} var notificationState = NotificationState.UNNOTIFIED; -function setNotificationState (notificationType, pending = true) { - pendingNotifications[notificationType] = pending; - notificationState = NotificationState.UNNOTIFIED; - for (var key in pendingNotifications) { - if (pendingNotifications[key]) { - notificationState = NotificationState.NOTIFIED; - break; +function setNotificationState (notificationType, pending = undefined) { + if (pending !== undefined) { + pendingNotifications[notificationType] = pending; + notificationState = NotificationState.UNNOTIFIED; + for (var key in pendingNotifications) { + if (pendingNotifications[key]) { + notificationState = NotificationState.NOTIFIED; + break; + } } } updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); @@ -568,7 +570,42 @@ function updateLabels(serverState) { labels.people.icon = pendingNotifications[HifiNotificationType.PEOPLE] ? menuNotificationIcon : null; labels.wallet.icon = pendingNotifications[HifiNotificationType.WALLET] ? menuNotificationIcon : null; labels.marketplace.icon = pendingNotifications[HifiNotificationType.MARKETPLACE] ? menuNotificationIcon : null; - + var onlineUsers = trayNotifications.getOnlineUsers(); + delete labels.people.submenu; + if (onlineUsers) { + for (var name in onlineUsers) { + if(labels.people.submenu == undefined) { + labels.people.submenu = []; + } + labels.people.submenu.push({ + label: name, + enabled: (onlineUsers[name].location != undefined), + click: function (item) { + setNotificationState(HifiNotificationType.PEOPLE, false); + if(onlineUsers[item.label] && onlineUsers[item.label].location) { + StartInterface("hifi://" + onlineUsers[item.label].location.root.name + onlineUsers[item.label].location.path); + } + } + }); + } + } + var currentStories = trayNotifications.getCurrentStories(); + delete labels.goto.submenu; + if (currentStories) { + for (var location in currentStories) { + if(labels.goto.submenu == undefined) { + labels.goto.submenu = []; + } + labels.goto.submenu.push({ + label: "event in " + location, + location: location, + click: function (item) { + setNotificationState(HifiNotificationType.GOTO, false); + StartInterface("hifi://" + item.location + currentStories[item.location].path); + } + }); + } + } } function updateTrayMenu(serverState) { @@ -919,6 +956,8 @@ app.on('ready', function() { trayNotifications.startPolling(); } updateTrayMenu(ProcessGroupStates.STOPPED); - - maybeInstallDefaultContentSet(onContentLoaded); + + if (isServerInstalled()) { + maybeInstallDefaultContentSet(onContentLoaded); + } }); diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index 464d268c5e..b1f337bbc3 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -73,11 +73,17 @@ HifiNotification.prototype = { text = this.data + " of your connections are online." } message = "Click to open PEOPLE."; - url="hifiapp:PEOPLE" + url="hifiapp:PEOPLE"; } else { - text = this.data.username + " is available in " + this.data.location.root.name + "."; - message = "Click to join them."; - url="hifi://" + this.data.location.root.name + this.data.location.path; + if (this.data.location) { + text = this.data.username + " is available in " + this.data.location.root.name + "."; + message = "Click to join them."; + url="hifi://" + this.data.location.root.name + this.data.location.path; + } else { + text = this.data.username + " is online."; + message = "Click to open PEOPLE."; + url="hifiapp:PEOPLE"; + } } break; @@ -136,7 +142,8 @@ HifiNotification.prototype = { function HifiNotifications(config, menuNotificationCallback) { this.config = config; this.menuNotificationCallback = menuNotificationCallback; - this.onlineUsers = new Set([]); + this.onlineUsers = {}; + this.currentStories = {}; this.storiesSince = new Date(this.config.get("storiesNotifySince", "1970-01-01T00:00:00.000Z")); this.peopleSince = new Date(this.config.get("peopleNotifySince", "1970-01-01T00:00:00.000Z")); this.walletSince = new Date(this.config.get("walletNotifySince", "1970-01-01T00:00:00.000Z")); @@ -213,6 +220,12 @@ HifiNotifications.prototype = { clearInterval(this.marketplacePollTimer); } }, + getOnlineUsers: function () { + return this.onlineUsers; + }, + getCurrentStories: function () { + return this.currentStories; + }, _showNotification: function () { var _this = this; @@ -225,7 +238,7 @@ HifiNotifications.prototype = { // previous notification immediately when a // new one is submitted _this.pendingNotifications.shift(); - if(_this.pendingNotifications.length > 0) { + if (_this.pendingNotifications.length > 0) { _this._showNotification(); } }); @@ -289,7 +302,6 @@ HifiNotifications.prototype = { finished(false); return; } - console.log(content); if (!content.total_entries) { finished(true, token); return; @@ -313,7 +325,6 @@ HifiNotifications.prototype = { notifyData = content.data.updates; break; } - notifyData.forEach(function (notifyDataEntry) { _this._addNotification(new HifiNotification(notifyType, notifyDataEntry)); }); @@ -346,7 +357,7 @@ HifiNotifications.prototype = { 'include_actions=announcement', 'restriction=open,hifi', 'require_online=true', - 'per_page=1' + 'per_page=' + MAX_NOTIFICATION_ITEMS ]; var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); // call a second time to determine if there are no more stories and we should @@ -357,7 +368,34 @@ HifiNotifications.prototype = { 'bearer': token } }, function (error, data) { - _this._pollToDisableHighlight(NotificationType.GOTO, error, data); + if (error || !data.body) { + console.log("Error: unable to get " + url); + finished(false); + return; + } + var content = JSON.parse(data.body); + if (!content || content.status != 'success') { + console.log("Error: unable to get " + url); + finished(false); + return; + } + + if (!content.total_entries) { + finished(true, token); + return; + } + if (!content.total_entries) { + _this.menuNotificationCallback(NotificationType.GOTO, false); + } + _this.currentStories = {}; + content.user_stories.forEach(function (story) { + // only show a single instance of each story location + // in the menu. This may cause issues with domains + // where the story locations are significantly different + // for each story. + _this.currentStories[story.place_name] = story; + }); + _this.menuNotificationCallback(NotificationType.GOTO); }); } }); @@ -400,20 +438,19 @@ HifiNotifications.prototype = { console.log("Error: unable to get " + url); return false; } - console.log(content); if (!content.total_entries) { _this.menuNotificationCallback(NotificationType.PEOPLE, false); - _this.onlineUsers = new Set([]); + _this.onlineUsers = {}; return; } - var currentUsers = new Set([]); + var currentUsers = {}; var newUsers = new Set([]); content.data.users.forEach(function (user) { - currentUsers.add(user.username); - if (!_this.onlineUsers.has(user.username)) { + currentUsers[user.username] = user; + if (!(user.username in _this.onlineUsers)) { newUsers.add(user); - _this.onlineUsers.add(user.username); + _this.onlineUsers[user.username] = user; } }); _this.onlineUsers = currentUsers;