diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index c84cfecb40..bac3b1e02f 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -17,11 +17,14 @@ #include "CharacterController.h" const uint16_t AvatarActionHold::holdVersion = 1; +const int AvatarActionHold::velocitySmoothFrames = 6; + AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) : ObjectActionSpring(id, ownerEntity) { _type = ACTION_TYPE_HOLD; + _measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames); #if WANT_DEBUG qDebug() << "AvatarActionHold::AvatarActionHold"; #endif @@ -204,8 +207,38 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { } withWriteLock([&]{ + if (_previousSet) { + glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; + _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity; + if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) { + _measuredLinearVelocitiesIndex = 0; + } + } + + glm::vec3 measuredLinearVelocity; + for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) { + // there is a bit of lag between when someone releases the trigger and when the software reacts to + // the release. we calculate the velocity from previous frames but we don't include several + // of the most recent. + // + // if _measuredLinearVelocitiesIndex is + // 0 -- ignore i of 3 4 5 + // 1 -- ignore i of 4 5 0 + // 2 -- ignore i of 5 0 1 + // 3 -- ignore i of 0 1 2 + // 4 -- ignore i of 1 2 3 + // 5 -- ignore i of 2 3 4 + if ((i + 1) % 6 == _measuredLinearVelocitiesIndex || + (i + 2) % 6 == _measuredLinearVelocitiesIndex || + (i + 3) % 6 == _measuredLinearVelocitiesIndex) { + continue; + } + measuredLinearVelocity += _measuredLinearVelocities[i]; + } + measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames - 3); // 3 because of the 3 we skipped, above + if (_kinematicSetVelocity) { - rigidBody->setLinearVelocity(glmToBullet(_linearVelocityTarget)); + rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget)); } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 609fd57ff3..bfa392172d 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -64,6 +64,10 @@ private: glm::vec3 _palmOffsetFromRigidBody; // leaving this here for future refernece. // glm::quat _palmRotationFromRigidBody; + + static const int velocitySmoothFrames; + QVector _measuredLinearVelocities; + int _measuredLinearVelocitiesIndex { 0 }; }; #endif // hifi_AvatarActionHold_h diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 94a10798fe..b66fca1448 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -13,7 +13,7 @@ /* global setEntityCustomData, getEntityCustomData, vec3toStr, flatten, Xform */ Script.include("/~/system/libraries/utils.js"); -Script.include("../libraries/Xform.js"); +Script.include("/~/system/libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -33,6 +33,8 @@ var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; +var COLLIDE_WITH_AV_AFTER_RELEASE_DELAY = 0.25; // seconds + var BUMPER_ON_VALUE = 0.5; var THUMB_ON_VALUE = 0.5; @@ -179,6 +181,10 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; +var delayedDeactivateFunc; +var delayedDeactivateTimeout; +var delayedDeactivateEntityID; + var CONTROLLER_STATE_MACHINE = {}; CONTROLLER_STATE_MACHINE[STATE_OFF] = { @@ -263,6 +269,17 @@ function propsArePhysical(props) { return isPhysical; } +function removeMyAvatarFromCollidesWith(origCollidesWith) { + var collidesWithSplit = origCollidesWith.split(","); + // remove myAvatar from the array + for (var i = collidesWithSplit.length - 1; i >= 0; i--) { + if (collidesWithSplit[i] === "myAvatar") { + collidesWithSplit.splice(i, 1); + } + } + return collidesWithSplit.join(); +} + // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; @@ -2061,6 +2078,17 @@ function MyController(hand) { } this.entityActivated = true; + if (delayedDeactivateTimeout && delayedDeactivateEntityID == entityID) { + // we have a timeout waiting to set collisions with myAvatar back on (so that when something + // is thrown it doesn't collide with the avatar's capsule the moment it's released). We've + // regrabbed the entity before the timeout fired, so cancel the timeout, run the function now + // and adjust the grabbedProperties. This will make the saved set of properties (the ones that + // get re-instated after all the grabs have been released) be correct. + Script.clearTimeout(delayedDeactivateTimeout); + delayedDeactivateTimeout = null; + grabbedProperties["collidesWith"] = delayedDeactivateFunc(); + } + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var now = Date.now(); @@ -2128,7 +2156,27 @@ function MyController(hand) { }); }; - this.deactivateEntity = function (entityID, noVelocity) { + this.delayedDeactivateEntity = function (entityID, collidesWith) { + // If, before the grab started, the held entity collided with myAvatar, we do the deactivation in + // two parts. Most of it is done in deactivateEntity(), but the final collidesWith and refcount + // are delayed a bit. This keeps thrown things from colliding with the avatar's capsule so often. + // The refcount is handled in this delayed fashion so things don't get confused if someone else + // grabs the entity before the timeout fires. + Entities.editEntity(entityID, { collidesWith: collidesWith }); + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + if (data && data["refCount"]) { + data["refCount"] = data["refCount"] - 1; + if (data["refCount"] < 1) { + data = null; + } + } else { + data = null; + } + + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + }; + + this.deactivateEntity = function (entityID, noVelocity, delayed) { var deactiveProps; if (!this.entityActivated) { @@ -2137,18 +2185,37 @@ function MyController(hand) { this.entityActivated = false; var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + var doDelayedDeactivate = false; if (data && data["refCount"]) { data["refCount"] = data["refCount"] - 1; if (data["refCount"] < 1) { deactiveProps = { gravity: data["gravity"], - collidesWith: data["collidesWith"], + // 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"] }; + doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0); + + if (doDelayedDeactivate) { + var delayedCollidesWith = data["collidesWith"]; + var delayedEntityID = entityID; + delayedDeactivateFunc = function () { + // set collidesWith back to original value a bit later than the rest + delayedDeactivateTimeout = null; + _this.delayedDeactivateEntity(delayedEntityID, delayedCollidesWith); + return delayedCollidesWith; + } + delayedDeactivateTimeout = + Script.setTimeout(delayedDeactivateFunc, COLLIDE_WITH_AV_AFTER_RELEASE_DELAY * MSECS_PER_SEC); + delayedDeactivateEntityID = entityID; + } + // things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If // it looks like the dropped thing should fall, give it a little velocity. var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]); @@ -2191,7 +2258,6 @@ function MyController(hand) { // angularVelocity: this.currentAngularVelocity }); } - data = null; } else if (this.shouldResetParentOnRelease) { // we parent-grabbed this from another parent grab. try to put it back where we found it. @@ -2210,7 +2276,9 @@ function MyController(hand) { } else { data = null; } - setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + if (!doDelayedDeactivate) { + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + } }; this.getOtherHandController = function () {