diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 02fc09c815..8a7744efb3 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -56,29 +56,28 @@ { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] }, { "from": "Vive.Head", "to" : "Standard.Head"}, - { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, - + { "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" }, { "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" }, { "from": "Vive.TrackedObject02", "to" : "Standard.TrackedObject02" }, diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 1f9caf747a..3f5cad655d 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -597,18 +597,11 @@ Item { // Function body by Howard Stearns 2017-01-08 function goToUserInDomain(avatarUuid) { var avatar = AvatarList.getAvatar(avatarUuid); - if (!avatar) { + if (!avatar || !avatar.position || !avatar.orientation) { console.log("This avatar is no longer present. goToUserInDomain() failed."); return; } - // FIXME: We would like the avatar to recompute the avatar's "maybe fly" test at the new position, so that if high enough up, - // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. - // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. - // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. - // Position avatar 2 metres from the target in the direction that target avatar was facing. - MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); - - // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch. - MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1})); + // This is the last step of what AddressManager.goToUser does, but we don't need to resolve the username. + MyAvatar.goToLocation(avatar.position, true, Quat.cancelOutRollAndPitch(avatar.orientation), true); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 02a1959a95..68e417ba1d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -424,6 +424,7 @@ void MyAvatar::update(float deltaTime) { emit positionGoneTo(); // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. _physicsSafetyPending = getCollisionsEnabled(); + _characterController.recomputeFlying(); // In case we've gone to into the sky. } if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. @@ -2315,6 +2316,19 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::quat& newOrientation, bool shouldFaceLocation) { + // Most cases of going to a place or user go through this now. Some possible improvements to think about in the future: + // - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it + // still worked if the target is in the air. + // - Sometimes (such as the response from /api/v1/users/:username/location), the location can be stale, but there is a + // node_id supplied by which we could update the information after going to the stale location first and "looking around". + // This could be passed through AddressManager::goToAddressFromObject => AddressManager::handleViewpoint => here. + // The trick is that you have to yield enough time to resolve the node_id. + // - Instead of always doing the same thing for shouldFaceLocation -- which places users uncomfortabley on top of each other -- + // it would be nice to see how many users are already "at" a person or place, and place ourself in semicircle or other shape + // around the target. Avatars and entities (specified by the node_id) could define an adjustable "face me" method that would + // compute the position (e.g., so that if I'm on stage, going to me would compute an available seat in the audience rather than + // being in my face on-stage). Note that this could work for going to an entity as well as to a person. + qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", " << newPosition.y << ", " << newPosition.z; @@ -3152,6 +3166,7 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const { } bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) { + std::lock_guard guard(_pinnedJointsMutex); auto hipsIndex = getJointIndex("Hips"); if (index != hipsIndex) { qWarning() << "Pinning is only supported for the hips joint at the moment."; @@ -3171,7 +3186,14 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o return true; } +bool MyAvatar::isJointPinned(int index) { + std::lock_guard guard(_pinnedJointsMutex); + auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); + return it != _pinnedJoints.end(); +} + bool MyAvatar::clearPinOnJoint(int index) { + std::lock_guard guard(_pinnedJointsMutex); auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index); if (it != _pinnedJoints.end()) { _pinnedJoints.erase(it); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7c9513cb3e..ab74460d4e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -448,9 +448,8 @@ public: virtual void clearJointData(const QString& name) override; virtual void clearJointsData() override; - - Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation); + bool isJointPinned(int index); Q_INVOKABLE bool clearPinOnJoint(int index); Q_INVOKABLE float getIKErrorOnLastSolve() const; @@ -837,6 +836,7 @@ private: bool getIsAway() const { return _isAway; } void setAway(bool value); + std::mutex _pinnedJointsMutex; std::vector _pinnedJoints; // height of user in sensor space, when standing erect. diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index f249be33ea..50e0474831 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -34,12 +34,25 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle } static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { + + glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); + + // check for pinned hips. + auto hipsIndex = myAvatar->getJointIndex("Hips"); + if (myAvatar->isJointPinned(hipsIndex)) { + Transform avatarTransform = myAvatar->getTransform(); + AnimPose result = AnimPose(worldToSensorMat * avatarTransform.getMatrix() * Matrices::Y_180); + result.scale() = glm::vec3(1.0f, 1.0f, 1.0f); + return result; + } else { + DebugDraw::getInstance().removeMarker("pinnedHips"); + } + glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor(); glm::vec3 hipsPos = extractTranslation(hipsMat); glm::quat hipsRot = glmExtractRotation(hipsMat); glm::mat4 avatarToWorldMat = myAvatar->getTransform().getMatrix(); - glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat; // dampen hips rotation, by mixing it with the avatar orientation in sensor space diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index b6c3ed4d27..6e6dc816d0 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -30,6 +30,7 @@ #include "filters/PostTransformFilter.h" #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" +#include "filters/ExponentialSmoothingFilter.h" using namespace controller; @@ -49,6 +50,7 @@ REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform") REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity") +REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); @@ -93,7 +95,7 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri output = objectParameters[name].toDouble(); return true; } - } + } return false; } @@ -117,7 +119,7 @@ bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output) objectParameters["z"].toDouble()); return true; } - } + } return false; } @@ -126,7 +128,7 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) auto objectParameters = parameters.toObject(); - if (objectParameters.contains("r0c0") && + if (objectParameters.contains("r0c0") && objectParameters.contains("r1c0") && objectParameters.contains("r2c0") && objectParameters.contains("r3c0") && @@ -169,9 +171,9 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output) bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) { if (parameters.isObject()) { auto objectParameters = parameters.toObject(); - if (objectParameters.contains("w") && - objectParameters.contains("x") && - objectParameters.contains("y") && + if (objectParameters.contains("w") && + objectParameters.contains("x") && + objectParameters.contains("y") && objectParameters.contains("z")) { output = glm::quat(objectParameters["w"].toDouble(), diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index bc1ef55725..048e23be1c 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -32,6 +32,7 @@ #include "filters/PostTransformFilter.h" #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" +#include "filters/ExponentialSmoothingFilter.h" #include "conditionals/AndConditional.h" using namespace controller; @@ -134,6 +135,11 @@ QObject* RouteBuilderProxy::lowVelocity(float rotationConstant, float translatio return this; } +QObject* RouteBuilderProxy::exponentialSmoothing(float rotationConstant, float translationConstant) { + addFilter(std::make_shared(rotationConstant, translationConstant)); + return this; +} + QObject* RouteBuilderProxy::constrainToInteger() { addFilter(std::make_shared()); return this; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 75f3747566..92a87e5e39 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -53,6 +53,7 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* postTransform(glm::mat4 transform); Q_INVOKABLE QObject* rotate(glm::quat rotation); Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant); + Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant); Q_INVOKABLE QObject* logicalNot(); private: diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp new file mode 100644 index 0000000000..9cf2673d55 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp @@ -0,0 +1,71 @@ +// +// Created by Anthony Thibault 2017/12/07 +// Copyright 2017 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 "ExponentialSmoothingFilter.h" + +#include +#include +#include "../../UserInputMapper.h" +#include "../../Input.h" +#include + +static const QString JSON_ROTATION = QStringLiteral("rotation"); +static const QString JSON_TRANSLATION = QStringLiteral("translation"); +namespace controller { + + Pose ExponentialSmoothingFilter::apply(Pose value) const { + + if (value.isValid()) { + + // to perform filtering in sensor space, we need to compute the transformations. + auto userInputMapper = DependencyManager::get(); + const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData(); + glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat; + glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat; + + // transform pose into sensor space. + Pose sensorValue = value.transform(avatarToSensorMat); + + if (_prevSensorValue.isValid()) { + // exponential smoothing filter + sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation(); + sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant); + + // remember previous sensor space value. + _prevSensorValue = sensorValue; + + // transform back into avatar space + return sensorValue.transform(sensorToAvatarMat); + } else { + // remember previous sensor space value. + _prevSensorValue = sensorValue; + + // no previous value to smooth with, so return value unchanged + return value; + } + } else { + // return invalid value unchanged + return value; + } + } + + bool ExponentialSmoothingFilter::parseParameters(const QJsonValue& parameters) { + + if (parameters.isObject()) { + auto obj = parameters.toObject(); + if (obj.contains(JSON_ROTATION) && obj.contains(JSON_TRANSLATION)) { + _rotationConstant = glm::clamp((float)obj[JSON_ROTATION].toDouble(), 0.0f, 1.0f); + _translationConstant = glm::clamp((float)obj[JSON_TRANSLATION].toDouble(), 0.0f, 1.0f); + return true; + } + } + return false; + } + +} diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h new file mode 100644 index 0000000000..134f57243e --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h @@ -0,0 +1,42 @@ +// +// Created by Anthony Thibault 2017/12/17 +// Copyright 2017 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_Controllers_Filters_Exponential_Smoothing_h +#define hifi_Controllers_Filters_Exponential_Smoothing_h + +#include "../Filter.h" + +namespace controller { + + class ExponentialSmoothingFilter : public Filter { + REGISTER_FILTER_CLASS(ExponentialSmoothingFilter); + + public: + ExponentialSmoothingFilter() {} + ExponentialSmoothingFilter(float rotationConstant, float translationConstant) : + _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} + + float apply(float value) const override { return value; } + Pose apply(Pose value) const override; + bool parseParameters(const QJsonValue& parameters) override; + + private: + + // Constant between 0 and 1. + // 1 indicates no smoothing at all, poses are passed through unaltered. + // Values near 1 are less smooth with lower latency. + // Values near 0 are more smooth with higher latency. + float _translationConstant { 0.375f }; + float _rotationConstant { 0.375f }; + + mutable Pose _prevSensorValue { Pose() }; // sensor space + }; + +} + +#endif diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index b884dcba17..21815e065a 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -596,7 +596,7 @@ bool AddressManager::handleDomainID(const QString& host) { void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly) { if (!handleViewpoint(path, false, trigger, wasPathOnly)) { qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path << - "- wll attempt to ask domain-server to resolve."; + "- will attempt to ask domain-server to resolve."; if (!wasPathOnly) { // if we received a path with a host then we need to remember what it was here so we can not diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 32e764bd10..d39930ab76 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -391,6 +391,10 @@ void CharacterController::setState(State desiredState) { } } +void CharacterController::recomputeFlying() { + _pendingFlags |= PENDING_FLAG_RECOMPUTE_FLYING; +} + void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) { float x = scale.x; float z = scale.z; @@ -657,6 +661,13 @@ void CharacterController::updateState() { if (!_dynamicsWorld) { return; } + if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) { + SET_STATE(CharacterController::State::Hover, "recomputeFlying"); + _hasSupport = false; + _stepHeight = _minStepHeight; // clears memory of last step obstacle + _pendingFlags &= ~PENDING_FLAG_RECOMPUTE_FLYING; + } + const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius; const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight; const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND; diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 0f97cc7c16..96e479dcad 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -31,6 +31,7 @@ const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; const uint32_t PENDING_FLAG_JUMP = 1U << 3; const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; +const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); class btRigidBody; @@ -54,6 +55,7 @@ public: void setGravity(float gravity); float getGravity(); + void recomputeFlying(); virtual void updateShapeIfNecessary() = 0;