diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index 1869980270..2bc4608e86 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "InterfaceActionFactory.h" @@ -29,6 +30,8 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& i return std::make_shared(id, ownerEntity); case ACTION_TYPE_HOLD: return std::make_shared(id, ownerEntity); + case ACTION_TYPE_TRAVEL_ORIENTED: + return std::make_shared(id, ownerEntity); } Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type"); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 31fb2abe53..717a0eeac7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -59,8 +59,6 @@ const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); -const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; - namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); @@ -853,32 +851,54 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::quat rotation; - _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); - return Quaternions::Y_180 * rotation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getRotation(); + } + default: { + glm::quat rotation; + _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); + return Quaternions::Y_180 * rotation; + } } } glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return extractTranslation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::vec3 translation; - _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); - return Quaternions::Y_180 * translation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return extractTranslation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getTranslation(); + } + default: { + glm::vec3 translation; + _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); + return Quaternions::Y_180 * translation; + } } } @@ -889,6 +909,10 @@ int Avatar::getJointIndex(const QString& name) const { Q_RETURN_ARG(int, result), Q_ARG(const QString&, name)); return result; } + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointIndex(name) : -1; } diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5acee052f2..51171b9c6b 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -56,6 +56,10 @@ void AvatarActionHold::prepareForPhysicsSimulation() { } withWriteLock([&]{ + glm::vec3 avatarRigidBodyPosition; + glm::quat avatarRigidBodyRotation; + getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); + if (_ignoreIK) { return; } @@ -70,9 +74,6 @@ void AvatarActionHold::prepareForPhysicsSimulation() { palmRotation = holdingAvatar->getUncachedLeftPalmRotation(); } - glm::vec3 avatarRigidBodyPosition; - glm::quat avatarRigidBodyRotation; - getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); // determine the difference in translation and rotation between the avatar's // rigid body and the palm position. The avatar's rigid body will be moved by bullet @@ -124,13 +125,20 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: if (pose.isValid()) { linearVelocity = pose.getVelocity(); angularVelocity = pose.getAngularVelocity(); + + if (isRightHand) { + pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInAvatarFrame(); + } else { + pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInAvatarFrame(); + } } if (_ignoreIK && pose.isValid()) { - // We cannot ignore other avatars IK and this is not the point of this option - // This is meant to make the grabbing behavior more reactive. - palmPosition = pose.getTranslation(); - palmRotation = pose.getRotation(); + Transform avatarTransform; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + avatarTransform = myAvatar->getTransform(); + palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getTargetScale()); + palmRotation = avatarTransform.getRotation() * pose.getRotation(); } else { glm::vec3 avatarRigidBodyPosition; glm::quat avatarRigidBodyRotation; @@ -159,11 +167,17 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: } } else { // regular avatar if (isRightHand) { - palmPosition = holdingAvatar->getRightPalmPosition(); - palmRotation = holdingAvatar->getRightPalmRotation(); + Transform controllerRightTransform = Transform(holdingAvatar->getControllerRightHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerRightTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerRightTransform.getTranslation()); } else { - palmPosition = holdingAvatar->getLeftPalmPosition(); - palmRotation = holdingAvatar->getLeftPalmRotation(); + Transform controllerLeftTransform = Transform(holdingAvatar->getControllerLeftHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerLeftTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerLeftTransform.getTranslation()); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5687b5025c..24dbc62318 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -532,11 +532,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); } +void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { + assert(QThread::currentThread() == thread()); + auto userInputMapper = DependencyManager::get(); + controller::Pose controllerPose = userInputMapper->getPoseState(poseKey); + Transform transform; + transform.setTranslation(controllerPose.getTranslation()); + transform.setRotation(controllerPose.getRotation()); + glm::mat4 controllerMatrix = transform.getMatrix(); + matrixCache.set(controllerMatrix); +} + // best called at end of main loop, after physics. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. void MyAvatar::updateSensorToWorldMatrix() { - // update the sensor mat so that the body position will end up in the desired // position when driven from the head. glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition()); @@ -545,10 +555,14 @@ void MyAvatar::updateSensorToWorldMatrix() { lateUpdatePalms(); if (_enableDebugDrawSensorToWorldMatrix) { - DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); + DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), + extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); } _sensorToWorldMatrixCache.set(_sensorToWorldMatrix); + + updateJointFromController(controller::Action::LEFT_HAND, _controllerLeftHandMatrixCache); + updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); } // Update avatar head rotation with sensor data @@ -2215,3 +2229,31 @@ bool MyAvatar::didTeleport() { bool MyAvatar::hasDriveInput() const { return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } + +glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getRotation(); + } + default: { + return Avatar::getAbsoluteJointRotationInObjectFrame(index); + } + } +} + +glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getTranslation(); + } + default: { + return Avatar::getAbsoluteJointTranslationInObjectFrame(index); + } + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1f212a1fec..c4ffc08cbc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -19,6 +19,7 @@ #include #include +#include #include "Avatar.h" #include "AtRestDetector.h" @@ -117,6 +118,9 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + // read the location of a hand controller and save the transform + void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache); + // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. @@ -270,6 +274,9 @@ public: Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); Q_INVOKABLE bool getCharacterControllerEnabled(); + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + public slots: void increaseSize(); void decreaseSize(); @@ -410,9 +417,8 @@ private: bool _useSnapTurn { true }; bool _clearOverlayWhenMoving { true }; - // working copy of sensorToWorldMatrix. - // See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access - glm::mat4 _sensorToWorldMatrix; + // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access + glm::mat4 _sensorToWorldMatrix { glm::mat4() }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9088ee0577..037f12d2fa 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -374,6 +374,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } + // faux joints + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + #ifdef WANT_DEBUG if (sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll @@ -429,6 +439,20 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } + +const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSafeValueCache& matrixCache) { + glm::quat orientation; + glm::vec3 position; + Transform transform; + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, orientation); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, TRANSLATION_COMPRESSION_RADIX); + transform.setTranslation(position); + transform.setRotation(orientation); + matrixCache.set(transform.getMatrix()); + return sourceBuffer; +} + + #define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ if (shouldLogError(now)) { \ @@ -655,6 +679,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } #endif + // faux joints + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache); + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache); + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; @@ -915,7 +943,24 @@ void AvatarData::clearJointsData() { } } +int AvatarData::getFauxJointIndex(const QString& name) const { + if (name == "_SENSOR_TO_WORLD_MATRIX") { + return SENSOR_TO_WORLD_MATRIX_INDEX; + } + if (name == "_CONTROLLER_LEFTHAND") { + return CONTROLLER_LEFTHAND_INDEX; + } + if (name == "_CONTROLLER_RIGHTHAND") { + return CONTROLLER_RIGHTHAND_INDEX; + } + return -1; +} + int AvatarData::getJointIndex(const QString& name) const { + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } QReadLocker readLock(&_jointDataLock); return _jointIndices.value(name) - 1; } @@ -1743,6 +1788,17 @@ glm::mat4 AvatarData::getSensorToWorldMatrix() const { return _sensorToWorldMatrixCache.get(); } +// thread-safe +glm::mat4 AvatarData::getControllerLeftHandMatrix() const { + return _controllerLeftHandMatrixCache.get(); +} + +// thread-safe +glm::mat4 AvatarData::getControllerRightHandMatrix() const { + return _controllerRightHandMatrixCache.get(); +} + + QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 572657e921..715c24b8ee 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -173,6 +173,8 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) + Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) + Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) public: @@ -356,6 +358,8 @@ public: // thread safe Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; + Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; public slots: void sendAvatarDataPacket(); @@ -369,6 +373,8 @@ public slots: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + float getTargetScale() { return _targetScale; } + protected: glm::vec3 _handPosition; @@ -433,6 +439,10 @@ protected: // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerRightHandMatrixCache { glm::mat4() }; + + int getFauxJointIndex(const QString& name) const; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); @@ -519,5 +529,10 @@ Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +// faux joint indexes (-1 means invalid) +const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2 +const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3 +const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 + #endif // hifi_AvatarData_h diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 7490a44c11..ff44d5d13d 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -325,7 +325,6 @@ QString UserInputMapper::getActionName(Action action) const { return QString(); } - QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; @@ -335,6 +334,12 @@ QVector UserInputMapper::getActionNames() const { return result; } +Pose UserInputMapper::getPoseState(Action action) const { + assert(QThread::currentThread() == thread()); + return _poseStates[toInt(action)]; +} + + bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = false; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 874e5054ea..baa05f2f9f 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -81,7 +81,7 @@ namespace controller { QVector getAllActions() const; QString getActionName(Action action) const; float getActionState(Action action) const { return _actionStates[toInt(action)]; } - Pose getPoseState(Action action) const { return _poseStates[toInt(action)]; } + Pose getPoseState(Action action) const; int findAction(const QString& actionName) const; QVector getActionNames() const; Input inputFromAction(Action action) const { return getActionInputs()[toInt(action)].first; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6904700be5..01f607b0ed 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -527,9 +527,11 @@ void HmdDisplayPlugin::compositeExtra() { if (_presentHandPoses[index] == IDENTITY_MATRIX) { return; } - const auto& points = _presentHandLaserPoints[index]; - const auto& lasers = _presentHandLasers[index]; - geometryCache->renderGlowLine(batch, points.first, points.second, lasers.color); + const auto& laser = _presentHandLasers[index]; + if (laser.valid()) { + const auto& points = _presentHandLaserPoints[index]; + geometryCache->renderGlowLine(batch, points.first, points.second, laser.color); + } }); }); } diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index ce9a93a6ac..2ce4ce5555 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -100,6 +100,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS if (normalizedActionTypeString == "hold") { return ACTION_TYPE_HOLD; } + if (normalizedActionTypeString == "traveloriented") { + return ACTION_TYPE_TRAVEL_ORIENTED; + } qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; return ACTION_TYPE_NONE; @@ -115,6 +118,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { return "spring"; case ACTION_TYPE_HOLD: return "hold"; + case ACTION_TYPE_TRAVEL_ORIENTED: + return "travel-oriented"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 9a881cf94c..d9a901f1f6 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -28,7 +28,8 @@ enum EntityActionType { ACTION_TYPE_NONE = 0, ACTION_TYPE_OFFSET = 1000, ACTION_TYPE_SPRING = 2000, - ACTION_TYPE_HOLD = 3000 + ACTION_TYPE_HOLD = 3000, + ACTION_TYPE_TRAVEL_ORIENTED = 4000 }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0d25d4f1be..c0272a644d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_WEB_ENTITIES_SUPPORT_DPI; + return VERSION_ENTITIES_ARROW_ACTION; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::SensorToWorldMat); + return static_cast(AvatarMixerPacketVersion::HandControllerJoints); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3ecdb75a18..8d08585c2b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -187,13 +187,15 @@ const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; +const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, AvatarEntities, AbsoluteSixByteRotations, - SensorToWorldMat + SensorToWorldMat, + HandControllerJoints }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp new file mode 100644 index 0000000000..18d09d21d9 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -0,0 +1,206 @@ +// +// ObjectActionTravelOriented.cpp +// libraries/physics/src +// +// Created by Seth Alves 2015-6-5 +// Copyright 2015 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 "QVariantGLM.h" +#include "ObjectActionTravelOriented.h" + +const uint16_t ObjectActionTravelOriented::actionVersion = 1; + + +ObjectActionTravelOriented::ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectAction(ACTION_TYPE_TRAVEL_ORIENTED, id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::ObjectActionTravelOriented"; + #endif +} + +ObjectActionTravelOriented::~ObjectActionTravelOriented() { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::~ObjectActionTravelOriented"; + #endif +} + +void ObjectActionTravelOriented::updateActionWorker(btScalar deltaTimeStep) { + withReadLock([&] { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + void* physicsInfo = ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (!rigidBody) { + qDebug() << "ObjectActionTravelOriented::updateActionWorker no rigidBody"; + return; + } + const float MAX_TIMESCALE = 600.0f; // 10 min is a long time + if (_angularTimeScale > MAX_TIMESCALE) { + return; + } + + // find normalized velocity + glm::vec3 velocity = bulletToGLM(rigidBody->getLinearVelocity()); + float speed = glm::length(velocity); + const float TRAVEL_ORIENTED_TOO_SLOW = 0.001f; // meters / second + if (speed < TRAVEL_ORIENTED_TOO_SLOW) { + return; + } + glm::vec3 direction = glm::normalize(velocity); + + // find current angle of "forward" + btQuaternion bodyRotation = rigidBody->getOrientation(); + glm::quat orientation = bulletToGLM(bodyRotation); + glm::vec3 forwardInWorldFrame = glm::normalize(orientation * _forward); + + // find the rotation that would line up direction and forward + glm::quat neededRotation = glm::rotation(forwardInWorldFrame, direction); + glm::quat rotationalTarget = neededRotation * orientation; + + btVector3 targetAngularVelocity(0.0f, 0.0f, 0.0f); + + auto alignmentDot = bodyRotation.dot(glmToBullet(rotationalTarget)); + const float ALMOST_ONE = 0.99999f; + if (glm::abs(alignmentDot) < ALMOST_ONE) { + btQuaternion target = glmToBullet(rotationalTarget); + if (alignmentDot < 0.0f) { + target = -target; + } + // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: + // + // Q1 = dQ * Q0 + // + // solving for dQ gives: + // + // dQ = Q1 * Q0^ + btQuaternion deltaQ = target * bodyRotation.inverse(); + float speed = deltaQ.getAngle() / _angularTimeScale; + targetAngularVelocity = speed * deltaQ.getAxis(); + if (speed > rigidBody->getAngularSleepingThreshold()) { + rigidBody->activate(); + } + } + // this action is aggresively critically damped and defeats the current velocity + rigidBody->setAngularVelocity(targetAngularVelocity); + }); +} + +const float MIN_TIMESCALE = 0.1f; + + +bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { + glm::vec3 forward; + float angularTimeScale; + + bool needUpdate = false; + bool somethingChanged = ObjectAction::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + forward = EntityActionInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true); + if (!ok) { + forward = _forward; + } + ok = true; + angularTimeScale = + EntityActionInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false); + if (!ok) { + angularTimeScale = _angularTimeScale; + } + + if (somethingChanged || + forward != _forward || + angularTimeScale != _angularTimeScale) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _forward = forward; + _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); + _active = (_forward != glm::vec3()); + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); + } + }); + activateBody(); + } + + return true; +} + +QVariantMap ObjectActionTravelOriented::getArguments() { + QVariantMap arguments = ObjectAction::getArguments(); + withReadLock([&] { + arguments["forward"] = glmToQMap(_forward); + arguments["angularTimeScale"] = _angularTimeScale; + }); + return arguments; +} + +QByteArray ObjectActionTravelOriented::serialize() const { + QByteArray serializedActionArguments; + QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly); + + dataStream << ACTION_TYPE_TRAVEL_ORIENTED; + dataStream << getID(); + dataStream << ObjectActionTravelOriented::actionVersion; + + withReadLock([&] { + dataStream << _forward; + dataStream << _angularTimeScale; + + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + }); + + return serializedActionArguments; +} + +void ObjectActionTravelOriented::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityActionType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectActionTravelOriented::actionVersion) { + assert(false); + return; + } + + withWriteLock([&] { + dataStream >> _forward; + dataStream >> _angularTimeScale; + + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + + dataStream >> _tag; + + _active = (_forward != glm::vec3()); + }); +} diff --git a/libraries/physics/src/ObjectActionTravelOriented.h b/libraries/physics/src/ObjectActionTravelOriented.h new file mode 100644 index 0000000000..66a425b409 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.h @@ -0,0 +1,39 @@ +// +// ObjectActionTravelOriented.h +// libraries/physics/src +// +// Created by Seth Alves 2016-8-28 +// Copyright 2015 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_ObjectActionTravelOriented_h +#define hifi_ObjectActionTravelOriented_h + +#include "ObjectAction.h" + +class ObjectActionTravelOriented : public ObjectAction { +public: + ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectActionTravelOriented(); + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual void updateActionWorker(float deltaTimeStep) override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + +protected: + static const uint16_t actionVersion; + + glm::vec3 _forward { glm::vec3() }; // the vector in object space that should point in the direction of travel + float _angularTimeScale { 0.1f }; + + glm::vec3 _angularVelocityTarget; +}; + +#endif // hifi_ObjectActionTravelOriented_h diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 60d41d9105..28488c9f3b 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -11,6 +11,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4 */ (function() { // BEGIN LOCAL_SCOPE @@ -24,6 +25,9 @@ var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; +var FORCE_IGNORE_IK = true; +var SHOW_GRAB_POINT_SPHERE = true; + // // these tune time-averaging and "on" value for analog trigger // @@ -59,6 +63,7 @@ var EQUIP_SPHERE_COLOR = { var EQUIP_SPHERE_ALPHA = 0.15; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + // // distant manipulation // @@ -87,21 +92,21 @@ var COLORS_GRAB_DISTANCE_HOLD = { blue: 214 }; - var PICK_MAX_DISTANCE = 500; // max length of pick-ray // // near grabbing // -var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. +var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. // if EQUIP_HOTSPOT_RENDER_RADIUS is greater than zero, the hotspot will appear before the hand // has reached the required position, and then grow larger once the hand is close enough to equip. var EQUIP_HOTSPOT_RENDER_RADIUS = 0.0; // radius used for palm vs equip-hotspot for rendering hot-spots +var MAX_EQUIP_HOTSPOT_RADIUS = 1.0; var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_RADIUS = 0.07; // radius used for palm vs object for near grabbing. var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. @@ -112,6 +117,13 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g // if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped. var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks + +var GRAB_POINT_SPHERE_OFFSET = { x: 0.0, y: 0.2, z: 0.0 }; +var GRAB_POINT_SPHERE_RADIUS = NEAR_GRAB_RADIUS; +var GRAB_POINT_SPHERE_COLOR = { red: 20, green: 90, blue: 238 }; +var GRAB_POINT_SPHERE_ALPHA = 0.85; + + // // other constants // @@ -168,7 +180,7 @@ var USE_BLACKLIST = true; var blacklist = []; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; -var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; +var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; // states for the state machine var STATE_OFF = 0; @@ -183,7 +195,6 @@ var STATE_ENTITY_TOUCHING = 7; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; -var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; @@ -273,11 +284,9 @@ function projectOntoEntityXYPlane(entityID, worldPos) { y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis } -function handLaserIntersectEntity(entityID, hand) { - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); +function handLaserIntersectEntity(entityID, start) { + var worldHandPosition = start.position; + var worldHandRotation = start.orientation; var props = entityPropertiesCache.getProps(entityID); @@ -355,7 +364,7 @@ function entityIsGrabbedByOther(entityID) { for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; + var tag = actionArguments.tag; if (tag == getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; @@ -680,9 +689,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp) { if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { // this is an old overlay, that has finished fading out, delete it! - overlayInfoSet.overlays.forEach(function(overlay) { - Overlays.deleteOverlay(overlay); - }); + overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; } else { // update overlay position, rotation to follow the object it's attached to. @@ -714,16 +721,36 @@ var equipHotspotBuddy = new EquipHotspotBuddy(); function MyController(hand) { this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - // this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - // this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - this.getHandRotation = function() { - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + this.autoUnequipCounter = 0; + + // handPosition is where the avatar's hand appears to be, in-world. + this.getHandPosition = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmPosition(); + } else { + return MyAvatar.getLeftPalmPosition(); + } + }; + this.getHandRotation = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmRotation(); + } else { + return MyAvatar.getLeftPalmRotation(); + } + }; + // controllerLocation is where the controller would be, in-world. + this.getControllerLocation = function (doOffset) { + var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + + var orientation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var position = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + // add to the real position so the grab-point is out in front of the hand, a bit + if (doOffset) { + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, GRAB_POINT_SPHERE_OFFSET)); + } + + return {position: position, orientation: orientation}; }; this.actionID = null; // action this script created... @@ -830,6 +857,39 @@ function MyController(hand) { } }; + this.grabPointSphereOn = function() { + if (!SHOW_GRAB_POINT_SPHERE) { + return; + } + if (!MyAvatar.sessionUUID) { + return; + } + if (!this.grabPointSphere) { + this.grabPointSphere = Overlays.addOverlay("sphere", { + localPosition: GRAB_POINT_SPHERE_OFFSET, + localRotation: { x: 0, y: 0, z: 0, w: 1 }, + dimensions: GRAB_POINT_SPHERE_RADIUS, + color: GRAB_POINT_SPHERE_COLOR, + alpha: GRAB_POINT_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false, + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND") + }); + } + }; + + this.grabPointSphereOff = function() { + if (this.grabPointSphere) { + Overlays.deleteOverlay(this.grabPointSphere); + this.grabPointSphere = null; + } + }; + this.searchSphereOn = function(location, size, color) { var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); @@ -905,10 +965,14 @@ function MyController(hand) { var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); if (PICK_WITH_HAND_RAY) { this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); } }; @@ -958,7 +1022,8 @@ function MyController(hand) { this.turnOffVisualizations = function() { this.overlayLineOff(); - + this.grabPointSphereOff(); + this.lineOff(); this.searchSphereOff(); restore2DMode(); @@ -1029,19 +1094,20 @@ function MyController(hand) { } if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + this.startingHandRotation = this.getControllerLocation(true).orientation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING, "trigger squeeze detected"); return; } } - var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + this.grabPointSphereOn(); + + var candidateEntities = Entities.findEntities(this.getControllerLocation(true).position, MAX_EQUIP_HOTSPOT_RADIUS); entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (!this.waitForTriggerRelease) { - this.updateEquipHaptics(potentialEquipHotspot, this.getHandPosition()); + this.updateEquipHaptics(potentialEquipHotspot, this.getControllerLocation(true).position); } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); @@ -1060,23 +1126,27 @@ function MyController(hand) { !potentialEquipHotspot && this.prevPotentialEquipHotspot) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; - } else if (potentialEquipHotspot && Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { + } else if (potentialEquipHotspot && + Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; } this.prevPotentialEquipHotspot = potentialEquipHotspot; }; + this.heartBeatIsStale = function(data) { + var now = Date.now(); + return data.heartBeat === undefined || now - data.heartBeat > HEART_BEAT_TIMEOUT; + }; + // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND // @returns {object} returns object with two keys entityID and distance // this.calcRayPickInfo = function(hand) { - - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; var pickRay = { origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, @@ -1196,7 +1266,7 @@ function MyController(hand) { var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && this.getOtherHandController().grabbedEntity == hotspot.entityID); - if (refCount > 0 && !okToEquipFromOtherHand) { + if (refCount > 0 && !this.heartBeatIsStale(grabProps) && !okToEquipFromOtherHand) { if (debug) { print("equip is skipping '" + props.name + "': grabbed by someone else"); } @@ -1213,20 +1283,21 @@ function MyController(hand) { var physical = propsArePhysical(props); var grabbable = false; var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; if (physical) { // physical things default to grabbable grabbable = true; } else { // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabProps && grabProps.refCount > 0) { + if (refCount > 0) { grabbable = true; } else { grabbable = false; } } - if (grabbableProps.hasOwnProperty("grabbable")) { + if (grabbableProps.hasOwnProperty("grabbable") && refCount === 0) { grabbable = grabbableProps.grabbable; } @@ -1321,7 +1392,7 @@ function MyController(hand) { return _this.collectEquipHotspots(entityID); })).filter(function(hotspot) { return (_this.hotspotIsEquippable(hotspot) && - Vec3.distance(hotspot.worldPosition, _this.getHandPosition()) < hotspot.radius + distance); + Vec3.distance(hotspot.worldPosition, _this.getControllerLocation(true).position) < hotspot.radius + distance); }); return equippableHotspots; }; @@ -1332,8 +1403,8 @@ function MyController(hand) { if (equippableHotspots.length > 0) { // sort by distance equippableHotspots.sort(function(a, b) { - var aDistance = Vec3.distance(a.worldPosition, this.getHandPosition()); - var bDistance = Vec3.distance(b.worldPosition, this.getHandPosition()); + var aDistance = Vec3.distance(a.worldPosition, this.getControllerLocation(true).position); + var bDistance = Vec3.distance(b.worldPosition, this.getControllerLocation(true).position); return aDistance - bDistance; }); return equippableHotspots[0]; @@ -1359,6 +1430,8 @@ function MyController(hand) { this.isInitialGrab = false; this.shouldResetParentOnRelease = false; + this.grabPointSphereOn(); + this.checkForStrayChildren(); if (this.triggerSmoothedReleased()) { @@ -1366,7 +1439,7 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); + var handPosition = this.getControllerLocation(true).position; var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1374,10 +1447,10 @@ function MyController(hand) { entityPropertiesCache.addEntity(rayPickInfo.entityID); } - var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); - entityPropertiesCache.addEntities(candidateEntities); + var candidateHotSpotEntities = Entities.findEntities(handPosition, MAX_EQUIP_HOTSPOT_RADIUS); + entityPropertiesCache.addEntities(candidateHotSpotEntities); - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateHotSpotEntities); if (potentialEquipHotspot) { if (this.triggerSmoothedGrab()) { this.grabbedHotspot = potentialEquipHotspot; @@ -1387,6 +1460,7 @@ function MyController(hand) { } } + var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); var grabbableEntities = candidateEntities.filter(function(entity) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); @@ -1546,18 +1620,13 @@ function MyController(hand) { }; this.distanceHoldingEnter = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','both'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'both'); this.clearEquipHaptics(); + this.grabPointSphereOff(); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); + var worldControllerPosition = this.getControllerLocation(true).position; - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - - // also transform the position into room space + // transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); @@ -1620,14 +1689,10 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - var worldControllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; // also transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); @@ -1698,8 +1763,6 @@ function MyController(hand) { } } - var handPosition = this.getHandPosition(); - // visualizations var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1764,10 +1827,7 @@ function MyController(hand) { }; this.dropGestureProcess = function(deltaTime) { - var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); - + var worldHandRotation = this.getControllerLocation(true).orientation; var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, @@ -1806,14 +1866,14 @@ function MyController(hand) { this.nearGrabbingEnter = function() { if (this.hand === 0) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'left'); - } if (this.hand === 1) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'right'); - } + this.grabPointSphereOff(); this.lineOff(); this.overlayLineOff(); + this.searchSphereOff(); this.dropGestureReset(); this.clearEquipHaptics(); @@ -1835,12 +1895,23 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties, false); - // var handRotation = this.getHandRotation(); - var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation(); - var handPosition = this.getHandPosition(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + if (FORCE_IGNORE_IK) { + this.ignoreIK = true; + } else { + this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + } + + var handRotation; + var handPosition; + if (this.ignoreIK) { + var controllerLocation = this.getControllerLocation(false); + handRotation = controllerLocation.orientation; + handPosition = controllerLocation.position; + } else { + handRotation = this.getHandRotation(); + handPosition = this.getHandPosition(); + } var hasPresetPosition = false; if (this.state == STATE_HOLD && this.grabbedHotspot) { @@ -1879,12 +1950,21 @@ function MyController(hand) { } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } else { // grab entity via parenting this.actionID = null; - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var handJointIndex; + if (this.ignoreIK) { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex, @@ -1892,14 +1972,15 @@ function MyController(hand) { angularVelocity: {x: 0, y: 0, z: 0} }; if (hasPresetPosition) { - reparentProps["localPosition"] = this.offsetPosition; - reparentProps["localRotation"] = this.offsetRotation; + reparentProps.localPosition = this.offsetPosition; + reparentProps.localRotation = this.offsetRotation; } Entities.editEntity(this.grabbedEntity, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } @@ -1937,6 +2018,8 @@ function MyController(hand) { this.nearGrabbing = function(deltaTime, timestamp) { + this.grabPointSphereOff(); + if (this.state == STATE_NEAR_GRABBING && !this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "trigger released"); @@ -1967,9 +2050,10 @@ function MyController(hand) { // store the offset attach points into preferences. if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { - var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (props && props.localPosition && props.localRotation) { - storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, props.localPosition, props.localRotation); + var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (prefprops && prefprops.localPosition && prefprops.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, + prefprops.localPosition, prefprops.localRotation); } } @@ -1988,22 +2072,34 @@ function MyController(hand) { "position", "rotation", "dimensions", "registrationPoint"]); if (!props.position) { - // server may have reset, taking our equipped entity with it. move back to "off" stte + // server may have reset, taking our equipped entity with it. move back to "off" state this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "entity has no position property"); return; } var now = Date.now(); - if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { + if (this.state == STATE_HOLD && now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID) { - var handPosition = this.getHandPosition(); + var handPosition; + if (this.ignoreIK) { + handPosition = this.getControllerLocation(false).position; + } else { + handPosition = this.getHandPosition(); + } var TEAR_AWAY_DISTANCE = 0.1; var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); if (dist > TEAR_AWAY_DISTANCE) { + this.autoUnequipCounter += 1; + } else { + this.autoUnequipCounter = 0; + } + + if (this.autoUnequipCounter > 1) { + // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + ", dist = " + dist); @@ -2076,16 +2172,15 @@ function MyController(hand) { }; this.nearTriggerEnter = function() { - this.clearEquipHaptics(); - + this.grabPointSphereOff(); Controller.triggerShortHapticPulse(1.0, this.hand); this.callEntityMethodOnGrabbed("startNearTrigger"); }; this.farTriggerEnter = function() { this.clearEquipHaptics(); - + this.grabPointSphereOff(); this.callEntityMethodOnGrabbed("startFarTrigger"); }; @@ -2105,10 +2200,9 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) + origin: this.getControllerLocation().position, + direction: Quat.getUp(this.getControllerLocation().orientation) }; var now = Date.now(); @@ -2137,7 +2231,7 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2162,7 +2256,7 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent; if (this.deadspotExpired) { @@ -2200,7 +2294,7 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { if (Entities.keyboardFocusEntity != this.grabbedEntity) { @@ -2237,7 +2331,7 @@ function MyController(hand) { }; this.release = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','none'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none'); this.turnOffVisualizations(); var noVelocity = false; @@ -2249,16 +2343,7 @@ function MyController(hand) { // If this looks like the release after adjusting something still held in the other hand, print the position // and rotation of the held thing to help content creators set the userData. var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (grabData.refCount > 1) { - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { - print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + - '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + - ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + - ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + - ', "w":' + grabbedProperties.localRotation.w + '}]'); - } - } + this.printNewOffsets = (grabData.refCount > 1); if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -2272,17 +2357,17 @@ function MyController(hand) { noVelocity = true; } } + + this.deactivateEntity(this.grabbedEntity, noVelocity); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); } - this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.grabbedEntity, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - this.grabbedEntity = null; this.grabbedHotspot = null; @@ -2293,13 +2378,14 @@ function MyController(hand) { this.cleanup = function() { this.release(); + this.grabPointSphereOff(); }; this.heartBeat = function(entityID) { var now = Date.now(); if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["heartBeat"] = now; + data.heartBeat = now; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.lastHeartBeat = now; } @@ -2308,12 +2394,14 @@ function MyController(hand) { this.resetAbandonedGrab = function(entityID) { print("cleaning up abandoned grab on " + entityID); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["refCount"] = 1; + data.refCount = 1; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.deactivateEntity(entityID, false); }; this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.autoUnequipCounter = 0; + if (this.entityActivated) { return; } @@ -2327,29 +2415,29 @@ function MyController(hand) { // get re-instated after all the grabs have been released) be correct. Script.clearTimeout(delayedDeactivateTimeout); delayedDeactivateTimeout = null; - grabbedProperties["collidesWith"] = delayedDeactivateFunc(); + grabbedProperties.collidesWith = delayedDeactivateFunc(); } var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var now = Date.now(); if (wasLoaded) { - data["refCount"] = 1; + data.refCount = 1; } else { - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + data.refCount = data.refCount ? data.refCount + 1 : 1; // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["heartBeat"] = now; + if (data.refCount == 1) { + data.heartBeat = now; this.lastHeartBeat = now; this.isInitialGrab = true; - data["gravity"] = grabbedProperties.gravity; - data["collidesWith"] = grabbedProperties.collidesWith; - data["collisionless"] = grabbedProperties.collisionless; - data["dynamic"] = grabbedProperties.dynamic; - data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; - data["parentJointIndex"] = grabbedProperties.parentJointIndex; + data.gravity = grabbedProperties.gravity; + data.collidesWith = grabbedProperties.collidesWith; + data.collisionless = grabbedProperties.collisionless; + data.dynamic = grabbedProperties.dynamic; + data.parentID = wasLoaded ? NULL_UUID : grabbedProperties.parentID; + data.parentJointIndex = grabbedProperties.parentJointIndex; var whileHeldProperties = { gravity: { @@ -2363,9 +2451,8 @@ function MyController(hand) { "collidesWith": COLLIDES_WITH_WHILE_GRABBED }; Entities.editEntity(entityID, whileHeldProperties); - } else if (data["refCount"] > 1) { - if (data["heartBeat"] === undefined || - now - data["heartBeat"] > HEART_BEAT_TIMEOUT) { + } else if (data.refCount > 1) { + if (this.heartBeatIsStale(data)) { // this entity has userData suggesting it is grabbed, but nobody is updating the hearbeat. // deactivate it before grabbing. this.resetAbandonedGrab(entityID); @@ -2380,7 +2467,8 @@ function MyController(hand) { // bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in // the collision mask hinges on who the physics simulation owner is. Entities.editEntity(entityID, { - "collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED + // "collidesWith": removeAvatarsFromCollidesWith(grabbedProperties.collidesWith) + collisionless: true }); } } @@ -2393,6 +2481,10 @@ function MyController(hand) { // unhook them. var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); + var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); children.forEach(function(childID) { print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); Entities.editEntity(childID, { @@ -2411,9 +2503,9 @@ function MyController(hand) { collidesWith: collidesWith }); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { data = null; } } else { @@ -2433,24 +2525,24 @@ function MyController(hand) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var doDelayedDeactivate = false; - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { deactiveProps = { - gravity: data["gravity"], + gravity: data.gravity, // don't set collidesWith myAvatar back right away, because thrown things tend to bounce off the // avatar's capsule. - collidesWith: removeMyAvatarFromCollidesWith(data["collidesWith"]), - collisionless: data["collisionless"], - dynamic: data["dynamic"], - parentID: data["parentID"], - parentJointIndex: data["parentJointIndex"] + collidesWith: removeMyAvatarFromCollidesWith(data.collidesWith), + collisionless: data.collisionless, + dynamic: data.dynamic, + parentID: data.parentID, + parentJointIndex: data.parentJointIndex }; - doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0); + doDelayedDeactivate = (data.collidesWith.indexOf("myAvatar") >= 0); if (doDelayedDeactivate) { - var delayedCollidesWith = data["collidesWith"]; + var delayedCollidesWith = data.collidesWith; var delayedEntityID = entityID; delayedDeactivateFunc = function() { // set collidesWith back to original value a bit later than the rest @@ -2470,19 +2562,19 @@ function MyController(hand) { if (!noVelocity && parentID == MyAvatar.sessionUUID && - Vec3.length(data["gravity"]) > 0.0 && - data["dynamic"] && - data["parentID"] == NULL_UUID && - !data["collisionless"]) { - deactiveProps["velocity"] = this.currentVelocity; + Vec3.length(data.gravity) > 0.0 && + data.dynamic && + data.parentID == NULL_UUID && + !data.collisionless) { + deactiveProps.velocity = this.currentVelocity; } if (noVelocity) { - deactiveProps["velocity"] = { + deactiveProps.velocity = { x: 0.0, y: 0.0, z: 0.0 }; - deactiveProps["angularVelocity"] = { + deactiveProps.angularVelocity = { x: 0.0, y: 0.0, z: 0.0 @@ -2508,6 +2600,17 @@ function MyController(hand) { } }; Entities.editEntity(entityID, deactiveProps); + + if (this.printNewOffsets) { + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { + print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + + ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + + ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + + ', "w":' + grabbedProperties.localRotation.w + '}]'); + } + } } else if (noVelocity) { Entities.editEntity(entityID, { velocity: { @@ -2520,7 +2623,7 @@ function MyController(hand) { y: 0.0, z: 0.0 }, - dynamic: data["dynamic"] + dynamic: data.dynamic }); } } else { @@ -2567,9 +2670,13 @@ function update(deltaTime) { if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { leftController.update(deltaTime, timestamp); + } else { + leftController.release(); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { rightController.update(deltaTime, timestamp); + } else { + rightController.release(); } equipHotspotBuddy.update(deltaTime, timestamp); entityPropertiesCache.update(); @@ -2620,7 +2727,7 @@ var handleHandMessages = function(channel, message, sender) { selectedController.nearGrabbingEnter(); } catch (e) { - print("WARNING: error parsing Hifi-Hand-Grab message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-Grab message: " + message); } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { @@ -2640,7 +2747,7 @@ var handleHandMessages = function(channel, message, sender) { } } catch (e) { - print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } } } diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index f3e94d23de..b4a8eefcd2 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -94,7 +94,6 @@ function Teleporter() { this.initialize = function() { this.createMappings(); - this.disableGrab(); }; this.createMappings = function() { @@ -218,10 +217,6 @@ function Teleporter() { this.updateConnected = null; this.inCoolIn = false; inTeleportMode = false; - - Script.setTimeout(function() { - _this.enableGrab(); - }, 200); }; this.update = function() { @@ -494,14 +489,6 @@ function Teleporter() { }); }; - this.disableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand); - }; - - this.enableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', 'none'); - }; - this.triggerHaptics = function() { var hand = this.teleportHand === 'left' ? 0 : 1; var haptic = Controller.triggerShortHapticPulse(0.2, hand);