mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-06 21:42:37 +02:00
This PR disables handTouch.js. handTouch.js allows the fingers to animate on the surface of an object. However, it can sometimes detect collisions with walls or tables when the avatar is standing next to them. We will more properly fix handTouch.js in a future PR, but for now we will disable the functionality. Also, a small bug fix was made to the Rig to prevent the idleOverlayAlpha from exceeding the 0.0 to 1.0 range. This can cause the fingers to bend incorrectly for a moment. Also, three new items were added to the Developer > Show Animation Stats panel. * Joint Override Count: displays the current count of joints that are overriden by MyAvatar.setJointRotation() JS API calls. * Flow: displays if flow is active of disabled. * Network Graph: displays if the network anim graph, used for teleportation, is enabled or disabled. https://highfidelity.atlassian.net/browse/BUGZ-154
959 lines
35 KiB
JavaScript
959 lines
35 KiB
JavaScript
//
|
|
// 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 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
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
for (var i = 0; i < fingerKeys.length; i++) {
|
|
var finger = fingerKeys[i];
|
|
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 = MyAvatar.getJointRotation(index);
|
|
dataDefault[side][finger][j] = dataCurrent[side][finger][j] = rotation;
|
|
}
|
|
}
|
|
dataDefault[side].set = true;
|
|
}
|
|
|
|
var rayPicks = {
|
|
left: {
|
|
pinky: undefined,
|
|
middle: undefined,
|
|
ring: undefined,
|
|
thumb: undefined,
|
|
index: undefined
|
|
},
|
|
right: {
|
|
pinky: undefined,
|
|
middle: undefined,
|
|
ring: undefined,
|
|
thumb: undefined,
|
|
index: undefined
|
|
}
|
|
};
|
|
|
|
var dataFailed = {
|
|
left: {
|
|
pinky: 0,
|
|
middle: 0,
|
|
ring: 0,
|
|
thumb: 0,
|
|
index: 0
|
|
},
|
|
right: {
|
|
pinky: 0,
|
|
middle: 0,
|
|
ring: 0,
|
|
thumb: 0,
|
|
index: 0
|
|
}
|
|
};
|
|
|
|
function clearRayPicks(side) {
|
|
for (var i = 0; i < fingerKeys.length; i++) {
|
|
var finger = fingerKeys[i];
|
|
if (rayPicks[side][finger] !== undefined) {
|
|
RayPick.removeRayPick(rayPicks[side][finger]);
|
|
rayPicks[side][finger] = undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createRayPicks(side) {
|
|
var data = palmData[side];
|
|
clearRayPicks(side);
|
|
for (var i = 0; i < fingerKeys.length; i++) {
|
|
var finger = fingerKeys[i];
|
|
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
|
|
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
|
|
var checkOffset = {
|
|
x: data.perpendicular.x * dist,
|
|
y: data.perpendicular.y * dist,
|
|
z: data.perpendicular.z * dist
|
|
};
|
|
|
|
var checkPoint = Vec3.sum(data.position, Vec3.multiply(2, checkOffset));
|
|
var sensorToWorldScale = MyAvatar.getSensorToWorldScale();
|
|
|
|
var origin = data.fingers[finger];
|
|
|
|
var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin));
|
|
|
|
origin = Vec3.multiply(1/sensorToWorldScale, origin);
|
|
|
|
rayPicks[side][finger] = RayPick.createRayPick(
|
|
{
|
|
"enabled": false,
|
|
"joint": handJointNames[side],
|
|
"posOffset": origin,
|
|
"dirOffset": direction,
|
|
"filter": RayPick.PICK_ENTITIES
|
|
}
|
|
);
|
|
|
|
RayPick.setPrecisionPicking(rayPicks[side][finger], true);
|
|
}
|
|
}
|
|
|
|
function activateNextRay(side, index) {
|
|
var nextIndex = (index < fingerKeys.length-1) ? index + 1 : 0;
|
|
for (var i = 0; i < fingerKeys.length; i++) {
|
|
var finger = fingerKeys[i];
|
|
if (i === nextIndex) {
|
|
RayPick.enableRayPick(rayPicks[side][finger]);
|
|
} else {
|
|
RayPick.disableRayPick(rayPicks[side][finger]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateSphereHand(side) {
|
|
var data = new Palm();
|
|
dataRelativeToWorld(side, palmData[side], data);
|
|
varsToDebug.palmData[side] = palmData[side];
|
|
|
|
var palmPoint = data.position;
|
|
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
|
|
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
|
|
|
|
// Situate the debugging overlays
|
|
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));
|
|
|
|
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
|
|
var finger = fingerKeys[updateFingerWithIndex];
|
|
var nearbyEntities = Entities.findEntities(spherePos, dist);
|
|
// Filter the entities that are allowed to be touched
|
|
var touchableEntities = nearbyEntities.filter(function (id) {
|
|
return untouchableEntities.indexOf(id) == -1;
|
|
});
|
|
var intersection;
|
|
if (rayPicks[side][finger] !== undefined) {
|
|
intersection = RayPick.getPrevRayPickResult(rayPicks[side][finger]);
|
|
}
|
|
|
|
var animationSteps = defaultAnimationSteps;
|
|
var newFingerData = dataDefault[side][finger];
|
|
var isAbleToGrab = false;
|
|
if (touchableEntities.length > 0) {
|
|
RayPick.setIncludeItems(rayPicks[side][finger], touchableEntities);
|
|
|
|
if (intersection === undefined) {
|
|
return;
|
|
}
|
|
|
|
var percent = 0; // Initialize
|
|
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
|
|
}
|
|
// Store if this finger is touching something
|
|
isTouching[side][finger] = isAbleToGrab;
|
|
if (isAbleToGrab) {
|
|
// update the open/close percentage for this finger
|
|
var FINGER_REACT_MULTIPLIER = 2.8;
|
|
|
|
percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist);
|
|
|
|
var THUMB_FACTOR = 0.2;
|
|
var FINGER_FACTOR = 0.05;
|
|
|
|
// Amount of grab coefficient added to the fingers - thumb is higher
|
|
var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR;
|
|
percent += grabMultiplier * grabPercent[side];
|
|
|
|
// Calculate new interpolation data
|
|
var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1);
|
|
// Assign close/open ratio to finger to simulate touch
|
|
newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1);
|
|
animationSteps = touchAnimationSteps;
|
|
}
|
|
varsToDebug.fingerPercent[side][finger] = percent;
|
|
|
|
}
|
|
if (!isAbleToGrab) {
|
|
dataFailed[side][finger] = dataFailed[side][finger] === 0 ? 1 : 2;
|
|
} else {
|
|
dataFailed[side][finger] = 0;
|
|
}
|
|
// If it only fails once it will not update increments
|
|
if (dataFailed[side][finger] !== 1) {
|
|
// Calculate animation increments
|
|
dataDelta[side][finger] =
|
|
multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps);
|
|
}
|
|
}
|
|
|
|
// Recreate the finger joint names
|
|
function getJointNames(side, finger, count) {
|
|
var names = [];
|
|
for (var i = 1; i < count+1; i++) {
|
|
var name = side[0].toUpperCase()+side.substring(1)+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i);
|
|
names.push(name);
|
|
}
|
|
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) {
|
|
varsToDebug.triggerValues.leftTriggerClicked = value;
|
|
};
|
|
|
|
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) {
|
|
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);
|
|
|
|
if (showLines && !linesCreated) {
|
|
createDebugLines();
|
|
linesCreated = true;
|
|
}
|
|
|
|
if (showSphere && !sphereCreated) {
|
|
createDebugSphere();
|
|
sphereCreated = true;
|
|
}
|
|
|
|
function getTouching(side) {
|
|
var animating = false;
|
|
for (var i = 0; i < fingerKeys.length; i++) {
|
|
var finger = fingerKeys[i];
|
|
animating = animating || isTouching[side][finger];
|
|
}
|
|
return animating; // return false only if none of the fingers are touching
|
|
}
|
|
|
|
function reEstimatePalmData() {
|
|
["right", "left"].forEach(function(side) {
|
|
estimatePalmData(side);
|
|
});
|
|
}
|
|
|
|
function recreateRayPicks() {
|
|
["right", "left"].forEach(function(side) {
|
|
createRayPicks(side);
|
|
});
|
|
}
|
|
|
|
function cleanUp() {
|
|
["right", "left"].forEach(function (side) {
|
|
if (linesCreated) {
|
|
Overlays.deleteOverlay(palmRay[side]);
|
|
}
|
|
if (sphereCreated) {
|
|
Overlays.deleteOverlay(sphereHand[side]);
|
|
}
|
|
clearRayPicks(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]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
MyAvatar.shouldDisableHandTouchChanged.connect(function (shouldDisable) {
|
|
if (shouldDisable) {
|
|
if (handTouchEnabled) {
|
|
cleanUp();
|
|
}
|
|
} else {
|
|
if (!handTouchEnabled) {
|
|
reEstimatePalmData();
|
|
recreateRayPicks();
|
|
}
|
|
}
|
|
handTouchEnabled = !shouldDisable;
|
|
});
|
|
|
|
Controller.inputDeviceRunningChanged.connect(function (deviceName, isEnabled) {
|
|
if (deviceName == LEAP_MOTION_NAME) {
|
|
leapMotionEnabled = isEnabled;
|
|
}
|
|
});
|
|
|
|
MyAvatar.disableHandTouchForIDChanged.connect(function (entityID, disable) {
|
|
var entityIndex = untouchableEntities.indexOf(entityID);
|
|
if (disable) {
|
|
if (entityIndex == -1) {
|
|
untouchableEntities.push(entityID);
|
|
}
|
|
} else {
|
|
if (entityIndex != -1) {
|
|
untouchableEntities.splice(entityIndex, 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
MyAvatar.onLoadComplete.connect(function () {
|
|
// Sometimes the rig is not ready when this signal is trigger
|
|
console.log("avatar loaded");
|
|
Script.setTimeout(function() {
|
|
reEstimatePalmData();
|
|
recreateRayPicks();
|
|
}, MSECONDS_AFTER_LOAD);
|
|
});
|
|
|
|
MyAvatar.sensorToWorldScaleChanged.connect(function() {
|
|
reEstimatePalmData();
|
|
});
|
|
|
|
Script.scriptEnding.connect(function () {
|
|
cleanUp();
|
|
});
|
|
|
|
Script.update.connect(function () {
|
|
|
|
if (!handTouchEnabled || leapMotionEnabled) {
|
|
return;
|
|
}
|
|
|
|
// 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();
|
|
recreateRayPicks();
|
|
}
|
|
|
|
// recalculate the base data
|
|
updateSphereHand(side);
|
|
activateNextRay(side, updateFingerWithIndex);
|
|
|
|
// 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 jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints
|
|
var names = getJointNames(side, finger, jointSuffixes);
|
|
|
|
// 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] < fingerKeys.length*touchAnimationSteps)) {
|
|
var quatRot = dataCurrent[side][finger][j];
|
|
MyAvatar.setJointRotation(index, quatRot);
|
|
} else {
|
|
MyAvatar.clearJointData(index);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}());
|