overte/examples/leapHands.js

395 lines
15 KiB
JavaScript

//
// leapHands.js
// examples
//
// Created by David Rowe on 8 Sep 2014.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example script that uses the Leap Motion to make the avatar's hands replicate the user's hand actions.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var leapHands = (function () {
var hands,
wrists,
NUM_HANDS = 2, // 0 = left; 1 = right
fingers,
NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky
THUMB = 0,
NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal(tip) joint
MAX_HAND_INACTIVE_COUNT = 20,
calibrationStatus,
UNCALIBRATED = 0,
CALIBRATING = 1,
CALIBRATED = 2,
CALIBRATION_TIME = 1000, // milliseconds
PI = 3.141593,
isWindows;
function printSkeletonJointNames() {
var jointNames,
i;
print(MyAvatar.skeletonModelURL);
print("Skeleton joint names ...");
jointNames = MyAvatar.getJointNames();
for (i = 0; i < jointNames.length; i += 1) {
print(i + ": " + jointNames[i]);
}
print("... skeleton joint names");
/*
http://public.highfidelity.io/models/skeletons/ron_standing.fst
Skeleton joint names ...
0: Hips
1: RightUpLeg
2: RightLeg
3: RightFoot
4: RightToeBase
5: RightToe_End
6: LeftUpLeg
7: LeftLeg
8: LeftFoot
9: LeftToeBase
10: LeftToe_End
11: Spine
12: Spine1
13: Spine2
14: RightShoulder
15: RightArm
16: RightForeArm
17: RightHand
18: RightHandPinky1
19: RightHandPinky2
20: RightHandPinky3
21: RightHandPinky4
22: RightHandRing1
23: RightHandRing2
24: RightHandRing3
25: RightHandRing4
26: RightHandMiddle1
27: RightHandMiddle2
28: RightHandMiddle3
29: RightHandMiddle4
30: RightHandIndex1
31: RightHandIndex2
32: RightHandIndex3
33: RightHandIndex4
34: RightHandThumb1
35: RightHandThumb2
36: RightHandThumb3
37: RightHandThumb4
38: LeftShoulder
39: LeftArm
40: LeftForeArm
41: LeftHand
42: LeftHandPinky1
43: LeftHandPinky2
44: LeftHandPinky3
45: LeftHandPinky4
46: LeftHandRing1
47: LeftHandRing2
48: LeftHandRing3
49: LeftHandRing4
50: LeftHandMiddle1
51: LeftHandMiddle2
52: LeftHandMiddle3
53: LeftHandMiddle4
54: LeftHandIndex1
55: LeftHandIndex2
56: LeftHandIndex3
57: LeftHandIndex4
58: LeftHandThumb1
59: LeftHandThumb2
60: LeftHandThumb3
61: LeftHandThumb4
62: Neck
63: Head
64: HeadTop_End
65: body
... skeleton joint names
*/
}
function finishCalibration() {
var avatarPosition,
avatarOrientation,
avatarHandPosition,
leapHandHeight,
h;
if (hands[0].controller.isActive() && hands[1].controller.isActive()) {
leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0;
// TODO: Temporary detection of Windows to work around Leap Controller problem.
isWindows = (hands[1].controller.getAbsRotation().z > (0.25 * PI));
} else {
calibrationStatus = UNCALIBRATED;
return;
}
avatarPosition = MyAvatar.position;
avatarOrientation = MyAvatar.orientation;
for (h = 0; h < NUM_HANDS; h += 1) {
avatarHandPosition = MyAvatar.getJointPosition(hands[h].jointName);
hands[h].zeroPosition = {
x: avatarHandPosition.x - avatarPosition.x,
y: avatarHandPosition.y - avatarPosition.y,
z: avatarPosition.z - avatarHandPosition.z
};
hands[h].zeroPosition = Vec3.multiplyQbyV(MyAvatar.orientation, hands[h].zeroPosition);
hands[h].zeroPosition.y = hands[h].zeroPosition.y - leapHandHeight;
}
MyAvatar.clearJointData("LeftHand");
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
MyAvatar.clearJointData("RightHand");
MyAvatar.clearJointData("RightForeArm");
MyAvatar.clearJointData("RightArm");
calibrationStatus = CALIBRATED;
print("Leap Motion: Calibrated");
}
function calibrate() {
calibrationStatus = CALIBRATING;
// Set avatar arms vertical, forearms horizontal, as "zero" position for calibration
MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0));
MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0));
MyAvatar.setJointData("LeftHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 90.0));
MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0));
MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
// Wait for arms to assume their positions before calculating
Script.setTimeout(finishCalibration, CALIBRATION_TIME);
}
function checkCalibration() {
if (calibrationStatus === CALIBRATED) {
return true;
}
if (calibrationStatus !== CALIBRATING) {
calibrate();
}
return false;
}
function setUp() {
calibrationStatus = UNCALIBRATED;
// TODO: Leap Motion controller joint naming doesn't match up with skeleton joint naming; numbers are out by 1.
hands = [
{
jointName: "LeftHand",
controller: Controller.createInputController("Spatial", "joint_L_hand"),
inactiveCount: 0
},
{
jointName: "RightHand",
controller: Controller.createInputController("Spatial", "joint_R_hand"),
inactiveCount: 0
}
];
wrists = [
{ controller: Controller.createInputController("Spatial", "joint_L_wrist") },
{ controller: Controller.createInputController("Spatial", "joint_R_wrist") }
];
fingers = [{}, {}];
fingers[0] = [
[
{ jointName: "LeftHandThumb1", controller: Controller.createInputController("Spatial", "joint_L_thumb2") },
{ jointName: "LeftHandThumb2", controller: Controller.createInputController("Spatial", "joint_L_thumb3") },
{ jointName: "LeftHandThumb3", controller: Controller.createInputController("Spatial", "joint_L_thumb4") }
],
[
{ jointName: "LeftHandIndex1", controller: Controller.createInputController("Spatial", "joint_L_index2") },
{ jointName: "LeftHandIndex2", controller: Controller.createInputController("Spatial", "joint_L_index3") },
{ jointName: "LeftHandIndex3", controller: Controller.createInputController("Spatial", "joint_L_index4") }
],
[
{ jointName: "LeftHandMiddle1", controller: Controller.createInputController("Spatial", "joint_L_middle2") },
{ jointName: "LeftHandMiddle2", controller: Controller.createInputController("Spatial", "joint_L_middle3") },
{ jointName: "LeftHandMiddle3", controller: Controller.createInputController("Spatial", "joint_L_middle4") }
],
[
{ jointName: "LeftHandRing1", controller: Controller.createInputController("Spatial", "joint_L_ring2") },
{ jointName: "LeftHandRing2", controller: Controller.createInputController("Spatial", "joint_L_ring3") },
{ jointName: "LeftHandRing3", controller: Controller.createInputController("Spatial", "joint_L_ring4") }
],
[
{ jointName: "LeftHandPinky1", controller: Controller.createInputController("Spatial", "joint_L_pinky2") },
{ jointName: "LeftHandPinky2", controller: Controller.createInputController("Spatial", "joint_L_pinky3") },
{ jointName: "LeftHandPinky3", controller: Controller.createInputController("Spatial", "joint_L_pinky4") }
]
];
fingers[1] = [
[
{ jointName: "RightHandThumb1", controller: Controller.createInputController("Spatial", "joint_R_thumb2") },
{ jointName: "RightHandThumb2", controller: Controller.createInputController("Spatial", "joint_R_thumb3") },
{ jointName: "RightHandThumb3", controller: Controller.createInputController("Spatial", "joint_R_thumb4") }
],
[
{ jointName: "RightHandIndex1", controller: Controller.createInputController("Spatial", "joint_R_index2") },
{ jointName: "RightHandIndex2", controller: Controller.createInputController("Spatial", "joint_R_index3") },
{ jointName: "RightHandIndex3", controller: Controller.createInputController("Spatial", "joint_R_index4") }
],
[
{ jointName: "RightHandMiddle1", controller: Controller.createInputController("Spatial", "joint_R_middle2") },
{ jointName: "RightHandMiddle2", controller: Controller.createInputController("Spatial", "joint_R_middle3") },
{ jointName: "RightHandMiddle3", controller: Controller.createInputController("Spatial", "joint_R_middle4") }
],
[
{ jointName: "RightHandRing1", controller: Controller.createInputController("Spatial", "joint_R_ring2") },
{ jointName: "RightHandRing2", controller: Controller.createInputController("Spatial", "joint_R_ring3") },
{ jointName: "RightHandRing3", controller: Controller.createInputController("Spatial", "joint_R_ring4") }
],
[
{ jointName: "RightHandPinky1", controller: Controller.createInputController("Spatial", "joint_R_pinky2") },
{ jointName: "RightHandPinky2", controller: Controller.createInputController("Spatial", "joint_R_pinky3") },
{ jointName: "RightHandPinky3", controller: Controller.createInputController("Spatial", "joint_R_pinky4") }
]
];
}
function moveHands() {
var h,
i,
j,
side,
handOffset,
handRoll,
handPitch,
handYaw,
handRotation,
wristAbsRotation,
locRotation;
for (h = 0; h < NUM_HANDS; h += 1) {
side = h === 0 ? -1.0 : 1.0;
if (hands[h].controller.isActive()) {
// Calibrate when and if a controller is first active.
if (!checkCalibration()) {
return;
}
// Hand position ...
handOffset = hands[h].controller.getAbsTranslation();
handOffset = {
x: -handOffset.x,
y: hands[h].zeroPosition.y + handOffset.y,
z: hands[h].zeroPosition.z - handOffset.z
};
// TODO: 2.0* scale factor should not be necessary; Leap Motion controller code needs investigating.
handRoll = 2.0 * -hands[h].controller.getAbsRotation().z;
wristAbsRotation = wrists[h].controller.getAbsRotation();
handPitch = 2.0 * -wristAbsRotation.x;
handYaw = 2.0 * wristAbsRotation.y;
// TODO: Leap Motion controller's right-hand roll calculation only works if physical hand is upside down.
// Approximate fix is to add a fudge factor.
if (h === 1 && isWindows) {
handRoll = handRoll + 0.6 * PI;
}
// Hand position and orientation ...
if (h === 0) {
handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }),
Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch }));
} else {
handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }),
Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch }));
}
MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true);
// Finger joints ...
// TODO: 2.0 * scale factors should not be necessary; Leap Motion controller code needs investigating.
for (i = 0; i < NUM_FINGERS; i += 1) {
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
if (fingers[h][i][j].controller !== null) {
locRotation = fingers[h][i][j].controller.getLocRotation();
if (i === THUMB) {
MyAvatar.setJointData(fingers[h][i][j].jointName,
Quat.fromPitchYawRollRadians(2.0 * side * locRotation.y, 2.0 * -locRotation.z,
2.0 * side * -locRotation.x));
} else {
MyAvatar.setJointData(fingers[h][i][j].jointName,
Quat.fromPitchYawRollRadians(2.0 * -locRotation.x, 0.0, 2.0 * -locRotation.y));
}
}
}
}
hands[h].inactiveCount = 0;
} else {
hands[h].inactiveCount += 1;
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
if (h === 0) {
MyAvatar.clearJointData("LeftHand");
MyAvatar.clearJointData("LeftForeArm");
MyAvatar.clearJointData("LeftArm");
} else {
MyAvatar.clearJointData("RightHand");
MyAvatar.clearJointData("RightForeArm");
MyAvatar.clearJointData("RightArm");
}
}
}
}
}
function tearDown() {
var h,
i,
j;
for (h = 0; h < NUM_HANDS; h += 1) {
Controller.releaseInputController(hands[h].controller);
Controller.releaseInputController(wrists[h].controller);
for (i = 0; i < NUM_FINGERS; i += 1) {
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
if (fingers[h][i][j].controller !== null) {
Controller.releaseInputController(fingers[h][i][j].controller);
}
}
}
}
}
return {
printSkeletonJointNames: printSkeletonJointNames,
setUp : setUp,
moveHands : moveHands,
tearDown : tearDown
};
}());
//leapHands.printSkeletonJointNames();
leapHands.setUp();
Script.update.connect(leapHands.moveHands);
Script.scriptEnding.connect(leapHands.tearDown);