//// 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// /* jslint bitwise: true */ /* global Script, Overlays, Controller, Vec3, MyAvatar, Entities, RayPick*/ (function () { var controllerStandard = Controller.Standard; var LEAP_MOTION_NAME = "LeapMotion"; // Hand touch is disabled due to twitchy finger bug when walking near walls or tables. see BUGZ-154. var handTouchEnabled = false; var leapMotionEnabled = Controller.getRunningInputDeviceNames().indexOf(LEAP_MOTION_NAME) >= 0; var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; var untouchableEntities = []; // Keys to access finger data var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; // Additionally close the hands to achieve a grabbing effect var grabPercent = { left: 0, right: 0 }; 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: { pinky: false, middle: false, ring: false, thumb: false, index: false }, right: { pinky: false, middle: false, ring: false, thumb: false, index: false } }; // frame count for transition to default pose var countToDefault = { left: 0, right: 0 }; // joint data for open pose var dataOpen = { left: { 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.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 close pose var dataClose = { left: { 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: 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, 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, 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, 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, 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, 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, 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) var touchAnimationSteps = 5; // Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out) var defaultAnimationSteps = 10; // Debugging info var showSphere = false; var showLines = false; // This get setup on creation 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: { pinky: 0.38, middle: 0.38, ring: 0.38, thumb: 0.38, index: 0.38 } , right: { pinky: 0.38, middle: 0.38, ring: 0.38, thumb: 0.38, index: 0.38 } }, triggerValues: { leftTriggerValue: 0, leftTriggerClicked: 0, rightTriggerValue: 0, 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 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, 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; } // Multiply/Divide the joint data - per finger joint function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { 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; } // Calculate the finger lengths by adding its joint lengths 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 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); 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 touch field; Sphere at 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 = new Palm(); 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 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); weightCount += handJointWeight; for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; 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]); 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") { // finger joints have double the weight than the hand joint // This would better position the palm estimation palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); weightCount += fingerJointWeight; } else { thumbLength = fingerLength; } } // 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/weightCount, palmCenter); 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)); // return getDataRelativeToHandJoint(side, data); dataRelativeToHandJoint(side, data, palmData[side]); palmData[side].set = true; } // 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 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 });Lines 1–500 of 960. Load more
// // 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 // /* jslint bitwise: true */ /* global Script, Overlays, Controller, Vec3, MyAvatar, Entities, RayPick */ (function () { var controllerStandard = Controller.Standard; var LEAP_MOTION_NAME = "LeapMotion"; // Hand touch is disabled due to twitchy finger bug when walking near walls or tables. see BUGZ-154. var handTouchEnabled = false; var leapMotionEnabled = Controller.getRunningInputDeviceNames().indexOf(LEAP_MOTION_NAME) >= 0; var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; var untouchableEntities = []; // Keys to access finger data var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; // Additionally close the hands to achieve a grabbing effect var grabPercent = { left: 0, right: 0 }; 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: { pinky: false, middle: false, ring: false, thumb: false, index: false }, right: { pinky: false, middle: false, ring: false, thumb: false, index: false } }; // frame count for transition to default pose var countToDefault = { left: 0, right: 0 }; // joint data for open pose var dataOpen = { left: { 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.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 close pose var dataClose = { left: { 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: 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, 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, 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, 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, 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, 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, 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) var touchAnimationSteps = 5; // Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out) var defaultAnimationSteps = 10; // Debugging info var showSphere = false; var showLines = false; // This get setup on creation 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: { pinky: 0.38, middle: 0.38, ring: 0.38, thumb: 0.38, index: 0.38 } , right: { pinky: 0.38, middle: 0.38, ring: 0.38, thumb: 0.38, index: 0.38 } }, triggerValues: { leftTriggerValue: 0, leftTriggerClicked: 0, rightTriggerValue: 0, 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 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, 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; } // Multiply/Divide the joint data - per finger joint function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { 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; } // Calculate the finger lengths by adding its joint lengths 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 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); 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 touch field; Sphere at 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 = new Palm(); 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 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); weightCount += handJointWeight; for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; 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]); 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") { // finger joints have double the weight than the hand joint // This would better position the palm estimation palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); weightCount += fingerJointWeight; } else { thumbLength = fingerLength; } } // 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/weightCount, palmCenter); 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)); // return getDataRelativeToHandJoint(side, data); dataRelativeToHandJoint(side, data, palmData[side]); palmData[side].set = true; } // 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 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 });