From 29af3b16126690d6b2b678dd98b682031fefc8ff Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 Feb 2019 10:47:42 -0800 Subject: [PATCH] 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; }