From 43c98b3f1e634f172bcc281f83774769f8546ddd Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 6 Sep 2016 14:42:53 -0700 Subject: [PATCH 01/14] attempt to avoid a very-still far-grab leaving something non-dynamic in bullet upon release. adjust position of grab-point sphere --- .../system/controllers/handControllerGrab.js | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 28488c9f3b..0f522c2c77 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -106,7 +106,7 @@ 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.07; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_RADIUS = 0.04; // 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. @@ -118,7 +118,7 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g 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_OFFSET = { x: 0.1, y: 0.175, z: -0.04 }; 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; @@ -722,6 +722,7 @@ var equipHotspotBuddy = new EquipHotspotBuddy(); function MyController(hand) { this.hand = hand; this.autoUnequipCounter = 0; + this.grabPointIntersectsEntity = false; // handPosition is where the avatar's hand appears to be, in-world. this.getHandPosition = function () { @@ -738,6 +739,18 @@ function MyController(hand) { return MyAvatar.getLeftPalmRotation(); } }; + + this.getGrabPointSphereOffset = function() { + if (hand === RIGHT_HAND) { + return GRAB_POINT_SPHERE_OFFSET; + } + return { + x: GRAB_POINT_SPHERE_OFFSET.x * -1, + y: GRAB_POINT_SPHERE_OFFSET.y, + z: GRAB_POINT_SPHERE_OFFSET.z * -1 + }; + }; + // controllerLocation is where the controller would be, in-world. this.getControllerLocation = function (doOffset) { var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; @@ -747,7 +760,7 @@ function MyController(hand) { 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)); + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, this.getGrabPointSphereOffset())); } return {position: position, orientation: orientation}; @@ -866,7 +879,7 @@ function MyController(hand) { } if (!this.grabPointSphere) { this.grabPointSphere = Overlays.addOverlay("sphere", { - localPosition: GRAB_POINT_SPHERE_OFFSET, + localPosition: this.getGrabPointSphereOffset(), localRotation: { x: 0, y: 0, z: 0, w: 1 }, dimensions: GRAB_POINT_SPHERE_RADIUS, color: GRAB_POINT_SPHERE_COLOR, @@ -1465,6 +1478,16 @@ function MyController(hand) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); + // before including any ray-picked entities, give a haptic pulse if the grab-point has hit something grabbable + if (grabbableEntities.length > 0) { + if (!this.grabPointIntersectsEntity) { + Controller.triggerHapticPulse(1, 20, this.hand); + this.grabPointIntersectsEntity = true; + } + } else { + this.grabPointIntersectsEntity = false; + } + if (rayPickInfo.entityID) { this.intersectionDistance = rayPickInfo.distance; if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { @@ -1683,6 +1706,15 @@ function MyController(hand) { if (!this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); + + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var velocity = Entities.getEntityProperties(this.grabbedEntity, ["velocity"]).velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z:0.0 }; + Entities.editEntity(this.grabbedEntity, { velocity: velocity }); + } + this.setState(STATE_OFF, "trigger released"); return; } From 215fd4ddfdb943ae0456fa619061f59b6d032990 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 6 Sep 2016 15:15:46 -0700 Subject: [PATCH 02/14] fix grab-point sphere for right hand --- scripts/system/controllers/handControllerGrab.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0f522c2c77..1700bde25e 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -118,7 +118,7 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks -var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: -0.04 }; +var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; 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; @@ -747,7 +747,7 @@ function MyController(hand) { return { x: GRAB_POINT_SPHERE_OFFSET.x * -1, y: GRAB_POINT_SPHERE_OFFSET.y, - z: GRAB_POINT_SPHERE_OFFSET.z * -1 + z: GRAB_POINT_SPHERE_OFFSET.z }; }; @@ -1481,7 +1481,8 @@ function MyController(hand) { // before including any ray-picked entities, give a haptic pulse if the grab-point has hit something grabbable if (grabbableEntities.length > 0) { if (!this.grabPointIntersectsEntity) { - Controller.triggerHapticPulse(1, 20, this.hand); + // Controller.triggerHapticPulse(1, 20, this.hand); + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.grabPointIntersectsEntity = true; } } else { From 34e4b4ae1964a249b34b3c83580b03caecd1344d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 6 Sep 2016 15:37:37 -0700 Subject: [PATCH 03/14] move haptic-pulse for when grab-point enters a grabbable entity to the right section of code --- .../system/controllers/handControllerGrab.js | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 1700bde25e..79331f0f3d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1116,11 +1116,14 @@ function MyController(hand) { this.grabPointSphereOn(); - var candidateEntities = Entities.findEntities(this.getControllerLocation(true).position, MAX_EQUIP_HOTSPOT_RADIUS); + var controllerLocation = this.getControllerLocation(true); + var worldHandPosition = controllerLocation.position; + + var candidateEntities = Entities.findEntities(worldHandPosition, MAX_EQUIP_HOTSPOT_RADIUS); entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (!this.waitForTriggerRelease) { - this.updateEquipHaptics(potentialEquipHotspot, this.getControllerLocation(true).position); + this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition); } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); @@ -1128,6 +1131,20 @@ function MyController(hand) { if (potentialEquipHotspot) { equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); } + + // when the grab-point enters a grabable entity, give a haptic pulse + candidateEntities = Entities.findEntities(worldHandPosition, NEAR_GRAB_RADIUS); + var grabbableEntities = candidateEntities.filter(function(entity) { + return _this.entityIsNearGrabbable(entity, worldHandPosition, NEAR_GRAB_MAX_DISTANCE); + }); + if (grabbableEntities.length > 0) { + if (!this.grabPointIntersectsEntity) { + Controller.triggerHapticPulse(1, 20, this.hand); + this.grabPointIntersectsEntity = true; + } + } else { + this.grabPointIntersectsEntity = false; + } }; this.clearEquipHaptics = function() { @@ -1478,17 +1495,6 @@ function MyController(hand) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); - // before including any ray-picked entities, give a haptic pulse if the grab-point has hit something grabbable - if (grabbableEntities.length > 0) { - if (!this.grabPointIntersectsEntity) { - // Controller.triggerHapticPulse(1, 20, this.hand); - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.grabPointIntersectsEntity = true; - } - } else { - this.grabPointIntersectsEntity = false; - } - if (rayPickInfo.entityID) { this.intersectionDistance = rayPickInfo.distance; if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { From 616b094111435c141cec9469f1e052fb55cc702f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 6 Sep 2016 16:16:09 -0700 Subject: [PATCH 04/14] experimenting with position of grab sphere --- scripts/system/controllers/handControllerGrab.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 79331f0f3d..cc43a57a68 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -118,7 +118,9 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks -var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; +// var GRAB_POINT_SPHERE_OFFSET = { x: 0, y: 0.2, z: 0 }; +// var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; +var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.32, z: 0.04 }; 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; From 624cc655ddbf1af5aa50cb8e32d2efc5ca271ef1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Sep 2016 13:24:53 -0700 Subject: [PATCH 05/14] disable code that backs-up in time to pick release velocity --- interface/src/avatar/AvatarActionHold.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 51171b9c6b..ce4455d921 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -244,14 +244,18 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { // 3 -- ignore i of 0 1 2 // 4 -- ignore i of 1 2 3 // 5 -- ignore i of 2 3 4 - if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { - continue; - } + + // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { + // continue; + // } + measuredLinearVelocity += _measuredLinearVelocities[i]; } - measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames - 3); // 3 because of the 3 we skipped, above + measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames + // - 3 // 3 because of the 3 we skipped, above + ); if (_kinematicSetVelocity) { rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); From b45181ea9210dcad5ba9e4295aae833b0ae4d4e5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Sep 2016 13:32:26 -0700 Subject: [PATCH 06/14] don't back up pick-ray from hand --- scripts/system/controllers/handControllerGrab.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index cc43a57a68..6f34ec4294 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1202,16 +1202,12 @@ function MyController(hand) { var directionNormalized = Vec3.normalize(pickRay.direction); var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = findRayIntersection(pickRayBacked, true, [], blacklist); + intersection = findRayIntersection(pickRay, true, [], blacklist); } else { - intersection = findRayIntersection(pickRayBacked, true); + intersection = findRayIntersection(pickRay, true); } if (intersection.intersects) { From 746d0cd91ce2c0b145ee51700d31f0d3081a2713 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Sep 2016 16:10:37 -0700 Subject: [PATCH 07/14] get various hand lasers to all line up with grab-point sphere --- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 22 ++++-- .../system/controllers/handControllerGrab.js | 78 +++++++------------ .../controllers/handControllerPointer.js | 12 ++- scripts/system/libraries/controllers.js | 46 +++++++++++ scripts/system/mod.js | 12 ++- 5 files changed, 99 insertions(+), 71 deletions(-) create mode 100644 scripts/system/libraries/controllers.js diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 01f607b0ed..28b19ee9e0 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -277,27 +277,37 @@ void HmdDisplayPlugin::updateFrameData() { continue; } - const auto& laserDirection = handLaser.direction; - auto model = _presentHandPoses[i]; - auto castDirection = glm::quat_cast(model) * laserDirection; + const vec3& laserDirection = handLaser.direction; + mat4 model = _presentHandPoses[i]; + vec3 castStart = vec3(model[3]); + vec3 castDirection = glm::quat_cast(model) * laserDirection; if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { castDirection = glm::normalize(castDirection); castDirection = glm::inverse(_presentUiModelTransform.getRotation()) * castDirection; } + // this offset needs to match GRAB_POINT_SPHERE_OFFSET in scripts/system/libraries/controllers.js + static const vec3 GRAB_POINT_SPHERE_OFFSET = vec3(0.1f, 0.04f, -0.32f); + vec3 grabPointOffset = GRAB_POINT_SPHERE_OFFSET; + if (i == 0) { + grabPointOffset.x *= -1.0f; // this changes between left and right hands + } + castStart += glm::quat_cast(model) * grabPointOffset; + // FIXME fetch the actual UI radius from... somewhere? float uiRadius = 1.0f; // Find the intersection of the laser with he UI and use it to scale the model matrix float distance; - if (!glm::intersectRaySphere(vec3(_presentHandPoses[i][3]), castDirection, _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { + if (!glm::intersectRaySphere(castStart, castDirection, + _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { continue; } - _presentHandLaserPoints[i].first = vec3(_presentHandPoses[i][3]); + _presentHandLaserPoints[i].first = castStart; _presentHandLaserPoints[i].second = _presentHandLaserPoints[i].first + (castDirection * distance); - vec3 intersectionPosition = vec3(_presentHandPoses[i][3]) + (castDirection * distance) - _presentUiModelTransform.getTranslation(); + vec3 intersectionPosition = castStart + (castDirection * distance) - _presentUiModelTransform.getTranslation(); intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; // Take the interesection normal and convert it to a texture coordinate diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 6f34ec4294..01be03c14a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -11,12 +11,13 @@ // // 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 */ +/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation */ (function() { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/Xform.js"); +Script.include("/~/system/libraries/controllers.js"); // // add lines where the hand ray picking is happening @@ -110,17 +111,12 @@ var NEAR_GRAB_RADIUS = 0.04; // 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. - -var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed // 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, y: 0.2, z: 0 }; -// var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; -var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.32, z: 0.04 }; 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; @@ -742,30 +738,8 @@ function MyController(hand) { } }; - this.getGrabPointSphereOffset = function() { - if (hand === RIGHT_HAND) { - return GRAB_POINT_SPHERE_OFFSET; - } - return { - x: GRAB_POINT_SPHERE_OFFSET.x * -1, - y: GRAB_POINT_SPHERE_OFFSET.y, - z: GRAB_POINT_SPHERE_OFFSET.z - }; - }; - - // 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, this.getGrabPointSphereOffset())); - } - - return {position: position, orientation: orientation}; + this.handToController = function() { + return (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; this.actionID = null; // action this script created... @@ -881,7 +855,7 @@ function MyController(hand) { } if (!this.grabPointSphere) { this.grabPointSphere = Overlays.addOverlay("sphere", { - localPosition: this.getGrabPointSphereOffset(), + localPosition: getGrabPointSphereOffset(this.handToController()), localRotation: { x: 0, y: 0, z: 0, w: 1 }, dimensions: GRAB_POINT_SPHERE_RADIUS, color: GRAB_POINT_SPHERE_COLOR, @@ -1109,7 +1083,7 @@ function MyController(hand) { } if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; - this.startingHandRotation = this.getControllerLocation(true).orientation; + this.startingHandRotation = getControllerWorldLocation(this.handToController(), true).orientation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING, "trigger squeeze detected"); return; @@ -1118,7 +1092,7 @@ function MyController(hand) { this.grabPointSphereOn(); - var controllerLocation = this.getControllerLocation(true); + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; var candidateEntities = Entities.findEntities(worldHandPosition, MAX_EQUIP_HOTSPOT_RADIUS); @@ -1176,7 +1150,7 @@ function MyController(hand) { // @returns {object} returns object with two keys entityID and distance // this.calcRayPickInfo = function(hand) { - var controllerLocation = this.getControllerLocation(true); + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; var worldHandRotation = controllerLocation.orientation; @@ -1200,9 +1174,6 @@ function MyController(hand) { } this.lastPickTime = now; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { intersection = findRayIntersection(pickRay, true, [], blacklist); @@ -1420,7 +1391,8 @@ function MyController(hand) { return _this.collectEquipHotspots(entityID); })).filter(function(hotspot) { return (_this.hotspotIsEquippable(hotspot) && - Vec3.distance(hotspot.worldPosition, _this.getControllerLocation(true).position) < hotspot.radius + distance); + Vec3.distance(hotspot.worldPosition, getControllerWorldLocation(_this.handToController(), true).position) < + hotspot.radius + distance); }); return equippableHotspots; }; @@ -1431,8 +1403,9 @@ function MyController(hand) { if (equippableHotspots.length > 0) { // sort by distance equippableHotspots.sort(function(a, b) { - var aDistance = Vec3.distance(a.worldPosition, this.getControllerLocation(true).position); - var bDistance = Vec3.distance(b.worldPosition, this.getControllerLocation(true).position); + var handControllerLocation = getControllerWorldLocation(this.handToController(), true); + var aDistance = Vec3.distance(a.worldPosition, handControllerLocation.position); + var bDistance = Vec3.distance(b.worldPosition, handControllerLocation.position); return aDistance - bDistance; }); return equippableHotspots[0]; @@ -1467,7 +1440,7 @@ function MyController(hand) { return; } - var handPosition = this.getControllerLocation(true).position; + var handPosition = getControllerWorldLocation(this.handToController(), true).position; var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1652,7 +1625,7 @@ function MyController(hand) { this.clearEquipHaptics(); this.grabPointSphereOff(); - var worldControllerPosition = this.getControllerLocation(true).position; + var worldControllerPosition = getControllerWorldLocation(this.handToController(), true).position; // transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); @@ -1727,7 +1700,7 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - var controllerLocation = this.getControllerLocation(true); + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; @@ -1864,7 +1837,7 @@ function MyController(hand) { }; this.dropGestureProcess = function(deltaTime) { - var worldHandRotation = this.getControllerLocation(true).orientation; + var worldHandRotation = getControllerWorldLocation(this.handToController(), true).orientation; var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, @@ -1942,7 +1915,7 @@ function MyController(hand) { var handRotation; var handPosition; if (this.ignoreIK) { - var controllerLocation = this.getControllerLocation(false); + var controllerLocation = getControllerWorldLocation(this.handToController(), false); handRotation = controllerLocation.orientation; handPosition = controllerLocation.position; } else { @@ -2122,7 +2095,7 @@ function MyController(hand) { if (props.parentID == MyAvatar.sessionUUID) { var handPosition; if (this.ignoreIK) { - handPosition = this.getControllerLocation(false).position; + handPosition = getControllerWorldLocation(this.handToController(), false).position; } else { handPosition = this.getHandPosition(); } @@ -2238,8 +2211,8 @@ function MyController(hand) { } var pickRay = { - origin: this.getControllerLocation().position, - direction: Quat.getUp(this.getControllerLocation().orientation) + origin: getControllerWorldLocation(this.handToController(), false).position, + direction: Quat.getUp(getControllerWorldLocation(this.handToController(), false).orientation) }; var now = Date.now(); @@ -2268,7 +2241,8 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2293,7 +2267,8 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { var pointerEvent; if (this.deadspotExpired) { @@ -2331,7 +2306,8 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, + getControllerWorldLocation(this.handToController(), true)); if (intersectInfo) { if (Entities.keyboardFocusEntity != this.grabbedEntity) { diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 6b62674be5..ce98ed6d8e 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -20,6 +20,7 @@ // When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand // controller beam intersects the HUD. +Script.include("/~/system/libraries/controllers.js"); // UTILITIES ------------- // @@ -203,16 +204,13 @@ function overlayFromWorldPoint(point) { } function activeHudPoint2d(activeHand) { // if controller is valid, update reticle position and answer 2d point. Otherwise falsey. - var controllerPose = Controller.getPoseValue(activeHand); - // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) + var controllerPose = getControllerWorldLocation(activeHand, true); if (!controllerPose.valid) { return; // Controller is cradled. } - var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), - MyAvatar.position); - // This gets point direction right, but if you want general quaternion it would be more complicated: - var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); - + var controllerPosition = controllerPose.position; + var controllerDirection = Quat.getUp(controllerPose.rotation); + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js new file mode 100644 index 0000000000..a1318ca47c --- /dev/null +++ b/scripts/system/libraries/controllers.js @@ -0,0 +1,46 @@ +// handControllerGrab.js +// +// Created by Seth Alves on 2016-9-7 +// Copyright 2016 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 +/* global MyAvatar, Vec3, Controller, Quat */ + + +// var GRAB_POINT_SPHERE_OFFSET = { x: 0, y: 0.2, z: 0 }; +// var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; + +// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.32, z: 0.04 }; + +getGrabPointSphereOffset = function(handController) { + if (handController === Controller.Standard.RightHand) { + return GRAB_POINT_SPHERE_OFFSET; + } + return { + x: GRAB_POINT_SPHERE_OFFSET.x * -1, + y: GRAB_POINT_SPHERE_OFFSET.y, + z: GRAB_POINT_SPHERE_OFFSET.z + }; +}; + +// controllerLocation is where the controller would be, in-world. +getControllerWorldLocation = function (handController, doOffset) { + var orientation; + var position; + var pose = Controller.getPoseValue(handController); + if (pose.valid) { + orientation = Quat.multiply(MyAvatar.orientation, pose.rotation); + 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, getGrabPointSphereOffset(handController))); + } + } + return {position: position, + translation: position, + orientation: orientation, + rotation: orientation, + valid: pose.valid}; +}; diff --git a/scripts/system/mod.js b/scripts/system/mod.js index b2c9785539..4db1576168 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -10,6 +10,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ + (function() { // BEGIN LOCAL_SCOPE @@ -144,7 +146,7 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ function handleSelectedOverlay(clickedOverlay) { // see this is one of our mod overlays - var modOverlayKeys = Object.keys(modOverlays) + var modOverlayKeys = Object.keys(modOverlays); for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; var modOverlay = modOverlays[avatarID]; @@ -187,13 +189,9 @@ Controller.mousePressEvent.connect(function(event){ var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); function controllerComputePickRay(hand) { - var controllerPose = Controller.getPoseValue(hand); + var controllerPose = getControllerWorldLocation(hand, true); if (controllerPose.valid) { - var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), - MyAvatar.position); - // This gets point direction right, but if you want general quaternion it would be more complicated: - var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); - return { origin: controllerPosition, direction: controllerDirection }; + return { origin: controllerPose.position, direction: controllerPose.orientation }; } } From 9c96ffc9cb42ff42274b29f7687f81878ccc8309 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Sep 2016 16:31:02 -0700 Subject: [PATCH 08/14] don't show grab-point spheres if controller poses aren't valid --- .../system/controllers/handControllerGrab.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 01be03c14a..588f5f95b6 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -11,7 +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, getControllerWorldLocation */ +/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset */ (function() { // BEGIN LOCAL_SCOPE @@ -1090,11 +1090,16 @@ function MyController(hand) { } } - this.grabPointSphereOn(); var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; + if (controllerLocation.valid) { + this.grabPointSphereOn(); + } else { + this.grabPointSphereOff(); + } + var candidateEntities = Entities.findEntities(worldHandPosition, MAX_EQUIP_HOTSPOT_RADIUS); entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); @@ -1431,8 +1436,6 @@ function MyController(hand) { this.isInitialGrab = false; this.shouldResetParentOnRelease = false; - this.grabPointSphereOn(); - this.checkForStrayChildren(); if (this.triggerSmoothedReleased()) { @@ -1440,7 +1443,14 @@ function MyController(hand) { return; } - var handPosition = getControllerWorldLocation(this.handToController(), true).position; + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var handPosition = controllerLocation.position; + + if (controllerLocation.valid) { + this.grabPointSphereOn(); + } else { + this.grabPointSphereOff(); + } var rayPickInfo = this.calcRayPickInfo(this.hand); From 9e99cc1f7879156d51af33f759be3170c1303499 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Sep 2016 07:35:52 -0700 Subject: [PATCH 09/14] fix mod.js --- scripts/system/libraries/controllers.js | 2 +- scripts/system/mod.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js index a1318ca47c..9bae46380e 100644 --- a/scripts/system/libraries/controllers.js +++ b/scripts/system/libraries/controllers.js @@ -25,7 +25,7 @@ getGrabPointSphereOffset = function(handController) { }; }; -// controllerLocation is where the controller would be, in-world. +// controllerWorldLocation is where the controller would be, in-world, with an added offset getControllerWorldLocation = function (handController, doOffset) { var orientation; var position; diff --git a/scripts/system/mod.js b/scripts/system/mod.js index 4db1576168..1a7b3b401e 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -15,6 +15,8 @@ (function() { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/controllers.js"); + // grab the toolbar var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); From d4faf69beb9536bacb62de23b1d3012447c7e85e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Sep 2016 15:42:42 -0700 Subject: [PATCH 10/14] don't cripple interface with log-spam --- libraries/physics/src/EntityMotionState.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index b70927d501..951728c4da 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "BulletUtil.h" #include "EntityMotionState.h" @@ -230,11 +231,17 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { bool positionSuccess; _entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false); if (!positionSuccess) { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " + "setPosition failed.*"); qDebug() << "EntityMotionState::setWorldTransform setPosition failed" << _entity->getID(); } bool orientationSuccess; _entity->setOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false); if (!orientationSuccess) { + static QString repeatedMessage = + LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " + "setOrientation failed.*"); qDebug() << "EntityMotionState::setWorldTransform setOrientation failed" << _entity->getID(); } _entity->setVelocity(getBodyLinearVelocity()); From 0f98c51d89416fae07964a5f276f681016476f0e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Sep 2016 16:13:22 -0700 Subject: [PATCH 11/14] distance-grabbed things collide with static entities --- .../system/controllers/handControllerGrab.js | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 588f5f95b6..a989b257c1 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1653,7 +1653,8 @@ function MyController(hand) { this.grabRadius = Vec3.distance(this.currentObjectPosition, worldControllerPosition); this.grabRadialVelocity = 0.0; - // compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object + // compute a constant based on the initial conditions which we use below to exagerate hand motion + // onto the held object this.radiusScalar = Math.log(this.grabRadius + 1.0); if (this.radiusScalar < 1.0) { this.radiusScalar = 1.0; @@ -1679,7 +1680,7 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID !== null) { - this.activateEntity(this.grabbedEntity, grabbedProperties, false); + this.activateEntity(this.grabbedEntity, grabbedProperties, false, true); this.callEntityMethodOnGrabbed("startDistanceGrab"); } @@ -1709,7 +1710,6 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; @@ -1913,7 +1913,7 @@ function MyController(hand) { } var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties, false); + this.activateEntity(this.grabbedEntity, grabbedProperties, false, false); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (FORCE_IGNORE_IK) { @@ -2422,7 +2422,7 @@ function MyController(hand) { this.deactivateEntity(entityID, false); }; - this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.activateEntity = function(entityID, grabbedProperties, wasLoaded, collideWithStatic) { this.autoUnequipCounter = 0; if (this.entityActivated) { @@ -2463,15 +2463,10 @@ function MyController(hand) { data.parentJointIndex = grabbedProperties.parentJointIndex; var whileHeldProperties = { - gravity: { - x: 0, - y: 0, - z: 0 - }, - // bummer, it isn't easy to do bitwise collisionMask operations like this: - // "collisionMask": COLLISION_MASK_WHILE_GRABBED | grabbedProperties.collisionMask - // when using string values - "collidesWith": COLLIDES_WITH_WHILE_GRABBED + gravity: { x: 0, y: 0, z: 0 }, + "collidesWith": collideWithStatic ? + COLLIDES_WITH_WHILE_GRABBED + ",static" : + COLLIDES_WITH_WHILE_GRABBED }; Entities.editEntity(entityID, whileHeldProperties); } else if (data.refCount > 1) { @@ -2480,7 +2475,7 @@ function MyController(hand) { // deactivate it before grabbing. this.resetAbandonedGrab(entityID); grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - return this.activateEntity(entityID, grabbedProperties, wasLoaded); + return this.activateEntity(entityID, grabbedProperties, wasLoaded, false); } this.isInitialGrab = false; From ecb06699721b644e44659558ebe4d006ea0db16a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Sep 2016 17:02:29 -0700 Subject: [PATCH 12/14] try to keep far grab from getting stuck if the entity is brought very near to the grab-point --- scripts/system/controllers/handControllerGrab.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index a989b257c1..178e20a9cf 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1756,7 +1756,13 @@ function MyController(hand) { var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; } var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); From 8734661a0e383668f85097bae9b9ba234df149a5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 14 Sep 2016 11:28:22 -0700 Subject: [PATCH 13/14] add explaining comment --- interface/src/avatar/AvatarActionHold.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index ce4455d921..b9e830b889 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -245,6 +245,7 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { // 4 -- ignore i of 1 2 3 // 5 -- ignore i of 2 3 4 + // This code is now disabled, but I'm leaving it comment-out because I suspect it will come back. // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { From 59db640cb54c723d0c2011534663281978311d9b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 14 Sep 2016 11:33:03 -0700 Subject: [PATCH 14/14] add explaining comment --- interface/src/avatar/AvatarActionHold.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index b9e830b889..8b8f8e8c2e 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -245,7 +245,7 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { // 4 -- ignore i of 1 2 3 // 5 -- ignore i of 2 3 4 - // This code is now disabled, but I'm leaving it comment-out because I suspect it will come back. + // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {