From 29af3b16126690d6b2b678dd98b682031fefc8ff Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Feb 2019 10:47:42 -0800 Subject: [PATCH 1/8] add a button to Avatar panel to lock or unlock wearables. allow grabbing / adjusting others' wearables if they are unlocked. --- interface/resources/qml/hifi/AvatarApp.qml | 17 +++ .../+android_interface/HifiConstants.qml | 1 + .../resources/qml/stylesUit/HifiConstants.qml | 1 + interface/src/avatar/MyAvatar.h | 2 +- interface/src/avatar/OtherAvatar.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 30 +++++- .../src/avatars-renderer/Avatar.h | 3 +- scripts/system/avatarapp.js | 102 ++++++++++++------ .../libraries/controllerDispatcherUtils.js | 2 - 9 files changed, 116 insertions(+), 44 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 753b9c5a81..a57b5713ed 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -16,6 +16,8 @@ Rectangle { property bool keyboardRaised: false property bool punctuationMode: false + HifiConstants { id: hifi } + HifiControls.Keyboard { id: keyboard z: 1000 @@ -48,6 +50,7 @@ Rectangle { property var jointNames: [] property var currentAvatarSettings; + property bool wearablesLocked; function fetchAvatarModelName(marketId, avatar) { var xmlhttp = new XMLHttpRequest(); @@ -187,6 +190,8 @@ Rectangle { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { adjustWearables.selectWearableByID(message.entityID); + } else if (message.method === 'wearablesLockedChanged') { + wearablesLocked = message.wearablesLocked; } } @@ -507,6 +512,7 @@ Rectangle { } SquareLabel { + id: adjustLabel anchors.right: parent.right anchors.verticalCenter: wearablesLabel.verticalCenter glyphText: "\ue02e" @@ -515,6 +521,17 @@ Rectangle { adjustWearables.open(currentAvatar); } } + + SquareLabel { + anchors.right: adjustLabel.left + anchors.verticalCenter: wearablesLabel.verticalCenter + anchors.rightMargin: 15 + glyphText: wearablesLocked ? hifi.glyphs.lock : hifi.glyphs.unlock; + + onClicked: { + emitSendToScript({'method' : 'toggleWearablesLock'}); + } + } } Rectangle { diff --git a/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml index d5fab57501..995af90f0b 100644 --- a/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml +++ b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml @@ -344,6 +344,7 @@ Item { readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" readonly property string lock: "\ue006" + readonly property string unlock: "\ue039" readonly property string checkmark: "\ue020" readonly property string leftRightArrows: "\ue021" readonly property string hfc: "\ue022" diff --git a/interface/resources/qml/stylesUit/HifiConstants.qml b/interface/resources/qml/stylesUit/HifiConstants.qml index 75f028cd4f..2394b36106 100644 --- a/interface/resources/qml/stylesUit/HifiConstants.qml +++ b/interface/resources/qml/stylesUit/HifiConstants.qml @@ -330,6 +330,7 @@ QtObject { readonly property string stop_square: "\ue01e" readonly property string avatarTPose: "\ue01f" readonly property string lock: "\ue006" + readonly property string unlock: "\ue039" readonly property string checkmark: "\ue020" readonly property string leftRightArrows: "\ue021" readonly property string hfc: "\ue022" diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index edb686a6a6..3f60b9cada 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2170,7 +2170,7 @@ private: bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; } void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; } bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; } - bool isMyAvatar() const override { return true; } + virtual bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; int _skeletonModelChangeCount { 0 }; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 11eb6542c4..2058408596 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -365,7 +365,7 @@ void OtherAvatar::handleChangedAvatarEntityData() { // AVATAR ENTITY UPDATE FLOW // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() // - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, - // - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces + // - ClientTraitsHandler::sendChangedTraitsToMixer() sends the entity bytes to the mixer which relays them to other interfaces // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance() // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 992ee5db96..8cff1cc52a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -372,11 +372,22 @@ bool Avatar::applyGrabChanges() { target->removeGrab(grab); _avatarGrabs.erase(itr); grabAddedOrRemoved = true; - if (isMyAvatar()) { - const EntityItemPointer& entity = std::dynamic_pointer_cast(target); - if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) { - EntityItemProperties properties = entity->getProperties(); - sendPacket(entity->getID()); + const EntityItemPointer& entity = std::dynamic_pointer_cast(target); + if (entity && entity->getEntityHostType() == entity::HostType::AVATAR) { + // grabs are able to move avatar-entities which belong ot other avatars (assuming + // the entities are grabbable, unlocked, etc). Regardless of who released the grab + // on this entity, the entity's owner needs to send off an update. + QUuid entityOwnerID = entity->getOwningAvatarID(); + if (entityOwnerID == getMyAvatarID() || entityOwnerID == AVATAR_SELF_ID) { + bool success; + SpatiallyNestablePointer myAvatarSN = SpatiallyNestable::findByID(entityOwnerID, success); + if (success) { + std::shared_ptr myAvatar = std::dynamic_pointer_cast(myAvatarSN); + if (myAvatar) { + EntityItemProperties properties = entity->getProperties(); + myAvatar->sendPacket(entity->getID(), properties); + } + } } } } else { @@ -2103,3 +2114,12 @@ void Avatar::updateDescendantRenderIDs() { } }); } + +QUuid Avatar::getMyAvatarID() const { + auto nodeList = DependencyManager::get(); + if (nodeList) { + return nodeList->getSessionUUID(); + } else { + return QUuid(); + } +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b4683d6032..2bed13cb12 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -180,7 +180,8 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - virtual bool isMyAvatar() const override { return false; } + QUuid getMyAvatarID() const; + virtual void createOrb() { } enum class LoadingStatus { diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index fb61b914a3..429053bb8b 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -1,6 +1,8 @@ "use strict"; /*jslint vars:true, plusplus:true, forin:true*/ -/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ +/*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print, + AvatarBookmarks, ContextOverlay, AddressManager +*/ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // avatarapp.js @@ -14,7 +16,7 @@ (function() { // BEGIN LOCAL_SCOPE -var request = Script.require('request').request; +// var request = Script.require('request').request; var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; Script.include("/~/system/libraries/controllers.js"); @@ -22,7 +24,7 @@ Script.include("/~/system/libraries/controllers.js"); var ENTRY_AVATAR_URL = "avatarUrl"; var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_SCALE = "avatarScale"; -var ENTRY_VERSION = "version"; +// var ENTRY_VERSION = "version"; function executeLater(callback) { Script.setTimeout(callback, 300); @@ -44,7 +46,7 @@ function getMyAvatarWearables() { } var localRotation = entity.properties.localRotation; - entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation); wearablesArray.push(entity); } @@ -52,7 +54,7 @@ function getMyAvatarWearables() { } function getMyAvatar() { - var avatar = {} + var avatar = {}; avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); @@ -68,7 +70,7 @@ function getMyAvatarSettings() { collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), - } + }; } function updateAvatarWearables(avatar, callback, wearablesOverride) { @@ -76,7 +78,7 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) { var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; - sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); if(callback) callback(); @@ -101,7 +103,7 @@ var adjustWearables = { this.opened = value; } } -} +}; var currentAvatarWearablesBackup = null; var currentAvatar = null; @@ -112,7 +114,7 @@ function onTargetScaleChanged() { if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { currentAvatar.scale = MyAvatar.getAvatarScale(); if(notifyScaleChanged) { - sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}) + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}); } } } @@ -126,7 +128,7 @@ function onSkeletonModelURLChanged() { function onDominantHandChanged(dominantHand) { if(currentAvatarSettings.dominantHand !== dominantHand) { currentAvatarSettings.dominantHand = dominantHand; - sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}) + sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}); } } @@ -140,32 +142,33 @@ function onHmdAvatarAlignmentTypeChanged(type) { function onCollisionsEnabledChanged(enabled) { if(currentAvatarSettings.collisionsEnabled !== enabled) { currentAvatarSettings.collisionsEnabled = enabled; - sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}) + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}); } } function onOtherAvatarsCollisionsEnabledChanged(enabled) { if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; - sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }) + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }); } } function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; - sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}) + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}); } } function onAnimGraphUrlChanged(url) { if (currentAvatarSettings.animGraphUrl !== url) { currentAvatarSettings.animGraphUrl = url; - sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }) + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }); if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); - sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl }) + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', + 'value': currentAvatarSettings.animGraphOverrideUrl }); } } } @@ -178,6 +181,33 @@ var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); +function getWearablesLocked() { + var wearablesLocked = true; + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + if (isGrabbable(wearable.id)) { + wearablesLocked = false; + } + }); + + return wearablesLocked; +} + +function lockWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, false); + }); +} + +function unlockWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, true); + }); +} + + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. switch (message.method) { case 'getAvatars': @@ -201,7 +231,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } } - sendToQml(message) + sendToQml(message); break; case 'selectAvatar': Entities.addingWearable.disconnect(onAddingWearable); @@ -228,7 +258,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); } - sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}) + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}); break; case 'adjustWearablesOpened': currentAvatarWearablesBackup = getMyAvatarWearables(); @@ -305,11 +335,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); if(currentAvatarURL !== message.avatarURL) { MyAvatar.useFullAvatarURL(message.avatarURL); - sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}) + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}); } break; case 'navigate': - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system") + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if(message.url.indexOf('app://') === 0) { if (message.url === 'app://marketplace') { tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -345,7 +375,17 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); - settings = getMyAvatarSettings(); + currentAvatarSettings = getMyAvatarSettings(); + break; + case 'toggleWearablesLock': + var wearablesLocked = getWearablesLocked(); + wearablesLocked = !wearablesLocked; + if (wearablesLocked) { + lockWearables(); + } else { + unlockWearables(); + } + sendToQml({'method' : 'wearablesLockedChanged', 'wearablesLocked' : wearablesLocked}); break; default: print('Unrecognized message from AvatarApp.qml'); @@ -398,7 +438,7 @@ function ensureWearableSelected(entityID) { function isEntityBeingWorn(entityID) { return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; -}; +} function onSelectedEntity(entityID, pointerEvent) { if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) @@ -445,14 +485,14 @@ function handleWearableMessages(channel, message, sender) { // for some reasons Entities.getEntityProperties returns more than was asked.. var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); - var properties = {} + var properties = {}; propertyNames.forEach(function(propertyName) { properties[propertyName] = entityProperties[propertyName]; - }) + }); properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); - sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true}) + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); }, 1000); } else if(parsedMessage.action === 'release') { @@ -481,8 +521,8 @@ function onBookmarkDeleted(bookmarkName) { function onBookmarkAdded(bookmarkName) { var bookmark = AvatarBookmarks.getBookmark(bookmarkName); bookmark.avatarEntites.forEach(function(avatarEntity) { - avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation) - }) + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); } @@ -601,14 +641,8 @@ function onTabletScreenChanged(type, url) { onAvatarAppScreen = onAvatarAppScreenNow; if(onAvatarAppScreenNow) { - var message = { - 'method' : 'initialize', - 'data' : { - 'jointNames' : MyAvatar.getJointNames() - } - }; - - sendToQml(message) + sendToQml({ method : 'initialize', data : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); } } diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 5cb95f625d..51645e5502 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -341,8 +341,6 @@ entityIsGrabbable = function (eigProps) { var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || eigProps.locked || - isAnothersAvatarEntity(eigProps) || - isAnothersChildEntity(eigProps) || FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { return false; } From 4fe94a4b32404ab0e096d2274fa53046fcbcb7a4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 21 Mar 2019 14:16:18 -0700 Subject: [PATCH 2/8] when new wearables are added or removed, rerun getWearablesLocked() and update UI --- scripts/system/avatarapp.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 429053bb8b..eb44bc7fbd 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -79,6 +79,7 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) { avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); if(callback) callback(); @@ -239,6 +240,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See AvatarBookmarks.loadBookmark(message.name); Entities.addingWearable.connect(onAddingWearable); Entities.deletingWearable.connect(onDeletingWearable); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -406,9 +408,11 @@ function isGrabbable(entityID) { } function setGrabbable(entityID, grabbable) { - var properties = Entities.getEntityProperties(entityID, ['avatarEntity']); - if (properties.avatarEntity) { - Entities.editEntity(entityID, { grab: { grabbable: grabbable }}); + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity && properties.grab.grabable != grabbable) { + var editProps = { grab: { grabbable: grabbable }}; + Entities.editEntity(entityID, editProps); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); } } @@ -453,12 +457,14 @@ function onAddingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); } function onDeletingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); } function handleWearableMessages(channel, message, sender) { From fa36f1214530252ad86af39da0bf2473b27822a1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 21 Mar 2019 16:12:38 -0700 Subject: [PATCH 3/8] lock wearables when adjust-wearables page is opened --- .../src/avatars-renderer/Avatar.cpp | 3 +- scripts/system/avatarapp.js | 31 ++++++------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 8cff1cc52a..b0a8875cbc 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -384,8 +384,7 @@ bool Avatar::applyGrabChanges() { if (success) { std::shared_ptr myAvatar = std::dynamic_pointer_cast(myAvatarSN); if (myAvatar) { - EntityItemProperties properties = entity->getProperties(); - myAvatar->sendPacket(entity->getID(), properties); + myAvatar->sendPacket(entity->getID()); } } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index eb44bc7fbd..ee337694a2 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -16,7 +16,6 @@ (function() { // BEGIN LOCAL_SCOPE -// var request = Script.require('request').request; var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; Script.include("/~/system/libraries/controllers.js"); @@ -24,7 +23,6 @@ Script.include("/~/system/libraries/controllers.js"); var ENTRY_AVATAR_URL = "avatarUrl"; var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_SCALE = "avatarScale"; -// var ENTRY_VERSION = "version"; function executeLater(callback) { Script.setTimeout(callback, 300); @@ -79,7 +77,7 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) { avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); if(callback) callback(); @@ -174,7 +172,6 @@ function onAnimGraphUrlChanged(url) { } } -var selectedAvatarEntityGrabbable = false; var selectedAvatarEntityID = null; var grabbedAvatarEntityChangeNotifier = null; @@ -240,7 +237,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See AvatarBookmarks.loadBookmark(message.name); Entities.addingWearable.connect(onAddingWearable); Entities.deletingWearable.connect(onDeletingWearable); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -265,6 +262,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'adjustWearablesOpened': currentAvatarWearablesBackup = getMyAvatarWearables(); adjustWearables.setOpened(true); + lockWearables(); Entities.mousePressOnEntity.connect(onSelectedEntity); Messages.subscribe('Hifi-Object-Manipulation'); @@ -409,10 +407,10 @@ function isGrabbable(entityID) { function setGrabbable(entityID, grabbable) { var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); - if (properties.avatarEntity && properties.grab.grabable != grabbable) { + if (properties.avatarEntity && properties.grab.grabbable != grabbable) { var editProps = { grab: { grabbable: grabbable }}; Entities.editEntity(entityID, editProps); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); } } @@ -422,18 +420,7 @@ function ensureWearableSelected(entityID) { Script.clearInterval(grabbedAvatarEntityChangeNotifier); grabbedAvatarEntityChangeNotifier = null; } - - if(selectedAvatarEntityID !== null) { - setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable); - } - selectedAvatarEntityID = entityID; - selectedAvatarEntityGrabbable = isGrabbable(entityID); - - if(selectedAvatarEntityID !== null) { - setGrabbable(selectedAvatarEntityID, true); - } - return true; } @@ -457,14 +444,14 @@ function onAddingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); } function onDeletingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); } function handleWearableMessages(channel, message, sender) { @@ -647,8 +634,8 @@ function onTabletScreenChanged(type, url) { onAvatarAppScreen = onAvatarAppScreenNow; if(onAvatarAppScreenNow) { - sendToQml({ method : 'initialize', data : { jointNames : MyAvatar.getJointNames() }}); - sendToQml({ method : 'wearablesLockedChanged', wearablesLocked : getWearablesLocked()}); + sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); } } From 5695c1580941e94eae37cb28b9afbed3e1bd8e70 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Mar 2019 16:55:21 -0700 Subject: [PATCH 4/8] avoid a deadlock when code invoked by onAddingEntity or onDeletingEntity runs more code that locks the entity tree --- libraries/entities/src/EntityScriptingInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index aa4b3902c2..ca914731b5 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { if (entity->isWearable()) { - emit addingWearable(entity->getEntityItemID()); + QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID())); } } void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { if (entity->isWearable()) { - emit deletingWearable(entity->getEntityItemID()); + QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID())); } } From e6c279ee5bd2efe83c931a5621fb8aa9904da3c6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 25 Mar 2019 16:58:29 -0700 Subject: [PATCH 5/8] unlock, rather than lock entities when adjust-attachments window is open, because the windows says you can adjust with hand-controllers --- scripts/system/avatarapp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index ee337694a2..65b422f1ab 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -262,7 +262,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'adjustWearablesOpened': currentAvatarWearablesBackup = getMyAvatarWearables(); adjustWearables.setOpened(true); - lockWearables(); + unlockWearables(); Entities.mousePressOnEntity.connect(onSelectedEntity); Messages.subscribe('Hifi-Object-Manipulation'); From d25d290394c34a4262a8e58d15e88e2dea462a2c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 25 Mar 2019 17:06:24 -0700 Subject: [PATCH 6/8] refer to ungrabbable wearables as 'frozen' rather than locked, because locked is such an overloaded term --- interface/resources/qml/hifi/AvatarApp.qml | 10 +++--- scripts/system/avatarapp.js | 40 +++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index a57b5713ed..997407885b 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -50,7 +50,7 @@ Rectangle { property var jointNames: [] property var currentAvatarSettings; - property bool wearablesLocked; + property bool wearablesFrozen; function fetchAvatarModelName(marketId, avatar) { var xmlhttp = new XMLHttpRequest(); @@ -190,8 +190,8 @@ Rectangle { updateCurrentAvatarInBookmarks(currentAvatar); } else if (message.method === 'selectAvatarEntity') { adjustWearables.selectWearableByID(message.entityID); - } else if (message.method === 'wearablesLockedChanged') { - wearablesLocked = message.wearablesLocked; + } else if (message.method === 'wearablesFrozenChanged') { + wearablesFrozen = message.wearablesFrozen; } } @@ -526,10 +526,10 @@ Rectangle { anchors.right: adjustLabel.left anchors.verticalCenter: wearablesLabel.verticalCenter anchors.rightMargin: 15 - glyphText: wearablesLocked ? hifi.glyphs.lock : hifi.glyphs.unlock; + glyphText: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock; onClicked: { - emitSendToScript({'method' : 'toggleWearablesLock'}); + emitSendToScript({'method' : 'toggleWearablesFrozen'}); } } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 65b422f1ab..509497669b 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -77,7 +77,7 @@ function updateAvatarWearables(avatar, callback, wearablesOverride) { avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); if(callback) callback(); @@ -179,26 +179,26 @@ var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); -function getWearablesLocked() { - var wearablesLocked = true; +function getWearablesFrozen() { + var wearablesFrozen = true; var wearablesArray = getMyAvatarWearables(); wearablesArray.forEach(function(wearable) { if (isGrabbable(wearable.id)) { - wearablesLocked = false; + wearablesFrozen = false; } }); - return wearablesLocked; + return wearablesFrozen; } -function lockWearables() { +function freezeWearables() { var wearablesArray = getMyAvatarWearables(); wearablesArray.forEach(function(wearable) { setGrabbable(wearable.id, false); }); } -function unlockWearables() { +function unfreezeWearables() { var wearablesArray = getMyAvatarWearables(); wearablesArray.forEach(function(wearable) { setGrabbable(wearable.id, true); @@ -237,7 +237,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See AvatarBookmarks.loadBookmark(message.name); Entities.addingWearable.connect(onAddingWearable); Entities.deletingWearable.connect(onDeletingWearable); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -262,7 +262,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'adjustWearablesOpened': currentAvatarWearablesBackup = getMyAvatarWearables(); adjustWearables.setOpened(true); - unlockWearables(); + unfreezeWearables(); Entities.mousePressOnEntity.connect(onSelectedEntity); Messages.subscribe('Hifi-Object-Manipulation'); @@ -377,15 +377,15 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatarSettings = getMyAvatarSettings(); break; - case 'toggleWearablesLock': - var wearablesLocked = getWearablesLocked(); - wearablesLocked = !wearablesLocked; - if (wearablesLocked) { - lockWearables(); + case 'toggleWearablesFrozen': + var wearablesFrozen = getWearablesFrozen(); + wearablesFrozen = !wearablesFrozen; + if (wearablesFrozen) { + freezeWearables(); } else { - unlockWearables(); + unfreezeWearables(); } - sendToQml({'method' : 'wearablesLockedChanged', 'wearablesLocked' : wearablesLocked}); + sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen}); break; default: print('Unrecognized message from AvatarApp.qml'); @@ -410,7 +410,7 @@ function setGrabbable(entityID, grabbable) { if (properties.avatarEntity && properties.grab.grabbable != grabbable) { var editProps = { grab: { grabbable: grabbable }}; Entities.editEntity(entityID, editProps); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } } @@ -444,14 +444,14 @@ function onAddingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function onDeletingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function handleWearableMessages(channel, message, sender) { @@ -635,7 +635,7 @@ function onTabletScreenChanged(type, url) { if(onAvatarAppScreenNow) { sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); - sendToQml({ 'method' : 'wearablesLockedChanged', 'wearablesLocked' : getWearablesLocked()}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } } From e085a00256d8bb1c83e13d481aa3c247d2bd5343 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 26 Mar 2019 09:32:12 -0700 Subject: [PATCH 7/8] cause 'save' button to unghost if an attachment is adjusted via grab while the adjust-wearables page is open --- .../qml/hifi/avatarapp/AdjustWearables.qml | 1 + scripts/system/avatarapp.js | 33 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 136d535b3f..391e4fab37 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -113,6 +113,7 @@ Rectangle { } else if (prop === 'dimensions') { scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x); } + modified = true; } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 509497669b..6439d30023 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -468,30 +468,35 @@ function handleWearableMessages(channel, message, sender) { } var entityID = parsedMessage.grabbedEntity; + + var updateWearable = function() { + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {}; + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }); + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, + 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); + + }; + if(parsedMessage.action === 'grab') { if(selectedAvatarEntityID !== entityID) { ensureWearableSelected(entityID); sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); } - grabbedAvatarEntityChangeNotifier = Script.setInterval(function() { - // for some reasons Entities.getEntityProperties returns more than was asked.. - var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; - var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); - var properties = {}; - - propertyNames.forEach(function(propertyName) { - properties[propertyName] = entityProperties[propertyName]; - }); - - properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); - sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); - - }, 1000); + grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000); } else if(parsedMessage.action === 'release') { if(grabbedAvatarEntityChangeNotifier !== null) { Script.clearInterval(grabbedAvatarEntityChangeNotifier); grabbedAvatarEntityChangeNotifier = null; + updateWearable(); } } } From fb7daa185d63aa7445bf20959ff9ac1df63dee51 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 27 Mar 2019 09:53:55 -0700 Subject: [PATCH 8/8] improved physics for grabbed AvatarEntities --- interface/src/avatar/OtherAvatar.cpp | 12 +++++ .../src/avatars-renderer/Avatar.cpp | 26 ----------- .../src/avatars-renderer/Avatar.h | 2 - libraries/entities/src/EntityItem.cpp | 45 ++++++++++--------- libraries/entities/src/EntityItem.h | 8 ++-- 5 files changed, 41 insertions(+), 52 deletions(-) diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 2058408596..b100b33dc8 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -495,6 +495,18 @@ void OtherAvatar::handleChangedAvatarEntityData() { const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); entity->setParentID(NULL_ID); entity->setParentID(oldParentID); + + if (entity->stillHasMyGrabAction()) { + // For this case: we want to ignore transform+velocities coming from authoritative OtherAvatar + // because the MyAvatar is grabbing and we expect the local grab state + // to have enough information to prevent simulation drift. + // + // Clever readers might realize this could cause problems. For example, + // if an ignored OtherAvagtar were to simultanously grab the object then there would be + // a noticeable discrepancy between participants in the distributed physics simulation, + // however the difference would be stable and would not drift. + properties.clearTransformOrVelocityChanges(); + } if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b0a8875cbc..839c4ed1d9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -372,23 +372,6 @@ bool Avatar::applyGrabChanges() { target->removeGrab(grab); _avatarGrabs.erase(itr); grabAddedOrRemoved = true; - const EntityItemPointer& entity = std::dynamic_pointer_cast(target); - if (entity && entity->getEntityHostType() == entity::HostType::AVATAR) { - // grabs are able to move avatar-entities which belong ot other avatars (assuming - // the entities are grabbable, unlocked, etc). Regardless of who released the grab - // on this entity, the entity's owner needs to send off an update. - QUuid entityOwnerID = entity->getOwningAvatarID(); - if (entityOwnerID == getMyAvatarID() || entityOwnerID == AVATAR_SELF_ID) { - bool success; - SpatiallyNestablePointer myAvatarSN = SpatiallyNestable::findByID(entityOwnerID, success); - if (success) { - std::shared_ptr myAvatar = std::dynamic_pointer_cast(myAvatarSN); - if (myAvatar) { - myAvatar->sendPacket(entity->getID()); - } - } - } - } } else { undeleted.push_back(id); } @@ -2113,12 +2096,3 @@ void Avatar::updateDescendantRenderIDs() { } }); } - -QUuid Avatar::getMyAvatarID() const { - auto nodeList = DependencyManager::get(); - if (nodeList) { - return nodeList->getSessionUUID(); - } else { - return QUuid(); - } -} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 2bed13cb12..974fae2034 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -180,8 +180,6 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - QUuid getMyAvatarID() const; - virtual void createOrb() { } enum class LoadingStatus { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index f0bf13891b..bd4c6e5c71 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -789,8 +789,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto lastEdited = lastEditedFromBufferAdjusted; bool otherOverwrites = overwriteLocalData && !weOwnSimulation; - auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) { - if (stillHasGrabActions()) { + // calculate hasGrab once outside the lambda rather than calling it every time inside + bool hasGrab = stillHasGrabAction(); + auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) { + if (hasGrab) { return false; } bool simulationChanged = lastEdited > updatedTimestamp; @@ -957,12 +959,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // by doing this parsing here... but it's not likely going to fully recover the content. // - if (overwriteLocalData && (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) { + if (overwriteLocalData && + !hasGrab && + (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES))) { // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more // closely match where the entities should be if they'd stepped forward in time to "now". The server // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and // use our simulation helper routine to get a best estimate of where the entity should be. + // + // NOTE: We don't want to do this in the hasGrab case because grabs "know best" + // (e.g. grabs will prevent drift between distributed physics simulations). + // float skipTimeForward = (float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND); // we want to extrapolate the motion forward to compensate for packet travel time, but @@ -1426,7 +1434,7 @@ void EntityItem::getTransformAndVelocityProperties(EntityItemProperties& propert void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) { uint8_t newPriority = glm::max(priority, _scriptSimulationPriority); - if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasGrabActions()) { + if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasMyGrabAction()) { newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; } if (newPriority != _scriptSimulationPriority) { @@ -1439,7 +1447,7 @@ void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) { void EntityItem::clearScriptSimulationPriority() { // DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this // is only ever called from the code that actually handles the dirty flags, and it knows best. - _scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; + _scriptSimulationPriority = stillHasMyGrabAction() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; } void EntityItem::setPendingOwnershipPriority(uint8_t priority) { @@ -2186,7 +2194,7 @@ void EntityItem::enableNoBootstrap() { } void EntityItem::disableNoBootstrap() { - if (!stillHasGrabActions()) { + if (!stillHasMyGrabAction()) { _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar @@ -2272,7 +2280,13 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a return success; } -bool EntityItem::stillHasGrabActions() const { +bool EntityItem::stillHasGrabAction() const { + return !_grabActions.empty(); +} + +// retutrns 'true' if there exists an action that returns 'true' for EntityActionInterface::isMine() +// (e.g. the action belongs to the MyAvatar instance) +bool EntityItem::stillHasMyGrabAction() const { QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); QList::const_iterator i = holdActions.begin(); while (i != holdActions.end()) { @@ -2700,20 +2714,6 @@ void EntityItem::setLastEdited(quint64 lastEdited) { }); } -quint64 EntityItem::getLastBroadcast() const { - quint64 result; - withReadLock([&] { - result = _lastBroadcast; - }); - return result; -} - -void EntityItem::setLastBroadcast(quint64 lastBroadcast) { - withWriteLock([&] { - _lastBroadcast = lastBroadcast; - }); -} - void EntityItem::markAsChangedOnServer() { withWriteLock([&] { _changedOnServer = usecTimestampNow(); @@ -3479,6 +3479,9 @@ void EntityItem::addGrab(GrabPointer grab) { simulation->addDynamic(action); markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); simulation->changeEntity(getThisPointer()); + + // don't forget to set isMine() for locally-created grabs + action->setIsMine(grab->getOwnerID() == Physics::getSessionUUID()); } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index fae871a124..01ed949a0c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -124,8 +124,8 @@ public: { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } /// Last time we sent out an edit packet for this entity - quint64 getLastBroadcast() const; - void setLastBroadcast(quint64 lastBroadcast); + quint64 getLastBroadcast() const { return _lastBroadcast; } + void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; } void markAsChangedOnServer(); quint64 getLastChangedOnServer() const; @@ -562,6 +562,8 @@ public: static void setPrimaryViewFrustumPositionOperator(std::function getPrimaryViewFrustumPositionOperator) { _getPrimaryViewFrustumPositionOperator = getPrimaryViewFrustumPositionOperator; } static glm::vec3 getPrimaryViewFrustumPosition() { return _getPrimaryViewFrustumPositionOperator(); } + bool stillHasMyGrabAction() const; + signals: void requestRenderUpdate(); void spaceUpdate(std::pair data); @@ -574,7 +576,7 @@ protected: void setSimulated(bool simulated) { _simulated = simulated; } const QByteArray getDynamicDataInternal() const; - bool stillHasGrabActions() const; + bool stillHasGrabAction() const; void setDynamicDataInternal(QByteArray dynamicData); virtual void dimensionsChanged() override;