diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 753b9c5a81..997407885b 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 wearablesFrozen; 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 === 'wearablesFrozenChanged') { + wearablesFrozen = message.wearablesFrozen; } } @@ -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: wearablesFrozen ? hifi.glyphs.lock : hifi.glyphs.unlock; + + onClicked: { + emitSendToScript({'method' : 'toggleWearablesFrozen'}); + } + } } Rectangle { 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/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..b100b33dc8 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 @@ -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 992ee5db96..839c4ed1d9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -372,13 +372,6 @@ 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()); - } - } } else { undeleted.push_back(id); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b4683d6032..974fae2034 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -180,7 +180,6 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - virtual bool isMyAvatar() const override { return false; } 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; 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())); } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index fb61b914a3..6439d30023 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,6 @@ (function() { // BEGIN LOCAL_SCOPE -var request = Script.require('request').request; var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; Script.include("/~/system/libraries/controllers.js"); @@ -22,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); @@ -44,7 +44,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 +52,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 +68,7 @@ function getMyAvatarSettings() { collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), - } + }; } function updateAvatarWearables(avatar, callback, wearablesOverride) { @@ -76,7 +76,8 @@ 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}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); if(callback) callback(); @@ -101,7 +102,7 @@ var adjustWearables = { this.opened = value; } } -} +}; var currentAvatarWearablesBackup = null; var currentAvatar = null; @@ -112,7 +113,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 +127,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,37 +141,37 @@ 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 }); } } } -var selectedAvatarEntityGrabbable = false; var selectedAvatarEntityID = null; var grabbedAvatarEntityChangeNotifier = null; @@ -178,6 +179,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 getWearablesFrozen() { + var wearablesFrozen = true; + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + if (isGrabbable(wearable.id)) { + wearablesFrozen = false; + } + }); + + return wearablesFrozen; +} + +function freezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, false); + }); +} + +function unfreezeWearables() { + 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 +229,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } } - sendToQml(message) + sendToQml(message); break; case 'selectAvatar': Entities.addingWearable.disconnect(onAddingWearable); @@ -209,6 +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' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -228,11 +257,12 @@ 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(); adjustWearables.setOpened(true); + unfreezeWearables(); Entities.mousePressOnEntity.connect(onSelectedEntity); Messages.subscribe('Hifi-Object-Manipulation'); @@ -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 'toggleWearablesFrozen': + var wearablesFrozen = getWearablesFrozen(); + wearablesFrozen = !wearablesFrozen; + if (wearablesFrozen) { + freezeWearables(); + } else { + unfreezeWearables(); + } + sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen}); break; default: print('Unrecognized message from AvatarApp.qml'); @@ -366,9 +406,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.grabbable != grabbable) { + var editProps = { grab: { grabbable: grabbable }}; + Entities.editEntity(entityID, editProps); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } } @@ -378,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; } @@ -398,7 +429,7 @@ function ensureWearableSelected(entityID) { function isEntityBeingWorn(entityID) { return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; -}; +} function onSelectedEntity(entityID, pointerEvent) { if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) @@ -413,12 +444,14 @@ function onAddingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function onDeletingWearable(entityID) { updateAvatarWearables(currentAvatar, function() { sendToQml({'method' : 'updateAvatarInBookmarks'}); }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } function handleWearableMessages(channel, message, sender) { @@ -435,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(); } } } @@ -481,8 +519,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 +639,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' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); } } 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; }