From 49bd62ca299d629c8cf133d32d19f329fdc00156 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 29 Dec 2017 14:04:36 -0700 Subject: [PATCH 01/10] Touch feeling for near grab --- interface/src/avatar/MySkeletonModel.cpp | 2 +- .../controllers/controllerDispatcher.js | 1 + scripts/system/libraries/handTouch.js | 395 ++++++++++++++++++ 3 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 scripts/system/libraries/handTouch.js diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index f249be33ea..ede6a606c4 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -332,7 +332,7 @@ void MySkeletonModel::updateFingers() { } prevAbsRot = pose.getRotation(); } else { - _rig.clearJointAnimationPriority(index); + // _rig.clearJointAnimationPriority(index); } } } diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 51f927f224..24ac757f60 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -20,6 +20,7 @@ controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/handTouch.js"); (function() { Script.include("/~/system/libraries/pointersUtils.js"); diff --git a/scripts/system/libraries/handTouch.js b/scripts/system/libraries/handTouch.js new file mode 100644 index 0000000000..5f917f7247 --- /dev/null +++ b/scripts/system/libraries/handTouch.js @@ -0,0 +1,395 @@ +// +// scripts/system/libraries/handTouch.js +// +// Created by Luis Cuenca on 12/29/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 +// + +(function(){ + + var animating = false; + var updateFingerWithIndex = 0; + var dataKeys = ["pinky", "ring", "middle", "index", "thumb"]; + + var grabPercent = { left: 0, + right: 0 }; + + var isGrabbing = false; + + var dataOpen = { + left: { + pinky: [{x: -0.18262, y:-2.666, z:-25.11229}, {x: 1.28845, y:0, z:1.06604}, {x: -3.967, y:0, z:-0.8351} ], + middle: [{x: -0.18262, y:0, z:-3.2809}, {x: 1.28845, y:0, z:-0.71834}, {x: -3.967, y:0, z:0.83978}], + ring: [{x: -0.18262, y:-1.11078, z:-16.24391}, {x: 1.28845, y:0, z:0.68153}, {x: -3.967, y:0, z:-0.69295}], + thumb: [{x: 5.207, y:2.595, z:38.40092}, {x: -9.869, y:11.755, z:10.50012}, {x: -9.778, y:9.647, z:15.16963}], + index: [{x: -0.18262, y:0.0099, z:2.28085}, {x: 1.28845, y:-0.01107, z:0.93037}, {x: -3.967, y:0, z:-2.64018}] + }, right: { + pinky: [{x: -0.111, y: 2.66601, z: 12.06423}, {x: 1.217, y: 0, z: -1.03973}, {x: -3.967, y: 0, z: 0.86424}], + middle: [{x: -0.111, y: 0, z: 3.26538}, {x: 1.217, y: 0, z: 0.71427}, {x: -3.967, y: 0, z: -0.85103}], + ring: [{x: -0.111, y: 1.11101, z: 3.56312}, {x: 1.217, y: 0, z: -0.64524}, {x: -3.967, y: 0, z: 0.69807}], + thumb: [{x: 5.207, y: -2.595, z: -38.26131}, {x: -9.869, y: -11.755, z: -10.51778}, {x: -9.77799, y: -9.647, z: -15.10783}], + index: [{x: -0.111, y: 0, z: -2.2816}, {x: 1.217, y: 0, z: -0.90168}, {x: -3.967, y: 0, z: 2.62649}] + } + } + + var dataClose = { + left: { + pinky:[{x: 75.45709, y:-8.01347, z:-22.54823}, {x: 69.562, y:0, z:1.06604}, {x: 74.73801, y:0, z:-0.8351}], + middle: [{x: 66.0237, y:-2.42536, z:-6.13193}, {x: 72.63042, y:0, z:-0.71834}, {x: 75.19901, y:0, z:0.83978}], + ring: [{x: 71.52988, y:-2.35423, z:-16.21694}, {x: 64.44739, y:0, z:0.68153}, {x: 70.518, y:0, z:-0.69295}], + thumb: [{x: 33.83371, y:-15.19106, z:34.66116}, {x: 0, y:0, z:-43.42915}, {x: 0, y:0, z:-30.18613}], + index: [{x: 35.56082, y:-1.21056, z:-2.07362}, {x: 79.79845, y:-0.01107, z:0.93037}, {x: 68.767, y:0, z:-2.64018}] + }, right: { + pinky:[{x: 75.45702, y: 8.013, z: 22.41022}, {x: 69.562, y: 0, z: -1.03973}, {x: 74.738, y: 0, z: 0.86424}], + middle: [{x: 66.02399, y: 2.425, z: 6.11638}, {x: 72.63002, y: 0, z: 0.71427}, {x: 72.63, y: 0, z: -0.85103}], + ring: [{x: 71.53, y: 5.022, z: 16.33612}, {x: 64.447, y: 0, z: -0.64524}, {x: 70.51801, y: 0, z: 0.69807}], + thumb: [{x: 33.834, y: 15.191, z: -34.52131}, {x: 0, y: 0, z: 43.41122}, {x: 0, y: 0, z: 30.24818}], + index: [{x: 35.633, y: 1.215, z: -6.6376}, {x: 79.72701, y: 0, z: -0.90168}, {x: 68.76701, y: 0, z: 2.62649}] + } + } + + var dataCurrent = { + left:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + }, + right:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + } + } + + var dataDelta = { + left:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + }, + right:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + } + } + + var animationSteps = 5; + + var showSphere = false; + var showLines = false; + + var fingerRays = { + left:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}, + right:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined} + }; + + var varsToDebug = { + toggleDebugSphere: function(){ + showSphere = !showSphere; + }, + toggleDebugLines: function(){ + showLines = !showLines; + }, + fingerPercent: { + left: { + pinky: 0.5, + middle: 0.5, + ring: 0.5, + thumb: 0.5, + index: 0.5 + } , + right: { + pinky: 0.5, + middle: 0.5, + ring: 0.5, + thumb: 0.5, + index: 0.5 + } + }, + triggerValues: { + leftTriggerValue: 0, + leftTriggerClicked: 0, + rightTriggerValue: 0, + rightTriggerClicked: 0, + leftSecondaryValue: 0, + rightSecondaryValue: 0 + } + } + + function addVals(val1, val2, sign) { + var val = []; + if (val1.length != val2.length) return; + for (var i = 0; i < val1.length; i++) { + val.push({x: 0, y: 0, z: 0}); + val[i].x = val1[i].x + sign*val2[i].x; + val[i].y = val1[i].y + sign*val2[i].y; + val[i].z = val1[i].z + sign*val2[i].z; + } + return val; + } + + function multiplyValsBy(val1, num) { + var val = []; + for (var i = 0; i < val1.length; i++) { + val.push({x: 0, y: 0, z: 0}); + val[i].x = val1[i].x * num; + val[i].y = val1[i].y * num; + val[i].z = val1[i].z * num; + } + return val; + } + + function getJointDistances(jointNamesArray) { + var result = {distances: [], totalDistance: 0} + for (var i = 1; i < jointNamesArray.length; i++) { + var index0 = MyAvatar.getJointIndex(jointNamesArray[i-1]); + var index1 = MyAvatar.getJointIndex(jointNamesArray[i]); + var pos0 = MyAvatar.getJointPosition(index0); + var pos1 = MyAvatar.getJointPosition(index1); + var distance = Vec3.distance(pos0, pos1); + result.distances.push(distance); + result.totalDistance += distance; + } + return result; + } + + function estimatePalmData(side) { + var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; + + // Calculate the finger lengths by adding its joint lengths + + var jointOffset = { x: 0, y: 0, z: 0 }; + var upperSide = side[0].toUpperCase() + side.substring(1); + var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand"); + var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); + var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; + + var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; + var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; + + var sumvec = {x:0, y:0, z:0}; + sumvec = Vec3.sum(worldPosHand, sumvec); + + var thumbLength = 0; + + for (var i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var jointNames = getJointNames(upperSide, finger, 4); + var fingerLength = getJointDistances(jointNames).totalDistance; + + var jointIndex = MyAvatar.getJointIndex(jointNames[0]); + positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex); + directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); + data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); + if (finger != "thumb") { + sumvec = Vec3.sum(Vec3.multiply(2, positions[finger]), sumvec); + } else { + thumbLength = fingerLength; + } + } + + data.perpendicular = side == "right" ? Vec3.normalize(Vec3.cross(directions["ring"], directions["pinky"])) : Vec3.normalize(Vec3.cross(directions["pinky"],directions["ring"])); + + data.position = Vec3.multiply(1.0/9, sumvec); + data.distance = 1.55*Vec3.distance(data.position, positions["index"]); + + // move back thumb check up origin + + data.fingers["thumb"] = Vec3.sum(data.fingers["thumb"], Vec3.multiply( -0.2 * thumbLength, data.perpendicular)); + + return data; + } + + Script.registerValue("GlobalDebugger", varsToDebug); + + for (var i = 0; i < dataKeys.length; i++) { + fingerRays["left"][dataKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + fingerRays["right"][dataKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + } + + var palmRay = { + left: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }), + right: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }) + } + + var sphereHand = { + right: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }), + left: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }) + } + + function updateSphereHand(side) { + var palmData = estimatePalmData(side); + var dist = 1.5*palmData.distance; + var palmPoint = palmData.position; + + var checkOffset = { x: palmData.perpendicular.x * dist, + y: palmData.perpendicular.y * dist, + z: palmData.perpendicular.z * dist }; + + var spherePos = Vec3.sum(palmPoint, checkOffset); + var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); + + Overlays.editOverlay(palmRay[side], { + start: palmPoint, + end: checkPoint, + visible: showLines + }); + + Overlays.editOverlay(sphereHand[side], { + position: spherePos, + scale: { + x: 2*dist, + y: 2*dist, + z: 2*dist + }, + visible: showSphere + }); + var pickRays = []; + for (var i = 0; i < dataKeys.length; i++) { + Overlays.editOverlay(fingerRays[side][dataKeys[i]], { + start: palmData.fingers[dataKeys[i]], + end: checkPoint, + visible: showLines + }); + } + var finger = dataKeys[updateFingerWithIndex]; + + var grabbables = Entities.findEntities(spherePos, dist); + + if (grabbables.length > 0) { + var origin = palmData.fingers[finger]; + var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); + var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); + var percent = 0.5; + if (intersection.intersects && intersection.distance < 2.5*dist) { + percent = intersection.distance/(2.5*dist); + var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; + percent += grabMultiplier * grabPercent[side]; + } + varsToDebug.fingerPercent[side][finger] = percent; + } + var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); + var percent = varsToDebug.fingerPercent[side][finger]; + var newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); + + // Assign animation interpolation steps + dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); + } + + function getJointNames(side, finger, count) { + var names = []; + for (var i = 1; i < count+1; i++) { + var name = side+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i); + names.push(name); + } + return names; + } + + var leftTriggerPress = function (value) { + varsToDebug.triggerValues.leftTriggerValue = value; + grabPercent["left"] = value; + }; + var leftTriggerClick = function (value) { + varsToDebug.triggerValues.leftTriggerClicked = value; + }; + var rightTriggerPress = function (value) { + varsToDebug.triggerValues.rightTriggerValue = value; + grabPercent["right"] = value; + }; + var rightTriggerClick = function (value) { + varsToDebug.triggerValues.rightTriggerClicked = value; + }; + var leftSecondaryPress = function (value) { + varsToDebug.triggerValues.leftSecondaryValue = value; + }; + var rightSecondaryPress = function (value) { + varsToDebug.triggerValues.rightSecondaryValue = value; + }; + + var MAPPING_NAME = "com.highfidelity.handTouch"; + var mapping = Controller.newMapping(MAPPING_NAME); + mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress); + mapping.from([Controller.Standard.RTClick]).peek().to(rightTriggerClick); + mapping.from([Controller.Standard.LT]).peek().to(leftTriggerPress); + mapping.from([Controller.Standard.LTClick]).peek().to(leftTriggerClick); + + mapping.from([Controller.Standard.RB]).peek().to(rightSecondaryPress); + mapping.from([Controller.Standard.LB]).peek().to(leftSecondaryPress); + mapping.from([Controller.Standard.LeftGrip]).peek().to(leftSecondaryPress); + mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress); + + Controller.enableMapping(MAPPING_NAME); + + Script.update.connect(function(){ + updateFingerWithIndex = updateFingerWithIndex < dataKeys.length-1 ? updateFingerWithIndex + 1 : 0; + + updateSphereHand("right"); + updateSphereHand("left"); + + for (var i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Right", finger, 3); + dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } + } + + for (var i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Left", finger, 3); + dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } + } + }); + +}()) From 47dbb0c168ad92bba5825a250ca0fee2ae4b2dbb Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 29 Dec 2017 15:35:44 -0700 Subject: [PATCH 02/10] load script manually --- scripts/system/controllers/controllerDispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 24ac757f60..51f927f224 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -20,7 +20,6 @@ controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/handTouch.js"); (function() { Script.include("/~/system/libraries/pointersUtils.js"); From 28d9409bf064d4cbee58a608da2a8af40ddbd040 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 4 Jan 2018 11:37:37 -0700 Subject: [PATCH 03/10] Fix default hand poses --- interface/resources/qml/js/Utils.jsc | Bin 6596 -> 0 bytes interface/src/avatar/MySkeletonModel.cpp | 11 +- interface/src/avatar/MySkeletonModel.h | 2 + scripts/defaultScripts.js | 3 +- .../{libraries => controllers}/handTouch.js | 198 +++++++++++++----- 5 files changed, 159 insertions(+), 55 deletions(-) delete mode 100644 interface/resources/qml/js/Utils.jsc rename scripts/system/{libraries => controllers}/handTouch.js (76%) diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc deleted file mode 100644 index ab20e996b9469915ac6a89901da175143e6b5024..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6596 zcmcIoZD?EP6@Kh1ckah_<+cmm)~+s7*+{cEDOm|)Y*uPBiCux$Iwl#JYtNP&7t3}W zIXI&o4oU8EkM2n zXjl7O2dDtRuNCd{@)jY-)d!#zenqKY4sn&W{weu)twmlb!$4JTzv65rm+K1vDC#!F zU$|ZVIZ9zlW+S<`HL;hgR&M8~1sQiLyXg~6*Kx4qHtya=?){4Iz`gR%B^6)FqD(EX zPx0NgjXdr9YAbNP75KOnxYqy<89DFo`<=jA05hh+!CUJc)7q5K-I-U<4BwMhw#!Kold03qHB1qPh=(m57jV zQZO*TlsKpO<^4D&)ji|-UBpF<$-`KmCFTK~acFxKqv8qb`vJ}(jwnv6ynF{ogq(=F zep_Ap)wNGud)4)O>UtbUguEF{VOB&&#)x21=BI^yoeI;=cpet< z8yEavN0<0LF8)1^DU2eDE}X7EzTC=9#(VVvU%?VLXPX@E8u^tC$cdhzrY~ z5|TzSE|9{#2}}z<=It55SzSj%iNA*yHv6nZbjwP_N>*aF82-K7|F|4Z{3((gYY*l1 z1HmPNES&HK!VAmo!G#Z7%nSc2m&=jFSbJz;vopy0hR~Acw-O%(%P$^o0YVGQoxRBc zfA58N%nJ{-l0bHGCbwg^#q(qrrDi+!w<_$cdfT#JQtVa6B|0HZKTvD>SPT8{3FK*i zWU(iZU!oSteUbW{j!MrrowDh%hwMA*Wz%5~+1G_Km+UHco!!3?d2`uawWQAO`Fhz^ zFY9DKQ7^k1!8+Li%B~H>BZmbX7dAVWSkh#gI3&0r zaF0rAW;@0YRTR^~w|fH1=rq7T1!gIn>dAFt!Sy*C45#|hZ zyKMQfPE=zR8BZ12Iu&e;jZ`qZ;(XBNe0X7VdI#CX&hyK$=>L{qIw{h|R_2l4RI7AY z^-G-Fg*Pl@c)U6&I`)6~vWKqD3?b!JxCu1uN`cA?Q-i-75ra?*w4> z7DLeZEXfck*xy13exhOT)~XM$^JKYWuGBjYFUK{J-kQV9*AmCcI=9Ipz3t&zt&~o& zbp5o=j@d0M5iMDXSkX$%t~YJ^dxYvnP2X7@RT0fd+JSJgHI(cgqRD9&=_E-h7ipTL zi!Ra}N%Kz9JRJ@v&Ijf=afs?ImCUopaAUf5Bc(P@)^4QL#!&4B4=hC;lN+q^_W+Q1 zP^n|i{5|@(6kNweRp@!g!IB0$&XzP(9WOq8UKl6|MM0Czbbp-MJtdAmS4|C?43SDf)z9}dNlLtGo5xl*UM-i)V9TyEvOFWw5I(m_-&8|fx011{`>NNH;(6Df(rr#QsN>-}$CJna|wOpzoRcq-Fq`bAH8fghvwNyJ`N_RlpJYiGQZuHu}HZ5Pa zdE8c|q5`8P==g;$uId?L_>-ZdTT7Ja(9^jWIyw}4%`oqUj`vkn%QKo+>hkt%Am^R8 zT%)}GyrqJUtqMAFDsNlV@getLocalOrientation(); _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); - // evaluate AnimGraph animation and update jointStates. + //// evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); Rig::EyeParameters eyeParams; @@ -323,17 +323,22 @@ void MySkeletonModel::updateFingers() { for (auto& link : chain) { int index = _rig.indexOfJoint(link.second); if (index >= 0) { + if (_jointRotationFrameOffsetMap.find(index) == _jointRotationFrameOffsetMap.end()) { + _jointRotationFrameOffsetMap.insert(std::pair(index, 0)); + } auto pose = myAvatar->getControllerPoseInSensorFrame(link.first); if (pose.valid) { glm::quat relRot = glm::inverse(prevAbsRot) * pose.getRotation(); // only set the rotation for the finger joints, not the hands. if (link.first != controller::Action::LEFT_HAND && link.first != controller::Action::RIGHT_HAND) { _rig.setJointRotation(index, true, relRot, CONTROLLER_PRIORITY); + _jointRotationFrameOffsetMap.find(index)->second = 0; } prevAbsRot = pose.getRotation(); - } else { - // _rig.clearJointAnimationPriority(index); + } else if (_jointRotationFrameOffsetMap.find(index)->second == 1){ + _rig.clearJointAnimationPriority(index); } + _jointRotationFrameOffsetMap.find(index)->second++; } } } diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index d9f57a439a..252b6c293b 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -28,6 +28,8 @@ private: AnimPose _prevHips; // sensor frame bool _prevHipsValid { false }; + + std::map _jointRotationFrameOffsetMap; }; #endif // hifi_MySkeletonModel_h diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 89d4c75ae4..c14cea63f2 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js", + "system/controllers/handTouch.js" //"system/chat.js" ]; diff --git a/scripts/system/libraries/handTouch.js b/scripts/system/controllers/handTouch.js similarity index 76% rename from scripts/system/libraries/handTouch.js rename to scripts/system/controllers/handTouch.js index 5f917f7247..34f11aa764 100644 --- a/scripts/system/libraries/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -11,15 +11,36 @@ (function(){ - var animating = false; var updateFingerWithIndex = 0; + + // Keys to access finger data var dataKeys = ["pinky", "ring", "middle", "index", "thumb"]; + // Additionally close the hands to achieve a grabbing effect var grabPercent = { left: 0, right: 0 }; - var isGrabbing = false; + // var isGrabbing = false; + + // Store which fingers are touching - if all false restate the default poses + var isTouching = { + left: { + pinky: false, + middle: false, + ring: false, + thumb: false, + index: false + }, right: { + pinky: false, + middle: false, + ring: false, + thumb: false, + index: false + } + } + // joint data for opened pose + var dataOpen = { left: { pinky: [{x: -0.18262, y:-2.666, z:-25.11229}, {x: 1.28845, y:0, z:1.06604}, {x: -3.967, y:0, z:-0.8351} ], @@ -35,6 +56,8 @@ index: [{x: -0.111, y: 0, z: -2.2816}, {x: 1.217, y: 0, z: -0.90168}, {x: -3.967, y: 0, z: 2.62649}] } } + + // joint data for closed hand var dataClose = { left: { @@ -52,6 +75,8 @@ } } + // joint data for the current frame + var dataCurrent = { left:{ pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], @@ -68,6 +93,8 @@ index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] } } + + // interpolated values on joint data to smooth movement var dataDelta = { left:{ @@ -86,16 +113,24 @@ } } + // Acquire an updated value per hand every 5 frames + var animationSteps = 5; + // Debugging info + var showSphere = false; var showLines = false; + // store the rays for the fingers - only for debug purposes + var fingerRays = { left:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}, right:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined} }; + // Register object with API Debugger + var varsToDebug = { toggleDebugSphere: function(){ showSphere = !showSphere; @@ -105,18 +140,18 @@ }, fingerPercent: { left: { - pinky: 0.5, - middle: 0.5, - ring: 0.5, - thumb: 0.5, - index: 0.5 + pinky: 0.75, + middle: 0.75, + ring: 0.75, + thumb: 0.75, + index: 0.75 } , right: { - pinky: 0.5, - middle: 0.5, - ring: 0.5, - thumb: 0.5, - index: 0.5 + pinky: 0.75, + middle: 0.75, + ring: 0.75, + thumb: 0.75, + index: 0.75 } }, triggerValues: { @@ -128,6 +163,8 @@ rightSecondaryValue: 0 } } + + // Add/Subtract the joint data - per finger joint function addVals(val1, val2, sign) { var val = []; @@ -141,6 +178,8 @@ return val; } + // Multiply/Divide the joint data - per finger joint + function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { @@ -151,6 +190,8 @@ } return val; } + + // Calculate the finger lengths by adding its joint lengths function getJointDistances(jointNamesArray) { var result = {distances: [], totalDistance: 0} @@ -165,23 +206,29 @@ } return result; } + + // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays function estimatePalmData(side) { - var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; - - // Calculate the finger lengths by adding its joint lengths + // Return data object + var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; var jointOffset = { x: 0, y: 0, z: 0 }; + var upperSide = side[0].toUpperCase() + side.substring(1); var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand"); + + // Store position of the hand joint var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; + // Data for finger rays var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; - var sumvec = {x:0, y:0, z:0}; - sumvec = Vec3.sum(worldPosHand, sumvec); + // Calculate palm center + var palmCenter = {x:0, y:0, z:0}; + palmCenter = Vec3.sum(worldPosHand, palmCenter); var thumbLength = 0; @@ -195,15 +242,17 @@ directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); if (finger != "thumb") { - sumvec = Vec3.sum(Vec3.multiply(2, positions[finger]), sumvec); + palmCenter = Vec3.sum(Vec3.multiply(2, positions[finger]), palmCenter); // Hand joint + 2 * 4 fingers(no thumb) = 9 } else { thumbLength = fingerLength; } } data.perpendicular = side == "right" ? Vec3.normalize(Vec3.cross(directions["ring"], directions["pinky"])) : Vec3.normalize(Vec3.cross(directions["pinky"],directions["ring"])); - - data.position = Vec3.multiply(1.0/9, sumvec); + + + data.position = Vec3.multiply(1.0/9, palmCenter); // Hand joint + 2 * 4 fingers(no thumb) = 9 + data.distance = 1.55*Vec3.distance(data.position, positions["index"]); // move back thumb check up origin @@ -213,8 +262,11 @@ return data; } + // Register GlobalDebugger for API Debugger Script.registerValue("GlobalDebugger", varsToDebug); + // Create debug overlays - finger rays + palm rays + spheres + for (var i = 0; i < dataKeys.length; i++) { fingerRays["left"][dataKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, @@ -261,14 +313,18 @@ } function updateSphereHand(side) { + var palmData = estimatePalmData(side); - var dist = 1.5*palmData.distance; var palmPoint = palmData.position; - + var dist = 1.5*palmData.distance; + + // Situate the debugging overlays + var checkOffset = { x: palmData.perpendicular.x * dist, y: palmData.perpendicular.y * dist, z: palmData.perpendicular.z * dist }; - + + var spherePos = Vec3.sum(palmPoint, checkOffset); var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); @@ -287,7 +343,7 @@ }, visible: showSphere }); - var pickRays = []; + for (var i = 0; i < dataKeys.length; i++) { Overlays.editOverlay(fingerRays[side][dataKeys[i]], { start: palmData.fingers[dataKeys[i]], @@ -295,6 +351,9 @@ visible: showLines }); } + + // Update the intersection of only one finger at a time + var finger = dataKeys[updateFingerWithIndex]; var grabbables = Entities.findEntities(spherePos, dist); @@ -303,14 +362,19 @@ var origin = palmData.fingers[finger]; var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); - var percent = 0.5; - if (intersection.intersects && intersection.distance < 2.5*dist) { + var percent = 0.75; + var isAbleToGrab = intersection.intersects && intersection.distance < 2.5*dist; + // Store if this finger is touching something + isTouching[side][finger] = isAbleToGrab; + if (isAbleToGrab) { + // update the open/close percentage for this finger percent = intersection.distance/(2.5*dist); var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; percent += grabMultiplier * grabPercent[side]; } - varsToDebug.fingerPercent[side][finger] = percent; + varsToDebug.fingerPercent[side][finger] = percent; // store the current open/close percentage } + // Calculate new interpolation data var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); var percent = varsToDebug.fingerPercent[side][finger]; var newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); @@ -319,6 +383,7 @@ dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); } + // Recreate the finger joint names - and how many 0 to count function getJointNames(side, finger, count) { var names = []; for (var i = 1; i < count+1; i++) { @@ -328,8 +393,11 @@ return names; } + // Capture the controller values + var leftTriggerPress = function (value) { varsToDebug.triggerValues.leftTriggerValue = value; + // the value for the trigger increments the hand-close percentage grabPercent["left"] = value; }; var leftTriggerClick = function (value) { @@ -337,6 +405,7 @@ }; var rightTriggerPress = function (value) { varsToDebug.triggerValues.rightTriggerValue = value; + // the value for the trigger increments the hand-close percentage grabPercent["right"] = value; }; var rightTriggerClick = function (value) { @@ -362,34 +431,61 @@ mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress); Controller.enableMapping(MAPPING_NAME); - + + function getTouching(side) { + var animating = false; + for (var i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + animating = animating || isTouching[side][finger]; + } + return animating; + } + Script.update.connect(function(){ - updateFingerWithIndex = updateFingerWithIndex < dataKeys.length-1 ? updateFingerWithIndex + 1 : 0; + + // iterate fingers + + updateFingerWithIndex = (updateFingerWithIndex < dataKeys.length-1) ? updateFingerWithIndex + 1 : 0; + // precalculate data + updateSphereHand("right"); updateSphereHand("left"); - - for (var i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Right", finger, 3); - dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); - for (var j = 0; j < names.length; j++) { - var index = MyAvatar.getJointIndex(names[j]); - var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } - } - - for (var i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Left", finger, 3); - dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); - for (var j = 0; j < names.length; j++) { - var index = MyAvatar.getJointIndex(names[j]); - var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } - } + + // Assign interpolated values + var i, j, index; + for (i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Right", finger, 3); + dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); + for (j = 0; j < names.length; j++) { + index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (getTouching("right")) { + var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } + } + } + + for (i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Left", finger, 3); + dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); + for (j = 0; j < names.length; j++) { + index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (getTouching("left")) { + var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } + } + } + }); }()) From ef0164c2725aeee055b38f2cd51698f44779bfa9 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 4 Jan 2018 11:43:51 -0700 Subject: [PATCH 04/10] Fix tabs --- interface/src/avatar/MySkeletonModel.cpp | 2 +- scripts/defaultScripts.js | 2 +- scripts/system/controllers/handTouch.js | 214 +++++++++++------------ 3 files changed, 109 insertions(+), 109 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0448578036..1068b371ed 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -222,7 +222,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto orientation = myAvatar->getLocalOrientation(); _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); - //// evaluate AnimGraph animation and update jointStates. + // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); Rig::EyeParameters eyeParams; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index c14cea63f2..4ab6134ab8 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -33,7 +33,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", - "system/controllers/handTouch.js" + "system/controllers/handTouch.js" //"system/chat.js" ]; diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 34f11aa764..2e1cf99166 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -12,17 +12,17 @@ (function(){ var updateFingerWithIndex = 0; - - // Keys to access finger data + + // Keys to access finger data var dataKeys = ["pinky", "ring", "middle", "index", "thumb"]; - // Additionally close the hands to achieve a grabbing effect + // Additionally close the hands to achieve a grabbing effect var grabPercent = { left: 0, right: 0 }; // var isGrabbing = false; - - // Store which fingers are touching - if all false restate the default poses + + // Store which fingers are touching - if all false restate the default poses var isTouching = { left: { pinky: false, @@ -39,8 +39,8 @@ } } - // joint data for opened pose - + // joint data for opened pose + var dataOpen = { left: { pinky: [{x: -0.18262, y:-2.666, z:-25.11229}, {x: 1.28845, y:0, z:1.06604}, {x: -3.967, y:0, z:-0.8351} ], @@ -56,8 +56,8 @@ index: [{x: -0.111, y: 0, z: -2.2816}, {x: 1.217, y: 0, z: -0.90168}, {x: -3.967, y: 0, z: 2.62649}] } } - - // joint data for closed hand + + // joint data for closed hand var dataClose = { left: { @@ -75,8 +75,8 @@ } } - // joint data for the current frame - + // joint data for the current frame + var dataCurrent = { left:{ pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], @@ -93,8 +93,8 @@ index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] } } - - // interpolated values on joint data to smooth movement + + // interpolated values on joint data to smooth movement var dataDelta = { left:{ @@ -113,24 +113,24 @@ } } - // Acquire an updated value per hand every 5 frames - + // Acquire an updated value per hand every 5 frames + var animationSteps = 5; - // Debugging info - + // Debugging info + var showSphere = false; var showLines = false; - // store the rays for the fingers - only for debug purposes - + // store the rays for the fingers - only for debug purposes + var fingerRays = { left:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}, right:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined} }; - // Register object with API Debugger - + // Register object with API Debugger + var varsToDebug = { toggleDebugSphere: function(){ showSphere = !showSphere; @@ -163,8 +163,8 @@ rightSecondaryValue: 0 } } - - // Add/Subtract the joint data - per finger joint + + // Add/Subtract the joint data - per finger joint function addVals(val1, val2, sign) { var val = []; @@ -178,8 +178,8 @@ return val; } - // Multiply/Divide the joint data - per finger joint - + // Multiply/Divide the joint data - per finger joint + function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { @@ -190,8 +190,8 @@ } return val; } - - // Calculate the finger lengths by adding its joint lengths + + // Calculate the finger lengths by adding its joint lengths function getJointDistances(jointNamesArray) { var result = {distances: [], totalDistance: 0} @@ -206,27 +206,27 @@ } return result; } - - // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays + + // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays function estimatePalmData(side) { // Return data object - var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; + var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; var jointOffset = { x: 0, y: 0, z: 0 }; - + var upperSide = side[0].toUpperCase() + side.substring(1); var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand"); - - // Store position of the hand joint + + // Store position of the hand joint var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; - // Data for finger rays + // Data for finger rays var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; - // Calculate palm center + // Calculate palm center var palmCenter = {x:0, y:0, z:0}; palmCenter = Vec3.sum(worldPosHand, palmCenter); @@ -249,10 +249,10 @@ } data.perpendicular = side == "right" ? Vec3.normalize(Vec3.cross(directions["ring"], directions["pinky"])) : Vec3.normalize(Vec3.cross(directions["pinky"],directions["ring"])); - - + + data.position = Vec3.multiply(1.0/9, palmCenter); // Hand joint + 2 * 4 fingers(no thumb) = 9 - + data.distance = 1.55*Vec3.distance(data.position, positions["index"]); // move back thumb check up origin @@ -262,11 +262,11 @@ return data; } - // Register GlobalDebugger for API Debugger + // Register GlobalDebugger for API Debugger Script.registerValue("GlobalDebugger", varsToDebug); - // Create debug overlays - finger rays + palm rays + spheres - + // Create debug overlays - finger rays + palm rays + spheres + for (var i = 0; i < dataKeys.length; i++) { fingerRays["left"][dataKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, @@ -313,18 +313,18 @@ } function updateSphereHand(side) { - + var palmData = estimatePalmData(side); var palmPoint = palmData.position; var dist = 1.5*palmData.distance; - - // Situate the debugging overlays - + + // Situate the debugging overlays + var checkOffset = { x: palmData.perpendicular.x * dist, y: palmData.perpendicular.y * dist, z: palmData.perpendicular.z * dist }; - + var spherePos = Vec3.sum(palmPoint, checkOffset); var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); @@ -351,9 +351,9 @@ visible: showLines }); } - - // Update the intersection of only one finger at a time - + + // Update the intersection of only one finger at a time + var finger = dataKeys[updateFingerWithIndex]; var grabbables = Entities.findEntities(spherePos, dist); @@ -363,18 +363,18 @@ var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); var percent = 0.75; - var isAbleToGrab = intersection.intersects && intersection.distance < 2.5*dist; - // Store if this finger is touching something - isTouching[side][finger] = isAbleToGrab; + var isAbleToGrab = intersection.intersects && intersection.distance < 2.5*dist; + // Store if this finger is touching something + isTouching[side][finger] = isAbleToGrab; if (isAbleToGrab) { - // update the open/close percentage for this finger + // update the open/close percentage for this finger percent = intersection.distance/(2.5*dist); var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; percent += grabMultiplier * grabPercent[side]; } varsToDebug.fingerPercent[side][finger] = percent; // store the current open/close percentage } - // Calculate new interpolation data + // Calculate new interpolation data var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); var percent = varsToDebug.fingerPercent[side][finger]; var newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); @@ -383,7 +383,7 @@ dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); } - // Recreate the finger joint names - and how many 0 to count + // Recreate the finger joint names - and how many 0 to count function getJointNames(side, finger, count) { var names = []; for (var i = 1; i < count+1; i++) { @@ -393,11 +393,11 @@ return names; } - // Capture the controller values - + // Capture the controller values + var leftTriggerPress = function (value) { varsToDebug.triggerValues.leftTriggerValue = value; - // the value for the trigger increments the hand-close percentage + // the value for the trigger increments the hand-close percentage grabPercent["left"] = value; }; var leftTriggerClick = function (value) { @@ -405,7 +405,7 @@ }; var rightTriggerPress = function (value) { varsToDebug.triggerValues.rightTriggerValue = value; - // the value for the trigger increments the hand-close percentage + // the value for the trigger increments the hand-close percentage grabPercent["right"] = value; }; var rightTriggerClick = function (value) { @@ -431,60 +431,60 @@ mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress); Controller.enableMapping(MAPPING_NAME); - - function getTouching(side) { - var animating = false; - for (var i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - animating = animating || isTouching[side][finger]; - } - return animating; - } - + + function getTouching(side) { + var animating = false; + for (var i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + animating = animating || isTouching[side][finger]; + } + return animating; + } + Script.update.connect(function(){ - - // iterate fingers - - updateFingerWithIndex = (updateFingerWithIndex < dataKeys.length-1) ? updateFingerWithIndex + 1 : 0; - // precalculate data - + // iterate fingers + + updateFingerWithIndex = (updateFingerWithIndex < dataKeys.length-1) ? updateFingerWithIndex + 1 : 0; + + // precalculate data + updateSphereHand("right"); updateSphereHand("left"); - // Assign interpolated values - var i, j, index; - for (i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Right", finger, 3); - dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); - for (j = 0; j < names.length; j++) { - index = MyAvatar.getJointIndex(names[j]); - // if no finger is touching restate the default poses - if (getTouching("right")) { - var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } else { - MyAvatar.clearJointData(index); - } - } - } + // Assign interpolated values + var i, j, index; + for (i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Right", finger, 3); + dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); + for (j = 0; j < names.length; j++) { + index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (getTouching("right")) { + var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } + } + } - for (i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Left", finger, 3); - dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); - for (j = 0; j < names.length; j++) { - index = MyAvatar.getJointIndex(names[j]); - // if no finger is touching restate the default poses - if (getTouching("left")) { - var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } else { - MyAvatar.clearJointData(index); - } - } - } + for (i = 0; i < dataKeys.length; i++) { + var finger = dataKeys[i]; + var names = getJointNames("Left", finger, 3); + dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); + for (j = 0; j < names.length; j++) { + index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (getTouching("left")) { + var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } + } + } }); From 367e2266f76cd7fc298bcd52b2ffc67d3ac4e453 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 4 Jan 2018 12:04:52 -0700 Subject: [PATCH 05/10] Fix eslint --- scripts/system/controllers/handTouch.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 2e1cf99166..f3e15c96af 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -9,6 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* jslint bitwise: true */ + +/* global Script, Overlays, Controller, Vec3, Quat, MyAvatar, Entities +*/ + (function(){ var updateFingerWithIndex = 0; @@ -376,7 +381,7 @@ } // Calculate new interpolation data var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); - var percent = varsToDebug.fingerPercent[side][finger]; + percent = varsToDebug.fingerPercent[side][finger]; var newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); // Assign animation interpolation steps @@ -453,16 +458,17 @@ updateSphereHand("left"); // Assign interpolated values - var i, j, index; + var i, j, index, finger, names, quatRot; + for (i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Right", finger, 3); + finger = dataKeys[i]; + names = getJointNames("Right", finger, 3); dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); for (j = 0; j < names.length; j++) { index = MyAvatar.getJointIndex(names[j]); // if no finger is touching restate the default poses if (getTouching("right")) { - var quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); + quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); MyAvatar.setJointRotation(index, quatRot); } else { MyAvatar.clearJointData(index); @@ -471,14 +477,14 @@ } for (i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var names = getJointNames("Left", finger, 3); + finger = dataKeys[i]; + names = getJointNames("Left", finger, 3); dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); for (j = 0; j < names.length; j++) { index = MyAvatar.getJointIndex(names[j]); // if no finger is touching restate the default poses if (getTouching("left")) { - var quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); + quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); MyAvatar.setJointRotation(index, quatRot); } else { MyAvatar.clearJointData(index); From 108748ca9670398b80e958079393b591387688a0 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 5 Jan 2018 09:55:50 -0700 Subject: [PATCH 06/10] smooth transitions and slower back to default --- scripts/system/controllers/handTouch.js | 222 +++++++++++++++--------- 1 file changed, 138 insertions(+), 84 deletions(-) diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index f3e15c96af..31118608eb 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -19,7 +19,7 @@ var updateFingerWithIndex = 0; // Keys to access finger data - var dataKeys = ["pinky", "ring", "middle", "index", "thumb"]; + var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; // Additionally close the hands to achieve a grabbing effect var grabPercent = { left: 0, @@ -44,6 +44,13 @@ } } + // frame count for transition to default pose + + var countToDefault = { + left: 0, + right: 0 + } + // joint data for opened pose var dataOpen = { @@ -67,19 +74,40 @@ var dataClose = { left: { pinky:[{x: 75.45709, y:-8.01347, z:-22.54823}, {x: 69.562, y:0, z:1.06604}, {x: 74.73801, y:0, z:-0.8351}], - middle: [{x: 66.0237, y:-2.42536, z:-6.13193}, {x: 72.63042, y:0, z:-0.71834}, {x: 75.19901, y:0, z:0.83978}], + middle: [{x: 66.0237, y:-2.42536, z:-6.13193}, {x: 65.63042, y:0, z:-0.71834}, {x: 60.19901, y:0, z:0.83978}], ring: [{x: 71.52988, y:-2.35423, z:-16.21694}, {x: 64.44739, y:0, z:0.68153}, {x: 70.518, y:0, z:-0.69295}], thumb: [{x: 33.83371, y:-15.19106, z:34.66116}, {x: 0, y:0, z:-43.42915}, {x: 0, y:0, z:-30.18613}], index: [{x: 35.56082, y:-1.21056, z:-2.07362}, {x: 79.79845, y:-0.01107, z:0.93037}, {x: 68.767, y:0, z:-2.64018}] }, right: { pinky:[{x: 75.45702, y: 8.013, z: 22.41022}, {x: 69.562, y: 0, z: -1.03973}, {x: 74.738, y: 0, z: 0.86424}], - middle: [{x: 66.02399, y: 2.425, z: 6.11638}, {x: 72.63002, y: 0, z: 0.71427}, {x: 72.63, y: 0, z: -0.85103}], + middle: [{x: 66.02399, y: 2.425, z: 6.11638}, {x: 65.63002, y: 0, z: 0.71427}, {x: 60.63, y: 0, z: -0.85103}], ring: [{x: 71.53, y: 5.022, z: 16.33612}, {x: 64.447, y: 0, z: -0.64524}, {x: 70.51801, y: 0, z: 0.69807}], thumb: [{x: 33.834, y: 15.191, z: -34.52131}, {x: 0, y: 0, z: 43.41122}, {x: 0, y: 0, z: 30.24818}], index: [{x: 35.633, y: 1.215, z: -6.6376}, {x: 79.72701, y: 0, z: -0.90168}, {x: 68.76701, y: 0, z: 2.62649}] } } + // snapshot for the default pose + + var dataDefault = { + left:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + set: false + }, + right:{ + pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + set: false + } + } + // joint data for the current frame var dataCurrent = { @@ -118,9 +146,13 @@ } } - // Acquire an updated value per hand every 5 frames + // Acquire an updated value per hand every 5 frames when finger is touching (faster in) - var animationSteps = 5; + var touchAnimationSteps = 5; + + // Acquire an updated value per hand every 10 frames when finger is returning to default position (slower out) + + var defaultAnimationSteps = 10; // Debugging info @@ -145,18 +177,18 @@ }, fingerPercent: { left: { - pinky: 0.75, - middle: 0.75, - ring: 0.75, - thumb: 0.75, - index: 0.75 + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, + index: 0.38 } , right: { - pinky: 0.75, - middle: 0.75, - ring: 0.75, - thumb: 0.75, - index: 0.75 + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, + index: 0.38 } }, triggerValues: { @@ -237,9 +269,9 @@ var thumbLength = 0; - for (var i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; - var jointNames = getJointNames(upperSide, finger, 4); + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var jointNames = getJointNames(side, finger, 4); var fingerLength = getJointDistances(jointNames).totalDistance; var jointIndex = MyAvatar.getJointIndex(jointNames[0]); @@ -247,20 +279,26 @@ directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); if (finger != "thumb") { - palmCenter = Vec3.sum(Vec3.multiply(2, positions[finger]), palmCenter); // Hand joint + 2 * 4 fingers(no thumb) = 9 + // finger joints have double the weight than the hand joint + // This would better position the palm estimation + palmCenter = Vec3.sum(Vec3.multiply(2, positions[finger]), palmCenter); } else { thumbLength = fingerLength; } } - data.perpendicular = side == "right" ? Vec3.normalize(Vec3.cross(directions["ring"], directions["pinky"])) : Vec3.normalize(Vec3.cross(directions["pinky"],directions["ring"])); + // perpendicular change direction depending on the side + + data.perpendicular = (side == "right") ? + Vec3.normalize(Vec3.cross(directions["index"], directions["pinky"])): + Vec3.normalize(Vec3.cross(directions["pinky"], directions["index"])); - data.position = Vec3.multiply(1.0/9, palmCenter); // Hand joint + 2 * 4 fingers(no thumb) = 9 + data.position = Vec3.multiply(1.0/9, palmCenter); // 1(weight) * Hand joint + 2(weight) * 4 fingers(no thumb) = 9 - data.distance = 1.55*Vec3.distance(data.position, positions["index"]); + data.distance = 1.55*Vec3.distance(data.position, positions["index"]); // 1.55 based on test/error for the sphere radius that best fits the hand - // move back thumb check up origin + // move back thumb ray origin data.fingers["thumb"] = Vec3.sum(data.fingers["thumb"], Vec3.multiply( -0.2 * thumbLength, data.perpendicular)); @@ -272,14 +310,14 @@ // Create debug overlays - finger rays + palm rays + spheres - for (var i = 0; i < dataKeys.length; i++) { - fingerRays["left"][dataKeys[i]] = Overlays.addOverlay("line3d", { + for (var i = 0; i < fingerKeys.length; i++) { + fingerRays["left"][fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, start: { x:0, y:0, z:0 }, end: { x:0, y:1, z:0 }, visible: showLines }); - fingerRays["right"][dataKeys[i]] = Overlays.addOverlay("line3d", { + fingerRays["right"][fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, start: { x:0, y:0, z:0 }, end: { x:0, y:1, z:0 }, @@ -317,6 +355,19 @@ }) } + function acquireDefaultPose(side) { + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var names = getJointNames(side, finger, 3); + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + var rotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(index)); + dataDefault[side][finger][j] = dataCurrent[side][finger][j] = rotation; + } + } + dataDefault[side].set = true; + } + function updateSphereHand(side) { var palmData = estimatePalmData(side); @@ -349,9 +400,9 @@ visible: showSphere }); - for (var i = 0; i < dataKeys.length; i++) { - Overlays.editOverlay(fingerRays[side][dataKeys[i]], { - start: palmData.fingers[dataKeys[i]], + for (var i = 0; i < fingerKeys.length; i++) { + Overlays.editOverlay(fingerRays[side][fingerKeys[i]], { + start: palmData.fingers[fingerKeys[i]], end: checkPoint, visible: showLines }); @@ -359,16 +410,22 @@ // Update the intersection of only one finger at a time - var finger = dataKeys[updateFingerWithIndex]; + var finger = fingerKeys[updateFingerWithIndex]; var grabbables = Entities.findEntities(spherePos, dist); - + var newFingerData = dataDefault[side][finger]; + var animationSteps = defaultAnimationSteps; + if (grabbables.length > 0) { var origin = palmData.fingers[finger]; var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); - var percent = 0.75; - var isAbleToGrab = intersection.intersects && intersection.distance < 2.5*dist; + var percent = 0.38; + var isAbleToGrab = intersection.intersects && intersection.distance < 1.5*dist; + if (isAbleToGrab && !getTouching(side)) { + acquireDefaultPose(side); // take a snapshot of the default pose before touch starts + newFingerData = dataDefault[side][finger]; // assign default pose to finger data + } // Store if this finger is touching something isTouching[side][finger] = isAbleToGrab; if (isAbleToGrab) { @@ -376,23 +433,25 @@ percent = intersection.distance/(2.5*dist); var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; percent += grabMultiplier * grabPercent[side]; + + // Calculate new interpolation data + var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); + newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); // assign close/open ratio to finger to simulate touch + animationSteps = touchAnimationSteps; } - varsToDebug.fingerPercent[side][finger] = percent; // store the current open/close percentage + varsToDebug.fingerPercent[side][finger] = percent; } - // Calculate new interpolation data - var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); - percent = varsToDebug.fingerPercent[side][finger]; - var newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); - - // Assign animation interpolation steps + + // Calculate animation increments dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); } - // Recreate the finger joint names - and how many 0 to count + // Recreate the finger joint names + function getJointNames(side, finger, count) { var names = []; for (var i = 1; i < count+1; i++) { - var name = side+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i); + var name = side[0].toUpperCase()+side.substring(1)+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i); names.push(name); } return names; @@ -437,61 +496,56 @@ Controller.enableMapping(MAPPING_NAME); + function getTouching(side) { var animating = false; - for (var i = 0; i < dataKeys.length; i++) { - var finger = dataKeys[i]; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; animating = animating || isTouching[side][finger]; } - return animating; + return animating; // return false only if none of the fingers are touching } Script.update.connect(function(){ - // iterate fingers + // index of the finger that needs to be updated this frame - updateFingerWithIndex = (updateFingerWithIndex < dataKeys.length-1) ? updateFingerWithIndex + 1 : 0; + updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0; - // precalculate data - updateSphereHand("right"); - updateSphereHand("left"); - - // Assign interpolated values - var i, j, index, finger, names, quatRot; - - for (i = 0; i < dataKeys.length; i++) { - finger = dataKeys[i]; - names = getJointNames("Right", finger, 3); - dataCurrent["right"][finger] = addVals(dataCurrent["right"][finger], dataDelta["right"][finger], 1); - for (j = 0; j < names.length; j++) { - index = MyAvatar.getJointIndex(names[j]); - // if no finger is touching restate the default poses - if (getTouching("right")) { - quatRot = Quat.fromVec3Degrees(dataCurrent["right"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } else { - MyAvatar.clearJointData(index); + + ["right", "left"].forEach(function(side){ + + // recalculate the base data + updateSphereHand(side); + + // this vars manage the transition to default pose + var isHandTouching = getTouching(side); + countToDefault[side] = isHandTouching ? 0 : countToDefault[side] + 1; + + + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var names = getJointNames(side, finger, 3); + + // Add the animation increments + + dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1); + + // update every finger joint + + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (isHandTouching || (dataDefault[side].set && countToDefault[side] < 5*touchAnimationSteps)) { + var quatRot = Quat.fromVec3Degrees(dataCurrent[side][finger][j]); + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } } } - } - - for (i = 0; i < dataKeys.length; i++) { - finger = dataKeys[i]; - names = getJointNames("Left", finger, 3); - dataCurrent["left"][finger] = addVals(dataCurrent["left"][finger], dataDelta["left"][finger], 1); - for (j = 0; j < names.length; j++) { - index = MyAvatar.getJointIndex(names[j]); - // if no finger is touching restate the default poses - if (getTouching("left")) { - quatRot = Quat.fromVec3Degrees(dataCurrent["left"][finger][j]); - MyAvatar.setJointRotation(index, quatRot); - } else { - MyAvatar.clearJointData(index); - } - } - } - + }); }); }()) From d0bc881bb25367127c5fcd122ff5e8360e747659 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 5 Jan 2018 12:20:41 -0700 Subject: [PATCH 07/10] Load script manually --- scripts/defaultScripts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 4ab6134ab8..89d4c75ae4 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js", - "system/controllers/handTouch.js" + "system/controllers/controllerScripts.js" //"system/chat.js" ]; From 4f6a804fd1b80f6fef0f0a5f8fbd4e53011918f3 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 5 Jan 2018 16:27:48 -0700 Subject: [PATCH 08/10] Fix magic numbers --- interface/src/avatar/MySkeletonModel.cpp | 2 +- scripts/system/controllers/handTouch.js | 41 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 1068b371ed..61236a0ff1 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -335,7 +335,7 @@ void MySkeletonModel::updateFingers() { _jointRotationFrameOffsetMap.find(index)->second = 0; } prevAbsRot = pose.getRotation(); - } else if (_jointRotationFrameOffsetMap.find(index)->second == 1){ + } else if (_jointRotationFrameOffsetMap.find(index)->second == 1) { // if the pose is invalid and was set on previous frame we do clear ( current frame offset = 1 ) _rig.clearJointAnimationPriority(index); } _jointRotationFrameOffsetMap.find(index)->second++; diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 31118608eb..709d16939c 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -263,15 +263,23 @@ var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; + var thumbLength = 0; + var weightCount = 0; + // Calculate palm center + + var handJointWeight = 1; + var fingerJointWeight = 2; + var palmCenter = {x:0, y:0, z:0}; palmCenter = Vec3.sum(worldPosHand, palmCenter); - var thumbLength = 0; + weightCount += handJointWeight; for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; - var jointNames = getJointNames(side, finger, 4); + var jointSuffixes = 4; // Get 4 joint names with suffix numbers (0, 1, 2, 3) + var jointNames = getJointNames(side, finger, jointSuffixes); var fingerLength = getJointDistances(jointNames).totalDistance; var jointIndex = MyAvatar.getJointIndex(jointNames[0]); @@ -281,7 +289,9 @@ if (finger != "thumb") { // finger joints have double the weight than the hand joint // This would better position the palm estimation - palmCenter = Vec3.sum(Vec3.multiply(2, positions[finger]), palmCenter); + + palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); + weightCount += fingerJointWeight; } else { thumbLength = fingerLength; } @@ -293,14 +303,14 @@ Vec3.normalize(Vec3.cross(directions["index"], directions["pinky"])): Vec3.normalize(Vec3.cross(directions["pinky"], directions["index"])); + data.position = Vec3.multiply(1.0/weightCount, palmCenter); - data.position = Vec3.multiply(1.0/9, palmCenter); // 1(weight) * Hand joint + 2(weight) * 4 fingers(no thumb) = 9 - - data.distance = 1.55*Vec3.distance(data.position, positions["index"]); // 1.55 based on test/error for the sphere radius that best fits the hand + var palmDistanceMultiplier = 1.55 // 1.55 based on test/error for the sphere radius that best fits the hand + data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions["index"]); // move back thumb ray origin - - data.fingers["thumb"] = Vec3.sum(data.fingers["thumb"], Vec3.multiply( -0.2 * thumbLength, data.perpendicular)); + var thumbBackMultiplier = 0.2; + data.fingers["thumb"] = Vec3.sum(data.fingers["thumb"], Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); return data; } @@ -358,7 +368,8 @@ function acquireDefaultPose(side) { for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; - var names = getJointNames(side, finger, 3); + var jointSuffixes = 3; // We need rotation of the 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); for (var j = 0; j < names.length; j++) { var index = MyAvatar.getJointIndex(names[j]); var rotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(index)); @@ -420,7 +431,7 @@ var origin = palmData.fingers[finger]; var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); - var percent = 0.38; + var percent = 0; // Initialize var isAbleToGrab = intersection.intersects && intersection.distance < 1.5*dist; if (isAbleToGrab && !getTouching(side)) { acquireDefaultPose(side); // take a snapshot of the default pose before touch starts @@ -430,7 +441,8 @@ isTouching[side][finger] = isAbleToGrab; if (isAbleToGrab) { // update the open/close percentage for this finger - percent = intersection.distance/(2.5*dist); + var distanceMultiplier = 2.5; + percent = intersection.distance/(distanceMultiplier*dist); var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; percent += grabMultiplier * grabPercent[side]; @@ -447,7 +459,7 @@ } // Recreate the finger joint names - + function getJointNames(side, finger, count) { var names = []; for (var i = 1; i < count+1; i++) { @@ -496,7 +508,7 @@ Controller.enableMapping(MAPPING_NAME); - + function getTouching(side) { var animating = false; for (var i = 0; i < fingerKeys.length; i++) { @@ -526,7 +538,8 @@ for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; - var names = getJointNames(side, finger, 3); + var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); // Add the animation increments From 8a7c68829d9db8bf059cdf94c643e55277b041fb Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 7 Jan 2018 11:56:30 -0700 Subject: [PATCH 09/10] Optimizations, cleanings and globals --- interface/src/avatar/MySkeletonModel.cpp | 11 +- scripts/system/controllers/handTouch.js | 493 +++++++++++++++-------- 2 files changed, 341 insertions(+), 163 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 61236a0ff1..e64547dacc 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -323,22 +323,25 @@ void MySkeletonModel::updateFingers() { for (auto& link : chain) { int index = _rig.indexOfJoint(link.second); if (index >= 0) { - if (_jointRotationFrameOffsetMap.find(index) == _jointRotationFrameOffsetMap.end()) { + auto rotationFrameOffset = _jointRotationFrameOffsetMap.find(index); + if (rotationFrameOffset == _jointRotationFrameOffsetMap.end()) { _jointRotationFrameOffsetMap.insert(std::pair(index, 0)); + rotationFrameOffset = _jointRotationFrameOffsetMap.find(index); } auto pose = myAvatar->getControllerPoseInSensorFrame(link.first); + if (pose.valid) { glm::quat relRot = glm::inverse(prevAbsRot) * pose.getRotation(); // only set the rotation for the finger joints, not the hands. if (link.first != controller::Action::LEFT_HAND && link.first != controller::Action::RIGHT_HAND) { _rig.setJointRotation(index, true, relRot, CONTROLLER_PRIORITY); - _jointRotationFrameOffsetMap.find(index)->second = 0; + rotationFrameOffset->second = 0; } prevAbsRot = pose.getRotation(); - } else if (_jointRotationFrameOffsetMap.find(index)->second == 1) { // if the pose is invalid and was set on previous frame we do clear ( current frame offset = 1 ) + } else if (rotationFrameOffset->second == 1) { // if the pose is invalid and was set on previous frame we do clear ( current frame offset = 1 ) _rig.clearJointAnimationPriority(index); } - _jointRotationFrameOffsetMap.find(index)->second++; + rotationFrameOffset->second++; } } } diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 709d16939c..345ba7a1d7 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -11,7 +11,7 @@ /* jslint bitwise: true */ -/* global Script, Overlays, Controller, Vec3, Quat, MyAvatar, Entities +/* global Script, Overlays, Controller, Vec3, MyAvatar, Entities */ (function(){ @@ -27,6 +27,27 @@ // var isGrabbing = false; + var Palm = function() { + this.position = {x:0, y:0, z:0}; + this.perpendicular = {x:0, y:0, z:0}; + this.distance = 0; + this.fingers = { + pinky: {x:0, y:0, z:0}, + middle: {x:0, y:0, z:0}, + ring: {x:0, y:0, z:0}, + thumb: {x:0, y:0, z:0}, + index: {x:0, y:0, z:0} + }; + this.set = false; + }; + + var palmData = { + left: new Palm(), + right: new Palm() + }; + + var handJointNames = {left: "LeftHand", right: "RightHand"}; + // Store which fingers are touching - if all false restate the default poses var isTouching = { left: { @@ -42,109 +63,106 @@ thumb: false, index: false } - } - + }; + // frame count for transition to default pose var countToDefault = { left: 0, right: 0 - } + }; // joint data for opened pose var dataOpen = { left: { - pinky: [{x: -0.18262, y:-2.666, z:-25.11229}, {x: 1.28845, y:0, z:1.06604}, {x: -3.967, y:0, z:-0.8351} ], - middle: [{x: -0.18262, y:0, z:-3.2809}, {x: 1.28845, y:0, z:-0.71834}, {x: -3.967, y:0, z:0.83978}], - ring: [{x: -0.18262, y:-1.11078, z:-16.24391}, {x: 1.28845, y:0, z:0.68153}, {x: -3.967, y:0, z:-0.69295}], - thumb: [{x: 5.207, y:2.595, z:38.40092}, {x: -9.869, y:11.755, z:10.50012}, {x: -9.778, y:9.647, z:15.16963}], - index: [{x: -0.18262, y:0.0099, z:2.28085}, {x: 1.28845, y:-0.01107, z:0.93037}, {x: -3.967, y:0, z:-2.64018}] + pinky:[{x: -0.0066, y:-0.0224, z:-0.2174, w:0.9758},{x: 0.0112, y:0.0001, z:0.0093, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0073, w:0.9994}], + ring:[{x: -0.0029, y:-0.0094, z:-0.1413, w:0.9899},{x: 0.0112, y:0.0001, z:0.0059, w:0.9999},{x: -0.0346, y:0.0002, z:-0.006, w:0.9994}], + middle:[{x: -0.0016, y:0, z:-0.0286, w:0.9996},{x: 0.0112, y:-0.0001, z:-0.0063, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0073, w:0.9994}], + index:[{x: -0.0016, y:0.0001, z:0.0199, w:0.9998},{x: 0.0112, y:0, z:0.0081, w:0.9999},{x: -0.0346, y:0.0008, z:-0.023, w:0.9991}], + thumb:[{x: 0.0354, y:0.0363, z:0.3275, w:0.9435},{x: -0.0945, y:0.0938, z:0.0995, w:0.9861},{x: -0.0952, y:0.0718, z:0.1382, w:0.9832}] }, right: { - pinky: [{x: -0.111, y: 2.66601, z: 12.06423}, {x: 1.217, y: 0, z: -1.03973}, {x: -3.967, y: 0, z: 0.86424}], - middle: [{x: -0.111, y: 0, z: 3.26538}, {x: 1.217, y: 0, z: 0.71427}, {x: -3.967, y: 0, z: -0.85103}], - ring: [{x: -0.111, y: 1.11101, z: 3.56312}, {x: 1.217, y: 0, z: -0.64524}, {x: -3.967, y: 0, z: 0.69807}], - thumb: [{x: 5.207, y: -2.595, z: -38.26131}, {x: -9.869, y: -11.755, z: -10.51778}, {x: -9.77799, y: -9.647, z: -15.10783}], - index: [{x: -0.111, y: 0, z: -2.2816}, {x: 1.217, y: 0, z: -0.90168}, {x: -3.967, y: 0, z: 2.62649}] + pinky:[{x: -0.0034, y:0.023, z:0.1051, w:0.9942},{x: 0.0106, y:-0.0001, z:-0.0091, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0075, w:0.9994}], + ring:[{x: -0.0013, y:0.0097, z:0.0311, w:0.9995},{x: 0.0106, y:-0.0001, z:-0.0056, w:0.9999},{x: -0.0346, y:-0.0002, z:0.0061, w:0.9994}], + middle:[{x: -0.001, y:0, z:0.0285, w:0.9996},{x: 0.0106, y:0.0001, z:0.0062, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0074, w:0.9994}], + index:[{x: -0.001, y:0, z:-0.0199, w:0.9998},{x: 0.0106, y:-0.0001, z:-0.0079, w:0.9999},{x: -0.0346, y:-0.0008, z:0.0229, w:0.9991}], + thumb:[{x: 0.0355, y:-0.0363, z:-0.3263, w:0.9439},{x: -0.0946, y:-0.0938, z:-0.0996, w:0.9861},{x: -0.0952, y:-0.0719, z:-0.1376, w:0.9833}] } - } - - // joint data for closed hand - + }; var dataClose = { left: { - pinky:[{x: 75.45709, y:-8.01347, z:-22.54823}, {x: 69.562, y:0, z:1.06604}, {x: 74.73801, y:0, z:-0.8351}], - middle: [{x: 66.0237, y:-2.42536, z:-6.13193}, {x: 65.63042, y:0, z:-0.71834}, {x: 60.19901, y:0, z:0.83978}], - ring: [{x: 71.52988, y:-2.35423, z:-16.21694}, {x: 64.44739, y:0, z:0.68153}, {x: 70.518, y:0, z:-0.69295}], - thumb: [{x: 33.83371, y:-15.19106, z:34.66116}, {x: 0, y:0, z:-43.42915}, {x: 0, y:0, z:-30.18613}], - index: [{x: 35.56082, y:-1.21056, z:-2.07362}, {x: 79.79845, y:-0.01107, z:0.93037}, {x: 68.767, y:0, z:-2.64018}] + pinky:[{x: 0.5878, y:-0.1735, z:-0.1123, w:0.7821},{x: 0.5704, y:0.0053, z:0.0076, w:0.8213},{x: 0.6069, y:-0.0044, z:-0.0058, w:0.7947}], + ring:[{x: 0.5761, y:-0.0989, z:-0.1025, w:0.8048},{x: 0.5332, y:0.0032, z:0.005, w:0.846},{x: 0.5773, y:-0.0035, z:-0.0049, w:0.8165}], + middle:[{x: 0.543, y:-0.0469, z:-0.0333, w:0.8378},{x: 0.5419, y:-0.0034, z:-0.0053, w:0.8404},{x: 0.5015, y:0.0037, z:0.0063, w:0.8651}], + index:[{x: 0.3051, y:-0.0156, z:-0.014, w:0.9521},{x: 0.6414, y:0.0051, z:0.0063, w:0.7671},{x: 0.5646, y:-0.013, z:-0.019, w:0.8251}], + thumb:[{x: 0.313, y:-0.0348, z:0.3192, w:0.8938},{x: 0, y:0, z:-0.37, w:0.929},{x: 0, y:0, z:-0.2604, w:0.9655}] }, right: { - pinky:[{x: 75.45702, y: 8.013, z: 22.41022}, {x: 69.562, y: 0, z: -1.03973}, {x: 74.738, y: 0, z: 0.86424}], - middle: [{x: 66.02399, y: 2.425, z: 6.11638}, {x: 65.63002, y: 0, z: 0.71427}, {x: 60.63, y: 0, z: -0.85103}], - ring: [{x: 71.53, y: 5.022, z: 16.33612}, {x: 64.447, y: 0, z: -0.64524}, {x: 70.51801, y: 0, z: 0.69807}], - thumb: [{x: 33.834, y: 15.191, z: -34.52131}, {x: 0, y: 0, z: 43.41122}, {x: 0, y: 0, z: 30.24818}], - index: [{x: 35.633, y: 1.215, z: -6.6376}, {x: 79.72701, y: 0, z: -0.90168}, {x: 68.76701, y: 0, z: 2.62649}] + pinky:[{x: 0.5881, y:0.1728, z:0.1114, w:0.7823},{x: 0.5704, y:-0.0052, z:-0.0075, w:0.8213},{x: 0.6069, y:0.0046, z:0.006, w:0.7947}], + ring:[{x: 0.5729, y:0.1181, z:0.0898, w:0.8061},{x: 0.5332, y:-0.003, z:-0.0048, w:0.846},{x: 0.5773, y:0.0035, z:0.005, w:0.8165}], + middle:[{x: 0.543, y:0.0468, z:0.0332, w:0.8378},{x: 0.5419, y:0.0034, z:0.0052, w:0.8404},{x: 0.5047, y:-0.0037, z:-0.0064, w:0.8632}], + index:[{x: 0.306, y:-0.0076, z:-0.0584, w:0.9502},{x: 0.6409, y:-0.005, z:-0.006, w:0.7675},{x: 0.5646, y:0.0129, z:0.0189, w:0.8251}], + thumb:[{x: 0.313, y:0.0352, z:-0.3181, w:0.8942},{x: 0, y:0, z:0.3698, w:0.9291},{x: 0, y:0, z:0.2609, w:0.9654}] } - } + }; // snapshot for the default pose var dataDefault = { left:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], set: false }, right:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], set: false } - } + }; // joint data for the current frame var dataCurrent = { left:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, right:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] } - } + }; // interpolated values on joint data to smooth movement var dataDelta = { left:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, right:{ - pinky:[{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - middle: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - ring: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - thumb: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}], - index: [{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0},{x: 0, y: 0, z: 0}] + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] } - } + }; // Acquire an updated value per hand every 5 frames when finger is touching (faster in) @@ -159,21 +177,28 @@ var showSphere = false; var showLines = false; - // store the rays for the fingers - only for debug purposes + // This get setup on creation - var fingerRays = { - left:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}, - right:{pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined} - }; + var linesCreated = false; + var sphereCreated = false; // Register object with API Debugger var varsToDebug = { + scriptLoaded: false, toggleDebugSphere: function(){ showSphere = !showSphere; + if (showSphere && !sphereCreated) { + createDebugSphere(); + sphereCreated = true; + } }, toggleDebugLines: function(){ showLines = !showLines; + if (showLines && !linesCreated) { + createDebugLines(); + linesCreated = true; + } }, fingerPercent: { left: { @@ -198,8 +223,15 @@ rightTriggerClicked: 0, leftSecondaryValue: 0, rightSecondaryValue: 0 - } - } + }, + palmData: { + left: new Palm(), + right: new Palm() + }, + offset: {x:0, y:0, z:0}, + avatarLoaded: false + }; + // Add/Subtract the joint data - per finger joint @@ -207,10 +239,11 @@ var val = []; if (val1.length != val2.length) return; for (var i = 0; i < val1.length; i++) { - val.push({x: 0, y: 0, z: 0}); + val.push({x: 0, y: 0, z: 0, w: 0}); val[i].x = val1[i].x + sign*val2[i].x; val[i].y = val1[i].y + sign*val2[i].y; val[i].z = val1[i].z + sign*val2[i].z; + val[i].w = val1[i].w + sign*val2[i].w; } return val; } @@ -220,10 +253,11 @@ function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { - val.push({x: 0, y: 0, z: 0}); + val.push({x: 0, y: 0, z: 0, w: 0}); val[i].x = val1[i].x * num; val[i].y = val1[i].y * num; val[i].z = val1[i].z * num; + val[i].w = val1[i].w * num; } return val; } @@ -231,7 +265,7 @@ // Calculate the finger lengths by adding its joint lengths function getJointDistances(jointNamesArray) { - var result = {distances: [], totalDistance: 0} + var result = {distances: [], totalDistance: 0}; for (var i = 1; i < jointNamesArray.length; i++) { var index0 = MyAvatar.getJointIndex(jointNamesArray[i-1]); var index1 = MyAvatar.getJointIndex(jointNamesArray[i]); @@ -244,11 +278,43 @@ return result; } + function dataRelativeToWorld(side, dataIn, dataOut) { + + var handJoint = handJointNames[side]; + var jointIndex = MyAvatar.getJointIndex(handJoint); + var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + + dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex); + // dataOut.perpendicular = Vec3.subtract(MyAvatar.jointToWorldPoint(dataIn.perpendicular, jointIndex), worldPosHand); + var localPerpendicular = side == "right" ? {x:0.2, y:0, z:1} : {x:-0.2, y:0, z:1}; + dataOut.perpendicular = Vec3.normalize(Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand)); + dataOut.distance = dataIn.distance; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + dataOut.fingers[finger] = MyAvatar.jointToWorldPoint(dataIn.fingers[finger], jointIndex); + } + } + + function dataRelativeToHandJoint(side, dataIn, dataOut) { + + var handJoint = handJointNames[side]; + var jointIndex = MyAvatar.getJointIndex(handJoint); + var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + + dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex); + dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex); + dataOut.distance = dataIn.distance; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + dataOut.fingers[finger] = MyAvatar.worldToJointPoint(dataIn.fingers[finger], jointIndex); + } + } + // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays function estimatePalmData(side) { // Return data object - var data = {position: undefined, perpendicular: undefined, distance: undefined, fingers: {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}}; + var data = new Palm(); var jointOffset = { x: 0, y: 0, z: 0 }; @@ -300,69 +366,105 @@ // perpendicular change direction depending on the side data.perpendicular = (side == "right") ? - Vec3.normalize(Vec3.cross(directions["index"], directions["pinky"])): - Vec3.normalize(Vec3.cross(directions["pinky"], directions["index"])); + Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): + Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); data.position = Vec3.multiply(1.0/weightCount, palmCenter); - var palmDistanceMultiplier = 1.55 // 1.55 based on test/error for the sphere radius that best fits the hand - data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions["index"]); + if (side == "right") varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); + + var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand + data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index); // move back thumb ray origin var thumbBackMultiplier = 0.2; - data.fingers["thumb"] = Vec3.sum(data.fingers["thumb"], Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); + data.fingers.thumb = Vec3.sum(data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); - return data; + //return getDataRelativeToHandJoint(side, data); + dataRelativeToHandJoint(side, data, palmData[side]); + palmData[side].set = true; + // return palmData[side]; } // Register GlobalDebugger for API Debugger Script.registerValue("GlobalDebugger", varsToDebug); + + + // store the rays for the fingers - only for debug purposes + + var fingerRays = { + left:{ + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + }, + right:{ + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + } + }; + // Create debug overlays - finger rays + palm rays + spheres - for (var i = 0; i < fingerKeys.length; i++) { - fingerRays["left"][fingerKeys[i]] = Overlays.addOverlay("line3d", { - color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, - visible: showLines - }); - fingerRays["right"][fingerKeys[i]] = Overlays.addOverlay("line3d", { - color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, - visible: showLines - }); + var palmRay, sphereHand; + + function createDebugLines() { + + for (var i = 0; i < fingerKeys.length; i++) { + fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + fingerRays.right[fingerKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + } + + palmRay = { + left: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }), + right: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }) + }; + linesCreated = true; } - var palmRay = { - left: Overlays.addOverlay("line3d", { - color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, - visible: showLines - }), - right: Overlays.addOverlay("line3d", { - color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, - visible: showLines - }) - } - - var sphereHand = { - right: Overlays.addOverlay("sphere", { - position: MyAvatar.position, - color: { red: 0, green: 255, blue: 0 }, - scale: { x: 0.01, y: 0.01, z: 0.01 }, - visible: showSphere - }), - left: Overlays.addOverlay("sphere", { - position: MyAvatar.position, - color: { red: 0, green: 255, blue: 0 }, - scale: { x: 0.01, y: 0.01, z: 0.01 }, - visible: showSphere - }) + function createDebugSphere() { + + sphereHand = { + right: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }), + left: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }) + }; + sphereCreated = true; } function acquireDefaultPose(side) { @@ -372,7 +474,7 @@ var names = getJointNames(side, finger, jointSuffixes); for (var j = 0; j < names.length; j++) { var index = MyAvatar.getJointIndex(names[j]); - var rotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(index)); + var rotation = MyAvatar.getJointRotation(index); dataDefault[side][finger][j] = dataCurrent[side][finger][j] = rotation; } } @@ -380,44 +482,51 @@ } function updateSphereHand(side) { + + var data = new Palm(); + dataRelativeToWorld(side, palmData[side], data); + varsToDebug.palmData[side] = palmData[side]; - var palmData = estimatePalmData(side); - var palmPoint = palmData.position; - var dist = 1.5*palmData.distance; + var palmPoint = data.position; + var LOOKUP_DISTANCE_MULTIPLIER = 1.5; + var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance; // Situate the debugging overlays - var checkOffset = { x: palmData.perpendicular.x * dist, - y: palmData.perpendicular.y * dist, - z: palmData.perpendicular.z * dist }; + var checkOffset = { x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist }; var spherePos = Vec3.sum(palmPoint, checkOffset); var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); - Overlays.editOverlay(palmRay[side], { - start: palmPoint, - end: checkPoint, - visible: showLines - }); - - Overlays.editOverlay(sphereHand[side], { - position: spherePos, - scale: { - x: 2*dist, - y: 2*dist, - z: 2*dist - }, - visible: showSphere - }); - - for (var i = 0; i < fingerKeys.length; i++) { - Overlays.editOverlay(fingerRays[side][fingerKeys[i]], { - start: palmData.fingers[fingerKeys[i]], + if (showLines) { + Overlays.editOverlay(palmRay[side], { + start: palmPoint, end: checkPoint, visible: showLines - }); - } + }); + for (var i = 0; i < fingerKeys.length; i++) { + Overlays.editOverlay(fingerRays[side][fingerKeys[i]], { + start: data.fingers[fingerKeys[i]], + end: checkPoint, + visible: showLines + }); + } + } + + if (showSphere) { + Overlays.editOverlay(sphereHand[side], { + position: spherePos, + scale: { + x: 2*dist, + y: 2*dist, + z: 2*dist + }, + visible: showSphere + }); + } // Update the intersection of only one finger at a time @@ -428,11 +537,11 @@ var animationSteps = defaultAnimationSteps; if (grabbables.length > 0) { - var origin = palmData.fingers[finger]; + var origin = data.fingers[finger]; var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); var percent = 0; // Initialize - var isAbleToGrab = intersection.intersects && intersection.distance < 1.5*dist; + var isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; if (isAbleToGrab && !getTouching(side)) { acquireDefaultPose(side); // take a snapshot of the default pose before touch starts newFingerData = dataDefault[side][finger]; // assign default pose to finger data @@ -441,9 +550,15 @@ isTouching[side][finger] = isAbleToGrab; if (isAbleToGrab) { // update the open/close percentage for this finger - var distanceMultiplier = 2.5; - percent = intersection.distance/(distanceMultiplier*dist); - var grabMultiplier = finger === "thumb" ? 0.2 : 0.05; + + var FINGER_REACT_MULTIPLIER = 2.8; + + percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist); + + var THUMB_FACTOR = 0.2; + var FINGER_FACTOR = 0.05; + + var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; // Amount of grab coefficient added to the fingers - thumb is higher percent += grabMultiplier * grabPercent[side]; // Calculate new interpolation data @@ -456,6 +571,7 @@ // Calculate animation increments dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); + } // Recreate the finger joint names @@ -474,7 +590,7 @@ var leftTriggerPress = function (value) { varsToDebug.triggerValues.leftTriggerValue = value; // the value for the trigger increments the hand-close percentage - grabPercent["left"] = value; + grabPercent.left = value; }; var leftTriggerClick = function (value) { varsToDebug.triggerValues.leftTriggerClicked = value; @@ -482,7 +598,7 @@ var rightTriggerPress = function (value) { varsToDebug.triggerValues.rightTriggerValue = value; // the value for the trigger increments the hand-close percentage - grabPercent["right"] = value; + grabPercent.right = value; }; var rightTriggerClick = function (value) { varsToDebug.triggerValues.rightTriggerClicked = value; @@ -508,6 +624,14 @@ Controller.enableMapping(MAPPING_NAME); + if (showLines && !linesCreated) { + createDebugLines(); + linesCreated = true; + } + if (showSphere && !sphereCreated) { + createDebugSphere(); + sphereCreated = true; + } function getTouching(side) { var animating = false; @@ -518,16 +642,67 @@ return animating; // return false only if none of the fingers are touching } + function reEstimatePalmData() { + ["right", "left"].forEach(function(side){ + estimatePalmData(side); + }); + } + + MyAvatar.onLoadComplete.connect(function () { + // Sometimes the rig is not ready when this signal is trigger + console.log("avatar loaded"); + Script.setInterval(function(){ + reEstimatePalmData(); + }, 2000); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(function(){ + reEstimatePalmData(); + }); + + Script.scriptEnding.connect(function () { + ["right", "left"].forEach(function(side){ + if (linesCreated) { + Overlays.deleteOverlay(palmRay[side]); + } + if (sphereCreated) { + Overlays.deleteOverlay(sphereHand[side]); + } + for (var i = 0; i < fingerKeys.length; i++) { + + var finger = fingerKeys[i]; + var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); + + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + MyAvatar.clearJointData(index); + } + + if (linesCreated) { + Overlays.deleteOverlay(fingerRays[side][finger]); + } + } + }); + + + + }); + Script.update.connect(function(){ // index of the finger that needs to be updated this frame + + updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0; - ["right", "left"].forEach(function(side){ - + + if (!palmData[side].set) { + reEstimatePalmData(); + } // recalculate the base data updateSphereHand(side); @@ -551,7 +726,7 @@ var index = MyAvatar.getJointIndex(names[j]); // if no finger is touching restate the default poses if (isHandTouching || (dataDefault[side].set && countToDefault[side] < 5*touchAnimationSteps)) { - var quatRot = Quat.fromVec3Degrees(dataCurrent[side][finger][j]); + var quatRot = dataCurrent[side][finger][j]; MyAvatar.setJointRotation(index, quatRot); } else { MyAvatar.clearJointData(index); @@ -561,4 +736,4 @@ }); }); -}()) +}()); From 50f89182d72b7db93a4d6bf9e54a4ea9bb1121c1 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 7 Jan 2018 18:29:13 -0700 Subject: [PATCH 10/10] Add Utils.jsc --- interface/resources/qml/js/Utils.jsc | Bin 0 -> 6596 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface/resources/qml/js/Utils.jsc diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc new file mode 100644 index 0000000000000000000000000000000000000000..ab20e996b9469915ac6a89901da175143e6b5024 GIT binary patch literal 6596 zcmcIoZD?EP6@Kh1ckah_<+cmm)~+s7*+{cEDOm|)Y*uPBiCux$Iwl#JYtNP&7t3}W zIXI&o4oU8EkM2n zXjl7O2dDtRuNCd{@)jY-)d!#zenqKY4sn&W{weu)twmlb!$4JTzv65rm+K1vDC#!F zU$|ZVIZ9zlW+S<`HL;hgR&M8~1sQiLyXg~6*Kx4qHtya=?){4Iz`gR%B^6)FqD(EX zPx0NgjXdr9YAbNP75KOnxYqy<89DFo`<=jA05hh+!CUJc)7q5K-I-U<4BwMhw#!Kold03qHB1qPh=(m57jV zQZO*TlsKpO<^4D&)ji|-UBpF<$-`KmCFTK~acFxKqv8qb`vJ}(jwnv6ynF{ogq(=F zep_Ap)wNGud)4)O>UtbUguEF{VOB&&#)x21=BI^yoeI;=cpet< z8yEavN0<0LF8)1^DU2eDE}X7EzTC=9#(VVvU%?VLXPX@E8u^tC$cdhzrY~ z5|TzSE|9{#2}}z<=It55SzSj%iNA*yHv6nZbjwP_N>*aF82-K7|F|4Z{3((gYY*l1 z1HmPNES&HK!VAmo!G#Z7%nSc2m&=jFSbJz;vopy0hR~Acw-O%(%P$^o0YVGQoxRBc zfA58N%nJ{-l0bHGCbwg^#q(qrrDi+!w<_$cdfT#JQtVa6B|0HZKTvD>SPT8{3FK*i zWU(iZU!oSteUbW{j!MrrowDh%hwMA*Wz%5~+1G_Km+UHco!!3?d2`uawWQAO`Fhz^ zFY9DKQ7^k1!8+Li%B~H>BZmbX7dAVWSkh#gI3&0r zaF0rAW;@0YRTR^~w|fH1=rq7T1!gIn>dAFt!Sy*C45#|hZ zyKMQfPE=zR8BZ12Iu&e;jZ`qZ;(XBNe0X7VdI#CX&hyK$=>L{qIw{h|R_2l4RI7AY z^-G-Fg*Pl@c)U6&I`)6~vWKqD3?b!JxCu1uN`cA?Q-i-75ra?*w4> z7DLeZEXfck*xy13exhOT)~XM$^JKYWuGBjYFUK{J-kQV9*AmCcI=9Ipz3t&zt&~o& zbp5o=j@d0M5iMDXSkX$%t~YJ^dxYvnP2X7@RT0fd+JSJgHI(cgqRD9&=_E-h7ipTL zi!Ra}N%Kz9JRJ@v&Ijf=afs?ImCUopaAUf5Bc(P@)^4QL#!&4B4=hC;lN+q^_W+Q1 zP^n|i{5|@(6kNweRp@!g!IB0$&XzP(9WOq8UKl6|MM0Czbbp-MJtdAmS4|C?43SDf)z9}dNlLtGo5xl*UM-i)V9TyEvOFWw5I(m_-&8|fx011{`>NNH;(6Df(rr#QsN>-}$CJna|wOpzoRcq-Fq`bAH8fghvwNyJ`N_RlpJYiGQZuHu}HZ5Pa zdE8c|q5`8P==g;$uId?L_>-ZdTT7Ja(9^jWIyw}4%`oqUj`vkn%QKo+>hkt%Am^R8 zT%)}GyrqJUtqMAFDsNlV@