diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 5e36d8beaf..f215fd4448 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -146,7 +146,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, break; } - if (traitType == AvatarTraits::AvatarEntity) { + if (traitType == AvatarTraits::AvatarEntity || + traitType == AvatarTraits::Grab) { auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID); if (packetTraitVersion > instanceVersionRef) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 182ff77098..a38e33714b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -208,6 +208,8 @@ #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" +#include "avatar/GrabManager.h" + #include #include #include @@ -919,6 +921,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -6085,6 +6088,9 @@ void Application::update(float deltaTime) { updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateDialogs(deltaTime); // update various stats dialogs if present + auto grabManager = DependencyManager::get(); + grabManager->simulateGrabs(); + QSharedPointer avatarManager = DependencyManager::get(); { diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 53074ac4ba..dd99907b8e 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -58,6 +58,16 @@ AvatarActionHold::~AvatarActionHold() { #endif } +void AvatarActionHold::removeFromOwner() { + auto avatarManager = DependencyManager::get(); + if (avatarManager) { + auto myAvatar = avatarManager->getMyAvatar(); + if (myAvatar) { + myAvatar->removeHoldAction(this); + } + } +} + bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) { auto myAvatar = DependencyManager::get()->getMyAvatar(); MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr; @@ -143,7 +153,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: ownerEntity->setTransitingWithAvatar(_isTransitingWithAvatar); } } - + if (holdingAvatar->isMyAvatar()) { std::shared_ptr myAvatar = avatarManager->getMyAvatar(); @@ -226,7 +236,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: } rotation = palmRotation * _relativeRotation; - position = palmPosition + rotation * _relativePosition; + position = palmPosition + palmRotation * _relativePosition; // update linearVelocity based on offset via _relativePosition; linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition); @@ -369,8 +379,12 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { hand = _hand; } - auto myAvatar = DependencyManager::get()->getMyAvatar(); - holderID = myAvatar->getSessionUUID(); + ok = true; + holderID = EntityDynamicInterface::extractStringArgument("hold", arguments, "holderID", ok, false); + if (!ok) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + holderID = myAvatar->getSessionUUID(); + } ok = true; kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false); @@ -417,13 +431,13 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { _kinematicSetVelocity = kinematicSetVelocity; _ignoreIK = ignoreIK; _active = true; - + auto myAvatar = DependencyManager::get()->getMyAvatar(); auto ownerEntity = _ownerEntity.lock(); if (ownerEntity) { ownerEntity->setDynamicDataDirty(true); - ownerEntity->setDynamicDataNeedsTransmit(true); + ownerEntity->setDynamicDataNeedsTransmit(true); ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive()); } }); diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index ddc5808d67..4583300012 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -26,6 +26,8 @@ public: AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity); virtual ~AvatarActionHold(); + virtual void removeFromOwner() override; + virtual bool updateArguments(QVariantMap arguments) override; virtual QVariantMap getArguments() override; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 7ca18ca258..21e59c06d8 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -887,3 +887,13 @@ QVariantMap AvatarManager::getPalData(const QStringList& specificAvatarIdentifie doc.insert("data", palData); return doc.toVariantMap(); } + +void AvatarManager::accumulateGrabPositions(std::map& grabAccumulators) { + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); + while (itr != avatarMap.end()) { + const auto& avatar = std::static_pointer_cast(*itr); + avatar->accumulateGrabPositions(grabAccumulators); + itr++; + } +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 75dbbc7abb..359af8e361 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -198,6 +198,8 @@ public: void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction); void removeDeadAvatarEntities(const SetOfEntities& deadEntities); + void accumulateGrabPositions(std::map& grabAccumulators); + public slots: /**jsdoc * @function AvatarManager.updateAvatarRenderStatus @@ -215,7 +217,7 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - + // called only from the AvatarHashMap thread - cannot be called while this thread holds the // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree // frequently grabs a read lock on the hash to get a given avatar by ID diff --git a/interface/src/avatar/GrabManager.cpp b/interface/src/avatar/GrabManager.cpp new file mode 100644 index 0000000000..5312bcc4eb --- /dev/null +++ b/interface/src/avatar/GrabManager.cpp @@ -0,0 +1,39 @@ +// +// GrabManager.cpp +// interface/src/avatar/ +// +// Created by Seth Alves on 2018-12-4. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "GrabManager.h" + +void GrabManager::simulateGrabs() { + QSharedPointer avatarManager = DependencyManager::get(); + + // Update grabbed objects + auto entityTreeRenderer = DependencyManager::get(); + auto entityTree = entityTreeRenderer->getTree(); + entityTree->withReadLock([&] { + PROFILE_RANGE(simulation, "Grabs"); + + std::map grabAccumulators; + avatarManager->accumulateGrabPositions(grabAccumulators); + + for (auto& accumulatedLocation : grabAccumulators) { + QUuid grabbedThingID = accumulatedLocation.first; + GrabLocationAccumulator& acc = accumulatedLocation.second; + bool success; + SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success); + if (success && grabbedThing) { + glm::vec3 finalPosition = acc.finalizePosition(); + glm::quat finalOrientation = acc.finalizeOrientation(); + grabbedThing->setWorldTransform(finalPosition, finalOrientation); + } + } + }); +} diff --git a/interface/src/avatar/GrabManager.h b/interface/src/avatar/GrabManager.h new file mode 100644 index 0000000000..8834f95aa1 --- /dev/null +++ b/interface/src/avatar/GrabManager.h @@ -0,0 +1,23 @@ +// +// GrabManager.h +// interface/src/avatar/ +// +// Created by Seth Alves on 2018-12-4. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include "AvatarManager.h" + +class GrabManager : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void simulateGrabs(); + +}; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 46c1f3dda1..1f63a904bb 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -746,7 +746,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); animateScaleChanges(deltaTime); - + setFlyingEnabled(getFlyingEnabled()); if (_cauterizationNeedsUpdate) { @@ -820,6 +820,7 @@ void MyAvatar::simulate(float deltaTime) { // and all of its joints, now update our attachements. Avatar::simulateAttachments(deltaTime); relayJointDataToChildren(); + updateGrabs(); if (!_skeletonModel->hasSkeleton()) { // All the simulation that can be done has been done @@ -874,47 +875,12 @@ void MyAvatar::simulate(float deltaTime) { zoneAllowsFlying = zone->getFlyingAllowed(); collisionlessAllowed = zone->getGhostingAllowed(); } - auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); - MovingEntitiesOperator moveOperator; + bool force = false; + bool iShouldTellServer = true; forEachDescendant([&](SpatiallyNestablePointer object) { - // if the queryBox has changed, tell the entity-server - if (object->getNestableType() == NestableType::Entity && object->updateQueryAACube()) { - EntityItemPointer entity = std::static_pointer_cast(object); - bool success; - AACube newCube = entity->getQueryAACube(success); - if (success) { - moveOperator.addEntityToMoveList(entity, newCube); - } - // send an edit packet to update the entity-server about the queryAABox - if (packetSender && entity->isDomainEntity()) { - EntityItemProperties properties = entity->getProperties(); - properties.setQueryAACubeDirty(); - properties.setLastEdited(now); - - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, - entity->getID(), properties); - entity->setLastBroadcast(usecTimestampNow()); - - entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - EntityItemPointer entityDescendant = std::dynamic_pointer_cast(descendant); - if (entityDescendant && entityDescendant->isDomainEntity() && descendant->updateQueryAACube()) { - EntityItemProperties descendantProperties; - descendantProperties.setQueryAACube(descendant->getQueryAACube()); - descendantProperties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, - entityDescendant->getID(), descendantProperties); - entityDescendant->setLastBroadcast(now); // for debug/physics status icons - } - }); - } - } + entityTree->updateEntityQueryAACube(object, packetSender, force, iShouldTellServer); }); - // also update the position of children in our local octree - if (moveOperator.hasMovingEntities()) { - PerformanceTimer perfTimer("recurseTreeWithOperator"); - entityTree->recurseTreeWithOperator(&moveOperator); - } }); bool isPhysicsEnabled = qApp->isPhysicsEnabled(); _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled); @@ -4746,3 +4712,50 @@ SpatialParentTree* MyAvatar::getParentTree() const { EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; return entityTree.get(); } + +const QUuid MyAvatar::grab(const QUuid& targetID, int parentJointIndex, + glm::vec3 positionalOffset, glm::quat rotationalOffset) { + auto grabID = QUuid::createUuid(); + // create a temporary grab object to get grabData + + QString hand = "none"; + if (parentJointIndex == CONTROLLER_RIGHTHAND_INDEX || + parentJointIndex == CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX || + parentJointIndex == FARGRAB_RIGHTHAND_INDEX || + parentJointIndex == getJointIndex("RightHand")) { + hand = "right"; + } else if (parentJointIndex == CONTROLLER_LEFTHAND_INDEX || + parentJointIndex == CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX || + parentJointIndex == FARGRAB_LEFTHAND_INDEX || + parentJointIndex == getJointIndex("LeftHand")) { + hand = "left"; + } + + Grab tmpGrab(DependencyManager::get()->getSessionUUID(), + targetID, parentJointIndex, hand, positionalOffset, rotationalOffset); + QByteArray grabData = tmpGrab.toByteArray(); + bool dataChanged = updateAvatarGrabData(grabID, grabData); + + if (dataChanged && _clientTraitsHandler) { + // indicate that the changed data should be sent to the mixer + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID); + } + + return grabID; +} + +void MyAvatar::releaseGrab(const QUuid& grabID) { + bool tellHandler { false }; + + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabData.remove(grabID)) { + _deletedAvatarGrabs.insert(grabID); + tellHandler = true; + } + }); + + if (tellHandler && _clientTraitsHandler) { + // indicate the deletion of the data to the mixer + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID); + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b2381366bb..92fe6d4f4a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1170,6 +1170,25 @@ public: glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); } + /**jsdoc + * Create a new grab. + * @function MyAvatar.grab + * @param {Uuid} targetID - id of grabbed thing + * @param {number} parentJointIndex - avatar joint being used to grab + * @param {Vec3} offset - target's positional offset from joint + * @param {Quat} rotationalOffset - target's rotational offset from joint + * @returns {Uuid} id of the new grab + */ + Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex, + glm::vec3 positionalOffset, glm::quat rotationalOffset); + + /**jsdoc + * Release (delete) a grab. + * @function MyAvatar.releaseGrab + * @param {Uuid} grabID - id of grabbed thing + */ + Q_INVOKABLE void releaseGrab(const QUuid& grabID); + public slots: /**jsdoc diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 0761cdf597..fdd707c950 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" @@ -481,6 +482,103 @@ void Avatar::removeAvatarEntitiesFromTree() { } } +void Avatar::updateGrabs() { + + // update the Grabs according to any changes in _avatarGrabData + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabDataChanged) { + foreach (auto grabID, _avatarGrabData.keys()) { + AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID); + if (grabItr == _avatarGrabs.end()) { + GrabPointer grab = std::make_shared(); + grab->fromByteArray(_avatarGrabData.value(grabID)); + _avatarGrabs[grabID] = grab; + _changedAvatarGrabs.insert(grabID); + } else { + GrabPointer grab = grabItr.value(); + bool changed = grab->fromByteArray(_avatarGrabData.value(grabID)); + if (changed) { + _changedAvatarGrabs.insert(grabID); + } + } + } + _avatarGrabDataChanged = false; + } + + auto treeRenderer = DependencyManager::get(); + auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr; + auto sessionID = DependencyManager::get()->getSessionUUID(); + + QMutableSetIterator delItr(_deletedAvatarGrabs); + while (delItr.hasNext()) { + QUuid grabID = delItr.next(); + GrabPointer grab = _avatarGrabs[grabID]; + if (!grab) { + delItr.remove(); + continue; + } + + bool success; + SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); + + // only clear this entry from the _deletedAvatarGrabs if we found the entity. + if (success && target) { + bool iShouldTellServer = target->getEditSenderID() == sessionID; + target->removeGrab(grab); + delItr.remove(); + // in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server + // about the final position. + if (entityTree) { + bool force = true; + entityTree->withWriteLock([&] { + entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); + }); + } + } + _avatarGrabs.remove(grabID); + _changedAvatarGrabs.remove(grabID); + } + + QMutableSetIterator changeItr(_changedAvatarGrabs); + while (changeItr.hasNext()) { + QUuid grabID = changeItr.next(); + GrabPointer& grab = _avatarGrabs[grabID]; + + bool success; + SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); + + if (success && target) { + target->addGrab(grab); + // only clear this entry from the _changedAvatarGrabs if we found the entity. + changeItr.remove(); + } + } + }); +} + +void Avatar::accumulateGrabPositions(std::map& grabAccumulators) { + // relay avatar's joint position to grabbed target in a way that allows for averaging + _avatarGrabsLock.withReadLock([&] { + foreach (auto grabID, _avatarGrabs.keys()) { + const GrabPointer& grab = _avatarGrabs.value(grabID); + + if (!grab->getActionID().isNull()) { + continue; // the accumulated value isn't used, in this case. + } + + glm::vec3 jointTranslation = getAbsoluteJointTranslationInObjectFrame(grab->getParentJointIndex()); + glm::quat jointRotation = getAbsoluteJointRotationInObjectFrame(grab->getParentJointIndex()); + glm::mat4 jointMat = createMatFromQuatAndPos(jointRotation, jointTranslation); + glm::mat4 offsetMat = createMatFromQuatAndPos(grab->getRotationalOffset(), grab->getPositionalOffset()); + glm::mat4 avatarMat = getTransform().getMatrix(); + glm::mat4 worldTransform = avatarMat * jointMat * offsetMat; + GrabLocationAccumulator& grabLocationAccumulator = grabAccumulators[grab->getTargetID()]; + grabLocationAccumulator.accumulate(extractTranslation(worldTransform), extractRotation(worldTransform)); + } + }); +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -551,7 +649,7 @@ void Avatar::simulate(float deltaTime, bool inView) { if (!hasParent()) { setLocalPosition(_globalPosition); } - + _simulationRate.increment(); if (inView) { _simulationInViewRate.increment(); @@ -582,6 +680,7 @@ void Avatar::simulate(float deltaTime, bool inView) { head->setScale(getModelScale()); head->simulate(deltaTime); relayJointDataToChildren(); + updateGrabs(); } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. _skeletonModel->simulate(deltaTime, false); @@ -1295,6 +1394,9 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } switch (index) { + case NO_JOINT_INDEX: { + return glm::quat(); + } case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 avatarMatrix = getLocalTransform().getMatrix(); @@ -1344,6 +1446,9 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } switch (index) { + case NO_JOINT_INDEX: { + return glm::vec3(0.0f); + } case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 avatarMatrix = getLocalTransform().getMatrix(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 8f70b12122..a54a74fb93 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -440,6 +440,8 @@ public: std::shared_ptr getTransit() { return std::make_shared(_transit); }; AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config); + void accumulateGrabPositions(std::map& grabAccumulators); + signals: void targetScaleChanged(float targetScale); @@ -542,6 +544,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + void updateGrabs(); void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); @@ -628,6 +631,8 @@ protected: static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); + + AvatarGrabMap _avatarGrabs; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b6c7954f67..ae72725e2e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1893,42 +1893,96 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice return bytesWritten; } + +qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { + qint64 bytesWritten = 0; + + // grab a read lock on the avatar entities and check for entity data for the given ID + QByteArray entityBinaryData; + + _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { + if (_avatarEntityData.contains(traitInstanceID)) { + entityBinaryData = _avatarEntityData[traitInstanceID]; + } + }); + + if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { + qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size() + << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; + return 0; + } + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + + if (!entityBinaryData.isNull()) { + AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); + + bytesWritten += destination.writePrimitive(entityBinarySize); + bytesWritten += destination.write(entityBinaryData); + } else { + bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); + } + + return bytesWritten; +} + + +qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { + qint64 bytesWritten = 0; + + // grab a read lock on the avatar grabs and check for grab data for the given ID + QByteArray grabBinaryData; + + _avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] { + if (_avatarGrabData.contains(traitInstanceID)) { + grabBinaryData = _avatarGrabData[traitInstanceID]; + } + }); + + if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { + qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size() + << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; + return 0; + } + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + + if (!grabBinaryData.isNull()) { + AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size(); + + bytesWritten += destination.writePrimitive(grabBinarySize); + bytesWritten += destination.write(grabBinaryData); + } else { + bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); + } + + return bytesWritten; +} + qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { qint64 bytesWritten = 0; if (traitType == AvatarTraits::AvatarEntity) { - // grab a read lock on the avatar entities and check for entity data for the given ID - QByteArray entityBinaryData; - - _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { - if (_avatarEntityData.contains(traitInstanceID)) { - entityBinaryData = _avatarEntityData[traitInstanceID]; - } - }); - - if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { - qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size() - << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; - return 0; - } - - bytesWritten += destination.writePrimitive(traitType); - - if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } - - bytesWritten += destination.write(traitInstanceID.toRfc4122()); - - if (!entityBinaryData.isNull()) { - AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); - - bytesWritten += destination.writePrimitive(entityBinarySize); - bytesWritten += destination.write(entityBinaryData); - } else { - bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); - } + packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); + } else if (traitType == AvatarTraits::Grab) { + packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); } return bytesWritten; @@ -1940,6 +1994,9 @@ void AvatarData::prepareResetTraitInstances() { foreach (auto entityID, _avatarEntityData.keys()) { _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } + foreach (auto grabID, _avatarGrabData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID); + } }); } } @@ -1956,12 +2013,16 @@ void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { if (traitType == AvatarTraits::AvatarEntity) { updateAvatarEntity(instanceID, traitBinaryData); + } else if (traitType == AvatarTraits::Grab) { + updateAvatarGrabData(instanceID, traitBinaryData); } } void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { if (traitType == AvatarTraits::AvatarEntity) { clearAvatarEntity(instanceID); + } else if (traitType == AvatarTraits::Grab) { + clearAvatarGrabData(instanceID); } } @@ -2909,3 +2970,38 @@ AABox AvatarData::getDefaultBubbleBox() const { bubbleBox.translate(_globalPosition); return bubbleBox; } + +bool AvatarData::updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData) { + bool changed { false }; + _avatarGrabsLock.withWriteLock([&] { + AvatarGrabDataMap::iterator itr = _avatarGrabData.find(grabID); + if (itr == _avatarGrabData.end()) { + // create a new one + if (_avatarGrabData.size() < MAX_NUM_AVATAR_GRABS) { + _avatarGrabData.insert(grabID, grabData); + _avatarGrabDataChanged = true; + changed = true; + } else { + qCWarning(avatars) << "Can't create more grabs on avatar, limit reached."; + } + } else { + // update an existing one + if (itr.value() != grabData) { + itr.value() = grabData; + _avatarGrabDataChanged = true; + changed = true; + } + } + }); + + return changed; +} + +void AvatarData::clearAvatarGrabData(const QUuid& grabID) { + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabData.remove(grabID)) { + _avatarGrabDataChanged = true; + _deletedAvatarGrabs.insert(grabID); + } + }); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 71e79191bc..64b8814149 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -54,15 +54,21 @@ #include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" +#include "Grab.h" #include using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; + using AvatarEntityMap = QMap; using AvatarEntityIDs = QSet; +using AvatarGrabDataMap = QMap; +using AvatarGrabIDs = QSet; +using AvatarGrabMap = QMap; + using AvatarDataSequenceNumber = uint16_t; // avatar motion behaviors @@ -1342,6 +1348,13 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } + qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); + qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); + // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" // Audio Mixer that the replicated avatar is connected to. bool _isReplicated{ false }; @@ -1452,6 +1465,12 @@ protected: AvatarEntityMap _avatarEntityData; bool _avatarEntityDataChanged { false }; + mutable ReadWriteLockable _avatarGrabsLock; + AvatarGrabDataMap _avatarGrabData; + bool _avatarGrabDataChanged { false }; // by network + AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics + AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics + // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; @@ -1511,6 +1530,9 @@ protected: f(index); } + bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData); + void clearAvatarGrabData(const QUuid& grabID); + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; @@ -1614,6 +1636,7 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value); // faux joint indexes (-1 means invalid) +const int NO_JOINT_INDEX = 65535; // -1 const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2 const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3 const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 @@ -1626,5 +1649,6 @@ const int FARGRAB_MOUSE_INDEX = 65526; // -10 const int LOWEST_PSEUDO_JOINT_INDEX = 65526; +const int MAX_NUM_AVATAR_GRABS = 6; #endif // hifi_AvatarData_h diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 5e28515d12..0f4ad0b90f 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -24,6 +24,7 @@ namespace AvatarTraits { SkeletonModelURL, FirstInstancedTrait, AvatarEntity = FirstInstancedTrait, + Grab, TotalTraitTypes }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index a88a8de308..380998321f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "EntitiesRendererLogging.h" #include "RenderableEntityItem.h" @@ -1250,3 +1251,11 @@ void EntityTreeRenderer::onEntityChanged(const EntityItemID& id) { _changedEntities.insert(id); }); } + +EntityEditPacketSender* EntityTreeRenderer::getPacketSender() { + EntityTreePointer tree = getTree(); + EntitySimulationPointer simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + return packetSender; +} diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index d0bd8a97e3..b4f0bda703 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -120,6 +120,8 @@ public: static void setGetAvatarUpOperator(std::function getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; } static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); } + EntityEditPacketSender* getPacketSender(); + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index 6b82e7df73..836dae2057 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -50,6 +50,8 @@ public: const QUuid& getID() const { return _id; } EntityDynamicType getType() const { return _type; } + virtual void removeFromOwner() { } + virtual void remapIDs(QHash& map) = 0; virtual bool isAction() const { return false; } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 94d0024fd5..cad00301a6 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -31,6 +31,8 @@ #include // usecTimestampNow() #include #include +#include +#include #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" @@ -38,6 +40,7 @@ #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" + Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType(); @@ -1647,7 +1650,10 @@ AACube EntityItem::getQueryAACube(bool& success) const { } bool EntityItem::shouldPuffQueryAACube() const { - return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent(); + bool hasGrabs = _grabsLock.resultWithReadLock([&] { + return _grabs.count() > 0; + }); + return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs; } // TODO: get rid of all users of this function... @@ -2455,13 +2461,22 @@ bool EntityItem::shouldSuppressLocationEdits() const { QList EntityItem::getActionsOfType(EntityDynamicType typeToGet) const { QList result; - QHash::const_iterator i = _objectActions.begin(); - while (i != _objectActions.end()) { + for (QHash::const_iterator i = _objectActions.begin(); + i != _objectActions.end(); + i++) { + EntityDynamicPointer action = i.value(); + if (action->getType() == typeToGet && action->isActive()) { + result += action; + } + } + + for (QHash::const_iterator i = _grabActions.begin(); + i != _grabActions.end(); + i++) { EntityDynamicPointer action = i.value(); if (action->getType() == typeToGet && action->isActive()) { result += action; } - i++; } return result; @@ -3281,4 +3296,65 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti bool EntityItem::isWearable() const { return isVisible() && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); -} \ No newline at end of file +} + +void EntityItem::addGrab(GrabPointer grab) { + SpatiallyNestable::addGrab(grab); + + if (getDynamic()) { + EntityTreePointer entityTree = getTree(); + assert(entityTree); + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + assert(simulation); + + auto actionFactory = DependencyManager::get(); + QUuid actionID = QUuid::createUuid(); + + EntityDynamicType dynamicType; + QVariantMap arguments; + int grabParentJointIndex =grab->getParentJointIndex(); + if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX) { + // add a far-grab action + dynamicType = DYNAMIC_TYPE_FAR_GRAB; + arguments["otherID"] = grab->getOwnerID(); + arguments["otherJointIndex"] = grabParentJointIndex; + arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset()); + arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset()); + arguments["linearTimeScale"] = 0.05; + arguments["angularTimeScale"] = 0.05; + } else { + // add a near-grab action + dynamicType = DYNAMIC_TYPE_HOLD; + arguments["holderID"] = grab->getOwnerID(); + arguments["hand"] = grab->getHand(); + arguments["timeScale"] = 0.05; + arguments["relativePosition"] = vec3ToQMap(grab->getPositionalOffset()); + arguments["relativeRotation"] = quatToQMap(grab->getRotationalOffset()); + arguments["kinematic"] = _grabProperties.getGrabKinematic(); + arguments["kinematicSetVelocity"] = true; + arguments["ignoreIK"] = _grabProperties.getGrabFollowsController(); + } + EntityDynamicPointer action = actionFactory->factory(dynamicType, actionID, getThisPointer(), arguments); + grab->setActionID(actionID); + _grabActions[actionID] = action; + simulation->addDynamic(action); + } +} + +void EntityItem::removeGrab(GrabPointer grab) { + SpatiallyNestable::removeGrab(grab); + + QUuid actionID = grab->getActionID(); + if (!actionID.isNull()) { + EntityDynamicPointer action = _grabActions.value(actionID); + if (action) { + _grabActions.remove(actionID); + EntityTreePointer entityTree = getTree(); + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + if (simulation) { + action->removeFromSimulation(simulation); + action->removeFromOwner(); + } + } + } +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5c45c96702..998409f6fa 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -51,7 +51,6 @@ typedef std::shared_ptr EntityDynamicPointer; typedef std::shared_ptr EntityTreeElementPointer; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr; - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { }; @@ -554,6 +553,9 @@ public: void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority); + virtual void addGrab(GrabPointer grab) override; + virtual void removeGrab(GrabPointer grab) override; + signals: void requestRenderUpdate(); void spaceUpdate(std::pair data); @@ -734,6 +736,8 @@ protected: GrabPropertyGroup _grabProperties; + QHash _grabActions; + private: std::unordered_map _materials; std::mutex _materialsLock; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 826d180539..ffa3549955 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2419,6 +2419,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab, GrabDelegateToParent, grabDelegateToParent); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, EquippableLeftPosition, equippableLeftPosition); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e2f483d5b8..a025288f95 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -385,6 +385,8 @@ public: void setQueryAACubeDirty() { _queryAACubeChanged = true; } + void setLocationDirty() { _positionChanged = true; _rotationChanged = true; } + void setCreated(QDateTime& v); bool hasTransformOrVelocityChanges() const; diff --git a/libraries/entities/src/EntityPropertyFlags.cpp b/libraries/entities/src/EntityPropertyFlags.cpp index dbb8463141..1f88c03c87 100644 --- a/libraries/entities/src/EntityPropertyFlags.cpp +++ b/libraries/entities/src/EntityPropertyFlags.cpp @@ -190,6 +190,7 @@ QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { result = f.getHasProperty(PROP_GRAB_FOLLOWS_CONTROLLER) ? result + "grab.FollowsController " : result; result = f.getHasProperty(PROP_GRAB_TRIGGERABLE) ? result + "grab.Triggerable " : result; result = f.getHasProperty(PROP_GRAB_EQUIPPABLE) ? result + "grab.Equippable " : result; + result = f.getHasProperty(PROP_GRAB_DELEGATE_TO_PARENT) ? result + "grab.GrabDelegateToParent " : result; result = f.getHasProperty(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET) ? result + "grab.LeftEquippablePositionOffset " : result; result = diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 864d5efa10..1e61894a8b 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -267,6 +267,7 @@ enum EntityPropertyList { PROP_GRAB_FOLLOWS_CONTROLLER, PROP_GRAB_TRIGGERABLE, PROP_GRAB_EQUIPPABLE, + PROP_GRAB_DELEGATE_TO_PARENT, PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 303e6d54c4..956ba12560 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -2268,3 +2268,126 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en } return result; } + +glm::vec3 EntityScriptingInterface::worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localPosition = SpatiallyNestable::worldToLocal(worldPosition, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localPosition; + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::quat localRotation = SpatiallyNestable::worldToLocal(worldRotation, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localRotation; + } else { + return glm::quat(); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(worldVelocity, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localAngularVelocity = SpatiallyNestable::worldToLocalAngularVelocity(worldAngularVelocity, parentID, + parentJointIndex, scalesWithParent, + success); + if (success) { + return localAngularVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + + bool success; + glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(worldDimensions, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localDimensions; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldPosition = SpatiallyNestable::localToWorld(localPosition, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldPosition; + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::localToWorldRotation(glm::quat localRotation, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::quat worldRotation = SpatiallyNestable::localToWorld(localRotation, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldRotation; + } else { + return glm::quat(); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(localVelocity, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(localAngularVelocity, + parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldAngularVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(localDimensions, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldDimensions; + } else { + return glm::vec3(0.0f); + } +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 1398c2ad1c..2c89381c31 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1573,6 +1573,109 @@ public slots: * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); + + /**jsdoc + * @function Entities.worldToLocalPosition + * @param {Vec3} worldPosition + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalRotation + * @param {Quat} worldRotation + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Quat} + */ + Q_INVOKABLE glm::quat worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalVelocity + * @param {Vec3} worldVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalAngularVelocity + * @param {Vec3} worldAngularVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalDimensions + * @param {Vec3} worldDimensions + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldPosition + * @param {Vec3} localPosition + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldRotation + * @param {Quat} localRotation + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Quat} + */ + Q_INVOKABLE glm::quat localToWorldRotation(glm::quat localRotation, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldVelocity + * @param {Vec3} localVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldAngularVelocity + * @param {Vec3} localAngularVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldDimensions + * @param {Vec3} localDimensions + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + + /**jsdoc * Get the static certificate for an entity. The static certificate contains static properties of the item which cannot * be altered. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b23fbca6d1..63bcd70a14 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2586,6 +2586,8 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) { grabProperties.setEquippable(equippable.toBool()); } + grabProperties.setGrabDelegateToParent(true); + if (grabbableKey["spatialKey"].isObject()) { QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); grabProperties.setEquippable(true); @@ -2956,3 +2958,52 @@ bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::Mat } return false; } + +void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { + // if the queryBox has changed, tell the entity-server + if (object->getNestableType() == NestableType::Entity && object->updateQueryAACube()) { + EntityItemPointer entity = std::static_pointer_cast(object); + if (entity->updateQueryAACube() || force) { + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success) { + moveOperator.addEntityToMoveList(entity, newCube); + } + // send an edit packet to update the entity-server about the queryAABox + // unless it is client-only + if (tellServer && packetSender && entity->isDomainEntity()) { + quint64 now = usecTimestampNow(); + EntityItemProperties properties = entity->getProperties(); + properties.setQueryAACubeDirty(); + properties.setLocationDirty(); + properties.setLastEdited(now); + + packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties); + entity->setLastBroadcast(now); // for debug/physics status icons + } + + entity->markDirtyFlags(Simulation::DIRTY_POSITION); + entityChanged(entity); + } + } + + object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer); + }); +} + +void EntityTree::updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + bool force, bool tellServer) { + // This is used when something other than a script or physics moves an entity. We need to put it in the + // correct place in our local octree, update its and its children's queryAACubes, and send an edit + // packet to the entity-server. + MovingEntitiesOperator moveOperator; + + updateEntityQueryAACubeWorker(object, packetSender, moveOperator, force, tellServer); + + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + recurseTreeWithOperator(&moveOperator); + } +} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d08e21e4f0..9181a4851c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -277,6 +277,9 @@ public: std::map getNamedPaths() const { return _namedPaths; } + void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + bool force, bool tellServer); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -392,6 +395,9 @@ private: bool _serverlessDomain { false }; std::map _namedPaths; + + void updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + MovingEntitiesOperator& moveOperator, bool force, bool tellServer); }; void convertGrabUserDataToProperties(EntityItemProperties& properties); diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index 996eed4720..73bc1ed2d0 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -24,6 +24,8 @@ void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab, + GrabDelegateToParent, grabDelegateToParent); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, EquippableLeftPosition, equippableLeftPosition); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, @@ -141,6 +143,7 @@ bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController()); APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable()); APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable()); + APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation()); APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition()); @@ -164,6 +167,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController); READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable); READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable); + READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation); READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition); @@ -177,6 +181,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_FOLLOWS_CONTROLLER, GrabFollowsController); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_TRIGGERABLE, Triggerable); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_EQUIPPABLE, Equippable); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, EquippableRightPosition); @@ -215,6 +220,7 @@ EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_GRAB_FOLLOWS_CONTROLLER, grabFollowsController); CHECK_PROPERTY_CHANGE(PROP_GRAB_TRIGGERABLE, triggerable); CHECK_PROPERTY_CHANGE(PROP_GRAB_EQUIPPABLE, equippable); + CHECK_PROPERTY_CHANGE(PROP_GRAB_DELEGATE_TO_PARENT, grabDelegateToParent); CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, equippableLeftPosition); CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, equippableLeftRotation); CHECK_PROPERTY_CHANGE(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, equippableRightPosition); @@ -273,6 +279,7 @@ EntityPropertyFlags GrabPropertyGroup::getEntityProperties(EncodeBitstreamParams requestedProperties += PROP_GRAB_FOLLOWS_CONTROLLER; requestedProperties += PROP_GRAB_TRIGGERABLE; requestedProperties += PROP_GRAB_EQUIPPABLE; + requestedProperties += PROP_GRAB_DELEGATE_TO_PARENT; requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET; requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET; requestedProperties += PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET; @@ -299,6 +306,7 @@ void GrabPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeB APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController()); APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable()); APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable()); + APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation()); APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition()); @@ -321,6 +329,7 @@ int GrabPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* dat READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController); READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable); READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable); + READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation); READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition); diff --git a/libraries/entities/src/GrabPropertyGroup.h b/libraries/entities/src/GrabPropertyGroup.h index 37b157de06..33550fef3d 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -31,6 +31,7 @@ static const bool INITIAL_KINEMATIC { true }; static const bool INITIAL_FOLLOWS_CONTROLLER { true }; static const bool INITIAL_TRIGGERABLE { false }; static const bool INITIAL_EQUIPPABLE { false }; +static const bool INITIAL_GRAB_DELEGATE_TO_PARENT { true }; static const glm::vec3 INITIAL_LEFT_EQUIPPABLE_POSITION { glm::vec3(0.0f) }; static const glm::quat INITIAL_LEFT_EQUIPPABLE_ROTATION { glm::quat() }; static const glm::vec3 INITIAL_RIGHT_EQUIPPABLE_POSITION { glm::vec3(0.0f) }; @@ -123,6 +124,8 @@ public: INITIAL_FOLLOWS_CONTROLLER); DEFINE_PROPERTY(PROP_GRAB_TRIGGERABLE, Triggerable, triggerable, bool, INITIAL_TRIGGERABLE); DEFINE_PROPERTY(PROP_GRAB_EQUIPPABLE, Equippable, equippable, bool, INITIAL_EQUIPPABLE); + DEFINE_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent, grabDelegateToParent, bool, + INITIAL_GRAB_DELEGATE_TO_PARENT); DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition, equippableLeftPosition, glm::vec3, INITIAL_LEFT_EQUIPPABLE_POSITION); DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation, equippableLeftRotation, diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index f2db155dca..37a1ec5f03 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::JointTransScaled); + return static_cast(AvatarMixerPacketVersion::GrabTraits); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0d4d065f5b..c353dd6ec7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -303,7 +303,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { MigrateSkeletonURLToTraits, MigrateAvatarEntitiesToTraits, FarGrabJointsRedux, - JointTransScaled + JointTransScaled, + GrabTraits }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index c5641ad347..87edc858cc 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -33,36 +33,53 @@ ObjectActionTractor::ObjectActionTractor(const QUuid& id, EntityItemPointer owne _rotationalTargetSet(true), _linearVelocityTarget(0.0f) { - #if WANT_DEBUG +#if WANT_DEBUG qCDebug(physics) << "ObjectActionTractor::ObjectActionTractor"; - #endif +#endif } ObjectActionTractor::~ObjectActionTractor() { - #if WANT_DEBUG +#if WANT_DEBUG qCDebug(physics) << "ObjectActionTractor::~ObjectActionTractor"; - #endif +#endif } bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity, - float& linearTimeScale, float& angularTimeScale) { - bool success { true }; - EntityItemPointer other = std::dynamic_pointer_cast(getOther()); - withReadLock([&]{ + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale) { + SpatiallyNestablePointer other = getOther(); + return resultWithReadLock([&]{ linearTimeScale = _linearTimeScale; angularTimeScale = _angularTimeScale; if (!_otherID.isNull()) { - if (other && other->isReadyToComputeShape()) { - rotation = _desiredRotationalTarget * other->getWorldOrientation(); - position = other->getWorldOrientation() * _desiredPositionalTarget + other->getWorldPosition(); + bool otherIsReady { true }; + if (other && other->getNestableType() == NestableType::Entity) { + EntityItemPointer otherEntity = std::static_pointer_cast(other); + otherIsReady = otherEntity->isReadyToComputeShape(); + } + if (other && otherIsReady) { + bool success; + glm::vec3 otherWorldPosition = other->getWorldPosition(_otherJointIndex, success); + if (!success) { + linearTimeScale = FLT_MAX; + angularTimeScale = FLT_MAX; + return false; + } + glm::quat otherWorldOrientation = other->getWorldOrientation(_otherJointIndex, success); + if (!success) { + linearTimeScale = FLT_MAX; + angularTimeScale = FLT_MAX; + return false; + } + rotation = _desiredRotationalTarget * otherWorldOrientation; + position = otherWorldOrientation * _desiredPositionalTarget + otherWorldPosition; } else { // we should have an "other" but can't find it, or its collision shape isn't loaded, // so disable the tractor. linearTimeScale = FLT_MAX; angularTimeScale = FLT_MAX; - success = false; + return false; } } else { rotation = _desiredRotationalTarget; @@ -70,8 +87,8 @@ bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, gl } linearVelocity = glm::vec3(); angularVelocity = glm::vec3(); + return true; }); - return success; } bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { @@ -108,9 +125,9 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { float linearTimeScale; float angularTimeScale; bool success = tractorAction->getTarget(deltaTimeStep, - rotationForAction, positionForAction, - linearVelocityForAction, angularVelocityForAction, - linearTimeScale, angularTimeScale); + rotationForAction, positionForAction, + linearVelocityForAction, angularVelocityForAction, + linearTimeScale, angularTimeScale); if (success) { if (angularTimeScale < MAX_TRACTOR_TIMESCALE) { angularTractorCount++; @@ -239,7 +256,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { glm::quat rotationalTarget; float angularTimeScale; QUuid otherID; - + int otherJointIndex; bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -274,18 +291,25 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { } ok = true; - otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action", - arguments, "otherID", ok, false)); + otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action", arguments, "otherID", ok, false)); if (!ok) { otherID = _otherID; } + ok = true; + otherJointIndex = EntityDynamicInterface::extractIntegerArgument("tractor action", arguments, + "otherJointIndex", ok, false); + if (!ok) { + otherJointIndex = _otherJointIndex; + } + if (somethingChanged || positionalTarget != _desiredPositionalTarget || linearTimeScale != _linearTimeScale || rotationalTarget != _desiredRotationalTarget || angularTimeScale != _angularTimeScale || - otherID != _otherID) { + otherID != _otherID || + otherJointIndex != _otherJointIndex) { // something changed needUpdate = true; } @@ -298,6 +322,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { _desiredRotationalTarget = rotationalTarget; _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); _otherID = otherID; + _otherJointIndex = otherJointIndex; _active = true; auto ownerEntity = _ownerEntity.lock(); @@ -313,20 +338,22 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { } /**jsdoc - * The "tractor" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * The "tractor" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and * orientation, optionally relative to another entity. * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. * * @typedef {object} Entities.ActionArguments-Tractor * @property {Vec3} targetPosition=0,0,0 - The target position. * @property {Quat} targetRotation=0,0,0,1 - The target rotation. - * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are * relative to this entity's position and rotation. + * @property {Uuid} otherJointIndex=null - If an entity JointIndex, the targetPosition and + * targetRotation are relative to this entity's joint's position and rotation. * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the - * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action * is applied using an exponential decay. * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the - * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the * action is applied using an exponential decay. */ QVariantMap ObjectActionTractor::getArguments() { @@ -339,6 +366,7 @@ QVariantMap ObjectActionTractor::getArguments() { arguments["angularTimeScale"] = _angularTimeScale; arguments["otherID"] = _otherID; + arguments["otherJointIndex"] = _otherJointIndex; }); return arguments; } @@ -354,6 +382,7 @@ void ObjectActionTractor::serializeParameters(QDataStream& dataStream) const { dataStream << localTimeToServerTime(_expires); dataStream << _tag; dataStream << _otherID; + dataStream << _otherJointIndex; }); } @@ -387,6 +416,7 @@ void ObjectActionTractor::deserializeParameters(QByteArray serializedArguments, dataStream >> _tag; dataStream >> _otherID; + dataStream >> _otherJointIndex; _active = true; }); diff --git a/libraries/physics/src/ObjectDynamic.h b/libraries/physics/src/ObjectDynamic.h index bfee79aca9..f41d6cd51d 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -66,6 +66,7 @@ protected: EntityItemID _otherID; SpatiallyNestableWeakPointer _other; SpatiallyNestablePointer getOther(); + int _otherJointIndex { -1 }; private: qint64 getEntityServerClockSkew() const; diff --git a/libraries/shared/src/Grab.cpp b/libraries/shared/src/Grab.cpp new file mode 100644 index 0000000000..195bc61f66 --- /dev/null +++ b/libraries/shared/src/Grab.cpp @@ -0,0 +1,70 @@ +// +// Grab.cpp +// libraries/avatars/src +// +// Created by Seth Alves on 2018-9-1. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Grab.h" + +QByteArray Grab::toByteArray() { + QByteArray ba; + QDataStream dataStream(&ba, QIODevice::WriteOnly); + const int dataEncodingVersion = 1; + dataStream << dataEncodingVersion << _ownerID << _targetID << _parentJointIndex + << _hand << _positionalOffset << _rotationalOffset; + return ba; +} + +bool Grab::fromByteArray(const QByteArray& grabData) { + QDataStream dataStream(grabData); + + int dataEncodingVersion; + QUuid newOwnerID { QUuid() }; + QUuid newTargetID { QUuid() }; + int newParentJointIndex { -1 }; + QString newHand { "none" }; + glm::vec3 newPositionalOffset { glm::vec3(0.0f) }; + glm::quat newRotationalOffset { glm::quat() }; + + dataStream >> dataEncodingVersion; + assert(dataEncodingVersion == 1); + dataStream >> newOwnerID; + dataStream >> newTargetID; + dataStream >> newParentJointIndex; + dataStream >> newHand; + dataStream >> newPositionalOffset; + dataStream >> newRotationalOffset; + + bool somethingChanged { false }; + if (_ownerID != newOwnerID) { + _ownerID = newOwnerID; + somethingChanged = true; + } + if (_targetID != newTargetID) { + _targetID = newTargetID; + somethingChanged = true; + } + if (_parentJointIndex != newParentJointIndex) { + _parentJointIndex = newParentJointIndex; + somethingChanged = true; + } + if (_hand != newHand) { + _hand = newHand; + somethingChanged = true; + } + if (_positionalOffset != newPositionalOffset) { + _positionalOffset = newPositionalOffset; + somethingChanged = true; + } + if (_rotationalOffset != newRotationalOffset) { + _rotationalOffset = newRotationalOffset; + somethingChanged = true; + } + + return somethingChanged; +} diff --git a/libraries/shared/src/Grab.h b/libraries/shared/src/Grab.h new file mode 100644 index 0000000000..5765d6fd0e --- /dev/null +++ b/libraries/shared/src/Grab.h @@ -0,0 +1,99 @@ +// +// Grab.h +// libraries/avatars/src +// +// Created by Seth Alves on 2018-9-1. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Grab_h +#define hifi_Grab_h + +#include +#include +#include "GLMHelpers.h" +#include "StreamUtils.h" + +class Grab; +using GrabPointer = std::shared_ptr; +using GrabWeakPointer = std::weak_ptr; + +class GrabLocationAccumulator { +public: + void accumulate(glm::vec3 position, glm::quat orientation) { + _position += position; + _orientation = orientation; // XXX + count++; + } + + glm::vec3 finalizePosition() { return count > 0 ? _position * (1.0f / count) : glm::vec3(0.0f); } + glm::quat finalizeOrientation() { return _orientation; } // XXX + +protected: + glm::vec3 _position; + glm::quat _orientation; + int count { 0 }; +}; + +class Grab { +public: + Grab() {}; + Grab(const QUuid& newOwnerID, const QUuid& newTargetID, int newParentJointIndex, const QString& newHand, + glm::vec3 newPositionalOffset, glm::quat newRotationalOffset) : + _ownerID(newOwnerID), + _targetID(newTargetID), + _parentJointIndex(newParentJointIndex), + _hand(newHand), + _positionalOffset(newPositionalOffset), + _rotationalOffset(newRotationalOffset) {} + + QByteArray toByteArray(); + bool fromByteArray(const QByteArray& grabData); + + Grab& operator=(const GrabPointer& other) { + _ownerID = other->_ownerID; + _targetID = other->_targetID; + _parentJointIndex = other->_parentJointIndex; + _hand = other->_hand; + _positionalOffset = other->_positionalOffset; + _rotationalOffset = other->_rotationalOffset; + _actionID = other->_actionID; + return *this; + } + + QUuid getActionID() const { return _actionID; } + void setActionID(const QUuid& actionID) { _actionID = actionID; } + + QUuid getOwnerID() const { return _ownerID; } + void setOwnerID(QUuid ownerID) { _ownerID = ownerID; } + + QUuid getTargetID() const { return _targetID; } + void setTargetID(QUuid targetID) { _targetID = targetID; } + + int getParentJointIndex() const { return _parentJointIndex; } + void setParentJointIndex(int parentJointIndex) { _parentJointIndex = parentJointIndex; } + + QString getHand() const { return _hand; } + void setHand(QString hand) { _hand = hand; } + + glm::vec3 getPositionalOffset() const { return _positionalOffset; } + void setPositionalOffset(glm::vec3 positionalOffset) { _positionalOffset = positionalOffset; } + + glm::quat getRotationalOffset() const { return _rotationalOffset; } + void setRotationalOffset(glm::quat rotationalOffset) { _rotationalOffset = rotationalOffset; } + +protected: + QUuid _actionID; // if an action is created in bullet for this grab, this is the ID + QUuid _ownerID; // avatar ID of grabber + QUuid _targetID; // SpatiallyNestable ID of grabbed + int _parentJointIndex { -1 }; // which avatar joint is being used to grab + QString _hand; // "left" or "right" + glm::vec3 _positionalOffset; // relative to joint + glm::quat _rotationalOffset; // relative to joint +}; + + +#endif // hifi_Grab_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index d704498143..9ffbb8fd3d 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1369,3 +1369,35 @@ bool SpatiallyNestable::isParentPathComplete(int depth) const { return parent->isParentPathComplete(depth + 1); } + +void SpatiallyNestable::addGrab(GrabPointer grab) { + _grabsLock.withWriteLock([&] { + _grabs.insert(grab); + }); +} + +void SpatiallyNestable::removeGrab(GrabPointer grab) { + _grabsLock.withWriteLock([&] { + _grabs.remove(grab); + }); +} + +QUuid SpatiallyNestable::getEditSenderID() { + // if more than one avatar is grabbing something, decide which one should tell the enity-server about it + QUuid editSenderID; + bool editSenderIDSet { false }; + _grabsLock.withReadLock([&] { + foreach (const GrabPointer &grab, _grabs) { + QUuid ownerID = grab->getOwnerID(); + if (!editSenderIDSet) { + editSenderID = ownerID; + editSenderIDSet = true; + } else { + if (ownerID < editSenderID) { + editSenderID = ownerID; + } + } + } + }); + return editSenderID; +} diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index cf2e304d19..0c6bada85b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -18,6 +18,7 @@ #include "AACube.h" #include "SpatialParentFinder.h" #include "shared/ReadWriteLockable.h" +#include "Grab.h" class SpatiallyNestable; using SpatiallyNestableWeakPointer = std::weak_ptr; @@ -213,6 +214,10 @@ public: virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed virtual void parentDeleted() { } // called on children of a deleted parent + virtual void addGrab(GrabPointer grab); + virtual void removeGrab(GrabPointer grab); + virtual QUuid getEditSenderID(); + protected: QUuid _id; mutable SpatiallyNestableWeakPointer _parent; @@ -232,6 +237,9 @@ protected: quint64 _translationChanged { 0 }; quint64 _rotationChanged { 0 }; + mutable ReadWriteLockable _grabsLock; + QSet _grabs; + private: SpatiallyNestable() = delete; const NestableType _nestableType; // EntityItem or an AvatarData diff --git a/scripts/developer/debugging/queryAACubeInspector.js b/scripts/developer/debugging/queryAACubeInspector.js index d8a87c3cf5..d6adcf02b7 100644 --- a/scripts/developer/debugging/queryAACubeInspector.js +++ b/scripts/developer/debugging/queryAACubeInspector.js @@ -40,7 +40,8 @@ function updateOverlay(entityID, queryAACube) { blue: 255 }, alpha: 1, - solid: false + solid: false, + grabbable: false }); } } @@ -60,4 +61,4 @@ function cleanup() { } } -Script.scriptEnding.connect(cleanup); \ No newline at end of file +Script.scriptEnding.connect(cleanup); diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js new file mode 100644 index 0000000000..39c65aa022 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -0,0 +1,521 @@ +"use strict"; + +// farGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues, + Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, + projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager, + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGroupParent, + worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarGrabEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.targetObject = null; + this.previouslyUnhooked = {}; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.highlightedEntity = null; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + this.lastUnexpectedChildrenCheckTime = 0; + + var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 540, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.startFarGrabEntity = function (controllerData, targetProps) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = targetProps.position; + this.currentObjectRotation = targetProps.rotation; + this.currentObjectTime = now; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(targetProps.dimensions, targetProps.density); + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); + + var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(targetProps.id, "startNearGrab", args); + + this.targetEntityID = targetProps.id; + + + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + } + var farJointIndex = FAR_GRAB_JOINTS[this.hand]; + this.grabID = MyAvatar.grab(targetProps.id, farJointIndex, + Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), + Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: targetProps.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + this.grabbing = true; + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var targetProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(targetProps.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); + + // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); + var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarGrabEntity = function (controllerData) { + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + + this.hapticTargetID = null; + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + unhighlightTargetEntity(this.targetEntityID); + this.grabbing = false; + this.targetEntityID = null; + this.potentialEntityWithContextOverlay = false; + MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity) { + var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES); + if (entityIsGrabbable(gtProps)) { + // give haptic feedback + if (gtProps.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = gtProps.id; + } + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, gtProps); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + return gtProps; + } + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + this.endFarGrabEntity(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", + this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" + ]; + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.targetEntityID) { + // if we are doing a distance grab and the object or tablet gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID || + HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { + this.endFarGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + + this.targetEntityID = entityID; + this.grabbedDistance = rayPickInfo.distance; + this.distanceHolding = true; + this.startFarGrabEntity(controllerData, targetProps); + } + } else { + var targetEntityID = rayPickInfo.objectID; + if (this.highlightedEntity !== targetEntityID) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); + + var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); + selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); + var selectionTargetEntity = selectionTargetObject.getTargetEntity(); + + if (entityIsGrabbable(selectionTargetEntity.props) || + entityIsGrabbable(selectionTargetObject.entityProps)) { + + Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); + } + this.highlightedEntity = rayPickInfo.objectID; + } + + if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var cotProps = Entities.getEntityProperties(rayPickInfo.objectID, + DISPATCHER_PROPERTIES); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, cotProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } else if (this.highlightedEntity) { + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarGrabEntity(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = this.distanceHolding ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); + } + return undefined; + }; + } + + var leftFarGrabEntity = new FarGrabEntity(LEFT_HAND); + var rightFarGrabEntity = new FarGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity); + enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarGrabEntity"); + disableDispatcherModule("RightFarGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index bb563a269c..ddff35b9e7 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -8,10 +8,10 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, - Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, + MSECS_PER_SEC, makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid, - DISPATCHER_PROPERTIES + DISPATCHER_PROPERTIES, HMD */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -65,25 +65,14 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.grabFollowsController = grabbableData.grabFollowsController; this.kinematicGrab = grabbableData.grabKinematic; - var handRotation; - var handPosition; - if (this.grabFollowsController) { - var controllerID = - (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var controllerLocation = getControllerWorldLocation(controllerID, false); - handRotation = controllerLocation.orientation; - handPosition = controllerLocation.position; + var handJointIndex; + if (HMD.mounted && HMD.isHandControllerAvailable() && grabbableData.grabFollowsController) { + handJointIndex = getControllerJointIndex(this.hand); } else { - handRotation = this.getHandRotation(); - handPosition = this.getHandPosition(); + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); } - - var objectRotation = targetProps.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = targetProps.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + this.offsetPosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex); + this.offsetRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex); var now = Date.now(); this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); diff --git a/scripts/system/controllers/controllerModules/nearGrabEntity.js b/scripts/system/controllers/controllerModules/nearGrabEntity.js new file mode 100644 index 0000000000..78e15f51e4 --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearGrabEntity.js @@ -0,0 +1,249 @@ +"use strict"; + +// nearGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, + enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, + findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, + HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, + TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, Uuid, highlightTargetEntity, unhighlightTargetEntity, + distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/cloneEntityUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + + // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + + // this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 + var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral + + function getGrabOffset(handController) { + var offset = getGrabPointSphereOffset(handController, true); + offset.y = -offset.y; + return Vec3.multiply(MyAvatar.sensorToWorldScale, offset); + } + + function NearGrabEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.grabbing = false; + this.hapticTargetID = null; + this.highlightedEntity = null; + this.cloneAllowed = true; + this.grabID = null; + + this.parameters = makeDispatcherModuleParameters( + 500, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.startNearGrabEntity = function (controllerData, targetProps) { + var grabData = getGrabbableData(targetProps); + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); + var handJointIndex; + if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) { + handJointIndex = getControllerJointIndex(this.hand); + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(targetProps.id, "startNearGrab", args); + + this.targetEntityID = targetProps.id; + + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + } + + var relativePosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex); + var relativeRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex); + + this.grabID = MyAvatar.grab(targetProps.id, handJointIndex, relativePosition, relativeRotation); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: targetProps.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + this.grabbing = true; + }; + + this.endNearGrabEntity = function (controllerData) { + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + + this.hapticTargetID = null; + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; + this.grabbing = false; + this.targetEntityID = null; + }; + + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by length from controller + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + var handPosition = controllerData.controllerLocations[this.hand].position; + var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); + var distance = Vec3.distance(handPosition, props.position); + if ((dist > TEAR_AWAY_DISTANCE) || + (distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) { + continue; + } + if (entityIsGrabbable(props) || entityIsCloneable(props)) { + // give haptic feedback + if (props.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = props.id; + } + if (!entityIsCloneable(props)) { + // if we've attempted to grab a non-cloneable child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + } + return props; + } + } + return null; + }; + + this.isReady = function (controllerData, deltaTime) { + this.targetEntityID = null; + this.grabbing = false; + + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + this.cloneAllowed = true; + return makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.targetEntityID = targetProps.id; + this.highlightedEntity = this.targetEntityID; + highlightTargetEntity(this.targetEntityID); + return makeRunningValues(true, [this.targetEntityID], []); + } else { + if (this.highlightedEntity) { + unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; + } + this.hapticTargetID = null; + return makeRunningValues(false, [], []); + } + }; + + this.run = function (controllerData, deltaTime) { + if (this.grabbing) { + if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + this.endNearGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + if (!props) { + props = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + if (!props) { + // entity was deleted + unhighlightTargetEntity(this.targetEntityID); + this.highlightedEntity = null; + this.grabbing = false; + this.targetEntityID = null; + this.hapticTargetID = null; + return makeRunningValues(false, [], []); + } + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); + } else { + // still searching / highlighting + var readiness = this.isReady(controllerData); + if (!readiness.active) { + unhighlightTargetEntity(this.highlightedEntity); + this.highlightedEntity = null; + return readiness; + } + if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { + // switch to grab + var targetProps = this.getTargetProps(controllerData); + var targetCloneable = entityIsCloneable(targetProps); + + if (targetCloneable) { + if (this.cloneAllowed) { + var cloneID = cloneEntity(targetProps); + if (cloneID !== null) { + var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES); + this.grabbing = true; + this.targetEntityID = cloneID; + this.startNearGrabEntity(controllerData, cloneProps); + this.cloneAllowed = false; // prevent another clone call until inputs released + } + } + } else if (targetProps) { + this.grabbing = true; + this.startNearGrabEntity(controllerData, targetProps); + } + } + } + + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.cleanup = function () { + if (this.targetEntityID) { + this.endNearGrabEntity(); + } + }; + } + + var leftNearGrabEntity = new NearGrabEntity(LEFT_HAND); + var rightNearGrabEntity = new NearGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearGrabEntity", leftNearGrabEntity); + enableDispatcherModule("RightNearGrabEntity", rightNearGrabEntity); + + function cleanup() { + leftNearGrabEntity.cleanup(); + rightNearGrabEntity.cleanup(); + disableDispatcherModule("LeftNearGrabEntity"); + disableDispatcherModule("RightNearGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 83fa455519..7ab824f2c5 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -18,11 +18,7 @@ var CONTOLLER_SCRIPTS = [ "toggleAdvancedMovementForHandControllers.js", "handTouch.js", "controllerDispatcher.js", - "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", - "controllerModules/nearActionGrabEntity.js", - "controllerModules/farActionGrabEntityDynOnly.js", - "controllerModules/farParentGrabEntity.js", "controllerModules/stylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", @@ -39,6 +35,16 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearTabletHighlight.js" ]; +if (Settings.getValue("useTraitsGrab", false)) { + CONTOLLER_SCRIPTS.push("controllerModules/nearGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farGrabEntity.js"); +} else { + CONTOLLER_SCRIPTS.push("controllerModules/nearParentGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/nearActionGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js"); +} + var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 070997d479..fbaa61e9c9 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -104,7 +104,7 @@ TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. // Smaller than TEAR_AWAY_DISTANCE for hysteresis. -DISPATCHER_HOVERING_LIST = "dispactherHoveringList"; +DISPATCHER_HOVERING_LIST = "dispatcherHoveringList"; DISPATCHER_HOVERING_STYLE = { isOutlineSmooth: true, outlineWidth: 0, @@ -144,6 +144,7 @@ DISPATCHER_PROPERTIES = [ "grab.grabFollowsController", "grab.triggerable", "grab.equippable", + "grab.grabDelegateToParent", "grab.equippableLeftPosition", "grab.equippableLeftRotation", "grab.equippableRightPosition", @@ -332,15 +333,15 @@ getControllerJointIndex = function (hand) { if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) { if (HMD.isHandControllerAvailable()) { var controllerJointIndex = -1; - if (Camera.mode === "first person") { - controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); - } else if (Camera.mode === "third person") { + // if (Camera.mode === "first person") { + // controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + // "_CONTROLLER_RIGHTHAND" : + // "_CONTROLLER_LEFTHAND"); + // } else if (Camera.mode === "third person") { controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - } + // } getControllerJointIndexCacheTime[hand] = now; getControllerJointIndexCache[hand] = controllerJointIndex; @@ -409,7 +410,8 @@ ensureDynamic = function (entityID) { }; findGroupParent = function (controllerData, targetProps) { - while (targetProps.parentID && + while (targetProps.grab.grabDelegateToParent && + targetProps.parentID && targetProps.parentID !== Uuid.NULL && Entities.getNestableType(targetProps.parentID) == "entity") { var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);