Merge pull request #10797 from ctrlaltdavid/21396

Make Leap Motion a plugin
This commit is contained in:
Brad Davis 2017-06-30 14:37:23 -07:00 committed by GitHub
commit 31d2c9980e
31 changed files with 993 additions and 1478 deletions

View file

@ -0,0 +1,12 @@
#
# Created by David Rowe on 16 Jun 2017.
# 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
#
macro(TARGET_LEAPMOTION)
target_include_directories(${TARGET_NAME} PRIVATE ${LEAPMOTION_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${LEAPMOTION_LIBRARIES})
endmacro()

View file

@ -10,7 +10,7 @@ Interface has been tested with SDK versions:
1. Copy the LeapSDK folders from the LeapDeveloperKit installation directory (Lib, Include) into the interface/externals/leapmotion folder.
This readme.txt should be there as well.
The files neeeded in the folders are:
The files needed in the folders are:
include/
- Leap.h
@ -21,8 +21,8 @@ Interface has been tested with SDK versions:
x86/
- Leap.dll
- Leap.lib
- mscvcp120.dll (optional if you already have the Msdev 2012 SDK redistriuable installed)
- mscvcr120.dll (optional if you already have the Msdev 2012 SDK redistriuable installed)
- mscvcp120.dll (optional if you already have the Msdev 2012 SDK redistributable installed)
- mscvcr120.dll (optional if you already have the Msdev 2012 SDK redistributable installed)
- lipLeap.dylib
libc++/
-libLeap.dylib

View file

@ -0,0 +1,48 @@
{
"name": "Leap Motion to Standard",
"channels": [
{ "from": "LeapMotion.LeftHand", "to": "Standard.LeftHand" },
{ "from": "LeapMotion.LeftHandThumb1", "to": "Standard.LeftHandThumb1"},
{ "from": "LeapMotion.LeftHandThumb2", "to": "Standard.LeftHandThumb2"},
{ "from": "LeapMotion.LeftHandThumb3", "to": "Standard.LeftHandThumb3"},
{ "from": "LeapMotion.LeftHandThumb4", "to": "Standard.LeftHandThumb4"},
{ "from": "LeapMotion.LeftHandIndex1", "to": "Standard.LeftHandIndex1"},
{ "from": "LeapMotion.LeftHandIndex2", "to": "Standard.LeftHandIndex2"},
{ "from": "LeapMotion.LeftHandIndex3", "to": "Standard.LeftHandIndex3"},
{ "from": "LeapMotion.LeftHandIndex4", "to": "Standard.LeftHandIndex4"},
{ "from": "LeapMotion.LeftHandMiddle1", "to": "Standard.LeftHandMiddle1"},
{ "from": "LeapMotion.LeftHandMiddle2", "to": "Standard.LeftHandMiddle2"},
{ "from": "LeapMotion.LeftHandMiddle3", "to": "Standard.LeftHandMiddle3"},
{ "from": "LeapMotion.LeftHandMiddle4", "to": "Standard.LeftHandMiddle4"},
{ "from": "LeapMotion.LeftHandRing1", "to": "Standard.LeftHandRing1"},
{ "from": "LeapMotion.LeftHandRing2", "to": "Standard.LeftHandRing2"},
{ "from": "LeapMotion.LeftHandRing3", "to": "Standard.LeftHandRing3"},
{ "from": "LeapMotion.LeftHandRing4", "to": "Standard.LeftHandRing4"},
{ "from": "LeapMotion.LeftHandPinky1", "to": "Standard.LeftHandPinky1"},
{ "from": "LeapMotion.LeftHandPinky2", "to": "Standard.LeftHandPinky2"},
{ "from": "LeapMotion.LeftHandPinky3", "to": "Standard.LeftHandPinky3"},
{ "from": "LeapMotion.LeftHandPinky4", "to": "Standard.LeftHandPinky4"},
{ "from": "LeapMotion.RightHand", "to": "Standard.RightHand" },
{ "from": "LeapMotion.RightHandThumb1", "to": "Standard.RightHandThumb1"},
{ "from": "LeapMotion.RightHandThumb2", "to": "Standard.RightHandThumb2"},
{ "from": "LeapMotion.RightHandThumb3", "to": "Standard.RightHandThumb3"},
{ "from": "LeapMotion.RightHandThumb4", "to": "Standard.RightHandThumb4"},
{ "from": "LeapMotion.RightHandIndex1", "to": "Standard.RightHandIndex1"},
{ "from": "LeapMotion.RightHandIndex2", "to": "Standard.RightHandIndex2"},
{ "from": "LeapMotion.RightHandIndex3", "to": "Standard.RightHandIndex3"},
{ "from": "LeapMotion.RightHandIndex4", "to": "Standard.RightHandIndex4"},
{ "from": "LeapMotion.RightHandMiddle1", "to": "Standard.RightHandMiddle1"},
{ "from": "LeapMotion.RightHandMiddle2", "to": "Standard.RightHandMiddle2"},
{ "from": "LeapMotion.RightHandMiddle3", "to": "Standard.RightHandMiddle3"},
{ "from": "LeapMotion.RightHandMiddle4", "to": "Standard.RightHandMiddle4"},
{ "from": "LeapMotion.RightHandRing1", "to": "Standard.RightHandRing1"},
{ "from": "LeapMotion.RightHandRing2", "to": "Standard.RightHandRing2"},
{ "from": "LeapMotion.RightHandRing3", "to": "Standard.RightHandRing3"},
{ "from": "LeapMotion.RightHandRing4", "to": "Standard.RightHandRing4"},
{ "from": "LeapMotion.RightHandPinky1", "to": "Standard.RightHandPinky1"},
{ "from": "LeapMotion.RightHandPinky2", "to": "Standard.RightHandPinky2"},
{ "from": "LeapMotion.RightHandPinky3", "to": "Standard.RightHandPinky3"},
{ "from": "LeapMotion.RightHandPinky4", "to": "Standard.RightHandPinky4"}
]
}

View file

@ -58,7 +58,48 @@
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
{ "from": "Standard.LeftHand", "to": "Actions.LeftHand" },
{ "from": "Standard.LeftHandThumb1", "to": "Actions.LeftHandThumb1"},
{ "from": "Standard.LeftHandThumb2", "to": "Actions.LeftHandThumb2"},
{ "from": "Standard.LeftHandThumb3", "to": "Actions.LeftHandThumb3"},
{ "from": "Standard.LeftHandThumb4", "to": "Actions.LeftHandThumb4"},
{ "from": "Standard.LeftHandIndex1", "to": "Actions.LeftHandIndex1"},
{ "from": "Standard.LeftHandIndex2", "to": "Actions.LeftHandIndex2"},
{ "from": "Standard.LeftHandIndex3", "to": "Actions.LeftHandIndex3"},
{ "from": "Standard.LeftHandIndex4", "to": "Actions.LeftHandIndex4"},
{ "from": "Standard.LeftHandMiddle1", "to": "Actions.LeftHandMiddle1"},
{ "from": "Standard.LeftHandMiddle2", "to": "Actions.LeftHandMiddle2"},
{ "from": "Standard.LeftHandMiddle3", "to": "Actions.LeftHandMiddle3"},
{ "from": "Standard.LeftHandMiddle4", "to": "Actions.LeftHandMiddle4"},
{ "from": "Standard.LeftHandRing1", "to": "Actions.LeftHandRing1"},
{ "from": "Standard.LeftHandRing2", "to": "Actions.LeftHandRing2"},
{ "from": "Standard.LeftHandRing3", "to": "Actions.LeftHandRing3"},
{ "from": "Standard.LeftHandRing4", "to": "Actions.LeftHandRing4"},
{ "from": "Standard.LeftHandPinky1", "to": "Actions.LeftHandPinky1"},
{ "from": "Standard.LeftHandPinky2", "to": "Actions.LeftHandPinky2"},
{ "from": "Standard.LeftHandPinky3", "to": "Actions.LeftHandPinky3"},
{ "from": "Standard.LeftHandPinky4", "to": "Actions.LeftHandPinky4"},
{ "from": "Standard.RightHand", "to": "Actions.RightHand" },
{ "from": "Standard.RightHandThumb1", "to": "Actions.RightHandThumb1"},
{ "from": "Standard.RightHandThumb2", "to": "Actions.RightHandThumb2"},
{ "from": "Standard.RightHandThumb3", "to": "Actions.RightHandThumb3"},
{ "from": "Standard.RightHandThumb4", "to": "Actions.RightHandThumb4"},
{ "from": "Standard.RightHandIndex1", "to": "Actions.RightHandIndex1"},
{ "from": "Standard.RightHandIndex2", "to": "Actions.RightHandIndex2"},
{ "from": "Standard.RightHandIndex3", "to": "Actions.RightHandIndex3"},
{ "from": "Standard.RightHandIndex4", "to": "Actions.RightHandIndex4"},
{ "from": "Standard.RightHandMiddle1", "to": "Actions.RightHandMiddle1"},
{ "from": "Standard.RightHandMiddle2", "to": "Actions.RightHandMiddle2"},
{ "from": "Standard.RightHandMiddle3", "to": "Actions.RightHandMiddle3"},
{ "from": "Standard.RightHandMiddle4", "to": "Actions.RightHandMiddle4"},
{ "from": "Standard.RightHandRing1", "to": "Actions.RightHandRing1"},
{ "from": "Standard.RightHandRing2", "to": "Actions.RightHandRing2"},
{ "from": "Standard.RightHandRing3", "to": "Actions.RightHandRing3"},
{ "from": "Standard.RightHandRing4", "to": "Actions.RightHandRing4"},
{ "from": "Standard.RightHandPinky1", "to": "Actions.RightHandPinky1"},
{ "from": "Standard.RightHandPinky2", "to": "Actions.RightHandPinky2"},
{ "from": "Standard.RightHandPinky3", "to": "Actions.RightHandPinky3"},
{ "from": "Standard.RightHandPinky4", "to": "Actions.RightHandPinky4"},
{ "from": "Standard.LeftFoot", "to": "Actions.LeftFoot" },
{ "from": "Standard.RightFoot", "to": "Actions.RightFoot" },

View file

@ -17,7 +17,7 @@ PreferencesDialog {
id: root
objectName: "GeneralPreferencesDialog"
title: "General Settings"
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect"]
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"]
property var settings: Settings {
category: root.objectName
property alias x: root.x

View file

@ -32,6 +32,6 @@ StackView {
TabletPreferencesDialog {
id: root
objectName: "TabletGeneralPreferences"
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect", "Vive Pucks Configuration"]
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect", "Vive Pucks Configuration", "Leap Motion"]
}
}

View file

@ -145,7 +145,6 @@
#include "avatar/MyHead.h"
#include "CrashHandler.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Leapmotion.h"
#include "DiscoverabilityManager.h"
#include "GLCanvas.h"
#include "InterfaceDynamicFactory.h"
@ -1886,8 +1885,6 @@ Application::~Application() {
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
Leapmotion::destroy();
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->shutdown();
}
@ -4055,8 +4052,6 @@ void Application::init() {
qCDebug(interfaceapp) << "Loaded settings";
Leapmotion::init();
// fire off an immediate domain-server check in now that settings are loaded
DependencyManager::get<NodeList>()->sendDomainServerCheckIn();
@ -4520,7 +4515,6 @@ void Application::update(float deltaTime) {
{
PerformanceTimer perfTimer("devices");
DeviceTracker::updateAll();
FaceTracker* tracker = getSelectedFaceTracker();
if (tracker && Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking) != tracker->isMuted()) {
@ -4589,8 +4583,6 @@ void Application::update(float deltaTime) {
keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData);
}
_controllerScriptingInterface->updateInputControllers();
// Transfer the user inputs to the driveKeys
// FIXME can we drop drive keys and just have the avatar read the action states directly?
myAvatar->clearDriveKeys();
@ -4615,6 +4607,31 @@ void Application::update(float deltaTime) {
auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix));
// If have previously done finger poses or there are new valid finger poses, update finger pose values. This so that if
// fingers are not being controlled, finger joints are not updated in MySkeletonModel.
// Assumption: Finger poses are either all present and valid or not present at all; thus can test just one joint.
MyAvatar::FingerPosesMap leftHandFingerPoses;
if (myAvatar->getLeftHandFingerControllerPosesInSensorFrame().size() > 0
|| userInputMapper->getPoseState(controller::Action::LEFT_HAND_THUMB1).isValid()) {
for (int i = (int)controller::Action::LEFT_HAND_THUMB1; i <= (int)controller::Action::LEFT_HAND_PINKY4; i++) {
leftHandFingerPoses[i] = {
userInputMapper->getPoseState((controller::Action)i).transform(avatarToSensorMatrix),
userInputMapper->getActionName((controller::Action)i)
};
}
}
MyAvatar::FingerPosesMap rightHandFingerPoses;
if (myAvatar->getRightHandFingerControllerPosesInSensorFrame().size() > 0
|| userInputMapper->getPoseState(controller::Action::RIGHT_HAND_THUMB1).isValid()) {
for (int i = (int)controller::Action::RIGHT_HAND_THUMB1; i <= (int)controller::Action::RIGHT_HAND_PINKY4; i++) {
rightHandFingerPoses[i] = {
userInputMapper->getPoseState((controller::Action)i).transform(avatarToSensorMatrix),
userInputMapper->getActionName((controller::Action)i)
};
}
}
myAvatar->setFingerControllerPosesInSensorFrame(leftHandFingerPoses, rightHandFingerPoses);
controller::Pose leftFootPose = userInputMapper->getPoseState(controller::Action::LEFT_FOOT);
controller::Pose rightFootPose = userInputMapper->getPoseState(controller::Action::RIGHT_FOOT);
myAvatar->setFootControllerPosesInSensorFrame(leftFootPose.transform(avatarToSensorMatrix), rightFootPose.transform(avatarToSensorMatrix));

View file

@ -566,9 +566,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false,
avatar.get(), SLOT(setEnableDebugDrawHandControllers(bool)));
MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion");
addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false);
// Developer > Entities >>>
MenuWrapper* entitiesOptionsMenu = developerMenu->addMenu("Entities");

View file

@ -114,7 +114,6 @@ namespace MenuOption {
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IndependentMode = "Independent Mode";
const QString ActionMotorControl = "Enable Default Motor Control";
const QString LeapMotionOnHMD = "Leap Motion on HMD";
const QString LoadScript = "Open and Run Script File...";
const QString LoadScriptURL = "Open and Run Script from URL...";
const QString LodTools = "LOD Tools";

View file

@ -1455,6 +1455,19 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const {
return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right) {
_leftHandFingerPosesInSensorFramceCache.set(left);
_rightHandFingerPosesInSensorFramceCache.set(right);
}
MyAvatar::FingerPosesMap MyAvatar::getLeftHandFingerControllerPosesInSensorFrame() const {
return _leftHandFingerPosesInSensorFramceCache.get();
}
MyAvatar::FingerPosesMap MyAvatar::getRightHandFingerControllerPosesInSensorFrame() const {
return _rightHandFingerPosesInSensorFramceCache.get();
}
void MyAvatar::setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) {
_leftFootControllerPoseInSensorFrameCache.set(left);
_rightFootControllerPoseInSensorFrameCache.set(right);

View file

@ -474,6 +474,11 @@ public:
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
typedef std::map<int, std::pair<controller::Pose, QString>> FingerPosesMap;
void setFingerControllerPosesInSensorFrame(const FingerPosesMap& left, const FingerPosesMap& right);
FingerPosesMap getLeftHandFingerControllerPosesInSensorFrame() const;
FingerPosesMap getRightHandFingerControllerPosesInSensorFrame() const;
void setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
controller::Pose getLeftFootControllerPoseInSensorFrame() const;
controller::Pose getRightFootControllerPoseInSensorFrame() const;
@ -788,13 +793,15 @@ private:
// These are stored in SENSOR frame
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _leftFootControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightFootControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _hipsControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _spine2ControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _headControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _leftArmControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightArmControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<FingerPosesMap> _leftHandFingerPosesInSensorFramceCache { };
ThreadSafeValueCache<FingerPosesMap> _rightHandFingerPosesInSensorFramceCache { };
ThreadSafeValueCache<controller::Pose> _leftFootControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightFootControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _hipsControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _spine2ControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _headControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _leftArmControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightArmControllerPoseInSensorFrameCache { controller::Pose() };
bool _hmdLeanRecenterEnabled = true;
AnimPose _prePhysicsRoomPose;

View file

@ -174,5 +174,50 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig.updateFromEyeParameters(eyeParams);
updateFingers(myAvatar->getLeftHandFingerControllerPosesInSensorFrame());
updateFingers(myAvatar->getRightHandFingerControllerPosesInSensorFrame());
}
void MySkeletonModel::updateFingers(const MyAvatar::FingerPosesMap& fingerPoses) {
// Assumes that finger poses are kept in order in the poses map.
if (fingerPoses.size() == 0) {
return;
}
auto posesMapItr = fingerPoses.begin();
bool isLeftHand = posesMapItr->first < (int)controller::Action::RIGHT_HAND_THUMB1;
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
auto handPose = isLeftHand
? myAvatar->getLeftHandControllerPoseInSensorFrame()
: myAvatar->getRightHandControllerPoseInSensorFrame();
auto handJointRotation = handPose.getRotation();
bool isHandValid = handPose.isValid();
bool isFingerValid = false;
glm::quat previousJointRotation;
while (posesMapItr != fingerPoses.end()) {
auto jointName = posesMapItr->second.second;
if (isHandValid && jointName.right(1) == "1") {
isFingerValid = posesMapItr->second.first.isValid();
previousJointRotation = handJointRotation;
}
if (isHandValid && isFingerValid) {
auto thisJointRotation = posesMapItr->second.first.getRotation();
const float CONTROLLER_PRIORITY = 2.0f;
_rig.setJointRotation(_rig.indexOfJoint(jointName), true, glm::inverse(previousJointRotation) * thisJointRotation,
CONTROLLER_PRIORITY);
previousJointRotation = thisJointRotation;
} else {
_rig.clearJointAnimationPriority(_rig.indexOfJoint(jointName));
}
posesMapItr++;
}
}

View file

@ -10,6 +10,7 @@
#define hifi_MySkeletonModel_h
#include <avatars-renderer/SkeletonModel.h>
#include "MyAvatar.h"
/// A skeleton loaded from a model.
class MySkeletonModel : public SkeletonModel {
@ -21,6 +22,9 @@ private:
public:
MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr);
void updateRig(float deltaTime, glm::mat4 parentTransform) override;
private:
void updateFingers(const MyAvatar::FingerPosesMap& fingerPoses);
};
#endif // hifi_MySkeletonModel_h

View file

@ -1,246 +0,0 @@
//
// Created by Sam Cake on 6/2/2014
// Copyright 2014 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
//
#include "Leapmotion.h"
#include <NumericalConstants.h>
#include "Menu.h"
const int PALMROOT_NUM_JOINTS = 3;
const int FINGER_NUM_JOINTS = 4;
const int HAND_NUM_JOINTS = FINGER_NUM_JOINTS*5+PALMROOT_NUM_JOINTS;
const DeviceTracker::Name Leapmotion::NAME = "Leapmotion";
// find the index of a joint from
// the side: true = right
// the finger & the bone:
// finger in [0..4] : bone in [0..3] a finger phalange
// [-1] up the hand branch : bone in [0..2] <=> [ hand, forearm, arm]
MotionTracker::Index evalJointIndex(bool isRightSide, int finger, int bone) {
MotionTracker::Index offset = 1 // start after root
+ (int(isRightSide) * HAND_NUM_JOINTS) // then offset for side
+ PALMROOT_NUM_JOINTS; // then add the arm/forearm/hand chain
if (finger >= 0) {
// from there go down in the correct finger and bone
return offset + (finger * FINGER_NUM_JOINTS) + bone;
} else {
// or go back up for the correct root bone
return offset - 1 - bone;
}
}
// static
void Leapmotion::init() {
DeviceTracker* device = DeviceTracker::getDevice(NAME);
if (!device) {
// create a new Leapmotion and register it
Leapmotion* leap = new Leapmotion();
DeviceTracker::registerDevice(NAME, leap);
}
}
// static
void Leapmotion::destroy() {
DeviceTracker::destroyDevice(NAME);
}
// static
Leapmotion* Leapmotion::getInstance() {
DeviceTracker* device = DeviceTracker::getDevice(NAME);
if (!device) {
// create a new Leapmotion and register it
device = new Leapmotion();
DeviceTracker::registerDevice(NAME, device);
}
return dynamic_cast< Leapmotion* > (device);
}
Leapmotion::Leapmotion() :
MotionTracker(),
_active(false)
{
// Create the Leapmotion joint hierarchy
std::vector< Semantic > sides;
sides.push_back("joint_L_");
sides.push_back("joint_R_");
std::vector< Semantic > rootBones;
rootBones.push_back("elbow");
rootBones.push_back("wrist");
rootBones.push_back("hand");
std::vector< Semantic > fingers;
fingers.push_back("thumb");
fingers.push_back("index");
fingers.push_back("middle");
fingers.push_back("ring");
fingers.push_back("pinky");
std::vector< Semantic > fingerBones;
fingerBones.push_back("1");
fingerBones.push_back("2");
fingerBones.push_back("3");
fingerBones.push_back("4");
std::vector< Index > palms;
for (unsigned int s = 0; s < sides.size(); s++) {
Index rootJoint = 0;
for (unsigned int rb = 0; rb < rootBones.size(); rb++) {
rootJoint = addJoint(sides[s] + rootBones[rb], rootJoint);
}
// capture the hand index for debug
palms.push_back(rootJoint);
for (unsigned int f = 0; f < fingers.size(); f++) {
for (unsigned int b = 0; b < fingerBones.size(); b++) {
rootJoint = addJoint(sides[s] + fingers[f] + fingerBones[b], rootJoint);
}
}
}
#ifdef HAVE_LEAPMOTION
if (Menu::getInstance()->isOptionChecked(MenuOption::LeapMotionOnHMD)) {
_controller.setPolicyFlags(Leap::Controller::POLICY_OPTIMIZE_HMD);
}
#endif
}
Leapmotion::~Leapmotion() {
}
#ifdef HAVE_LEAPMOTION
glm::quat quatFromLeapBase(float sideSign, const Leap::Matrix& basis) {
// fix the handness to right and always...
glm::vec3 xAxis = glm::normalize(sideSign * glm::vec3(basis.xBasis.x, basis.xBasis.y, basis.xBasis.z));
glm::vec3 yAxis = glm::normalize(glm::vec3(basis.yBasis.x, basis.yBasis.y, basis.yBasis.z));
glm::vec3 zAxis = glm::normalize(glm::vec3(basis.zBasis.x, basis.zBasis.y, basis.zBasis.z));
xAxis = glm::normalize(glm::cross(yAxis, zAxis));
glm::quat orientation = (glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis)));
return orientation;
}
glm::vec3 vec3FromLeapVector(const Leap::Vector& vec) {
return glm::vec3(vec.x * METERS_PER_MILLIMETER, vec.y * METERS_PER_MILLIMETER, vec.z * METERS_PER_MILLIMETER);
}
#endif
void Leapmotion::update() {
#ifdef HAVE_LEAPMOTION
bool wasActive = _active;
_active = _controller.isConnected();
if (_active || wasActive) {
// Go through all the joints and increment their counter since last update.
// Increment all counters once after controller first becomes inactive so that each joint reports itself as inactive.
// TODO C++11 for (auto jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) {
for (JointTracker::Vector::iterator jointIt = _jointsArray.begin(); jointIt != _jointsArray.end(); jointIt++) {
(*jointIt).tickNewFrame();
}
}
if (!_active) {
return;
}
// Get the most recent frame and report some basic information
const Leap::Frame frame = _controller.frame();
static int64_t lastFrameID = -1;
int64_t newFrameID = frame.id();
// If too soon then exit
if (lastFrameID >= newFrameID)
return;
glm::vec3 delta(0.0f);
glm::quat handOri;
if (!frame.hands().isEmpty()) {
for (int handNum = 0; handNum < frame.hands().count(); handNum++) {
const Leap::Hand hand = frame.hands()[handNum];
int side = (hand.isRight() ? 1 : -1);
JointTracker* parentJointTracker = _jointsArray.data();
int rootBranchIndex = -1;
Leap::Arm arm = hand.arm();
if (arm.isValid()) {
glm::quat ori = quatFromLeapBase(float(side), arm.basis());
glm::vec3 pos = vec3FromLeapVector(arm.elbowPosition());
JointTracker* elbow = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 2)); // 2 is the index of the elbow joint
elbow->editAbsFrame().setTranslation(pos);
elbow->editAbsFrame().setRotation(ori);
elbow->updateLocFromAbsTransform(parentJointTracker);
elbow->activeFrame();
parentJointTracker = elbow;
pos = vec3FromLeapVector(arm.wristPosition());
JointTracker* wrist = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 1)); // 1 is the index of the wrist joint
wrist->editAbsFrame().setTranslation(pos);
wrist->editAbsFrame().setRotation(ori);
wrist->updateLocFromAbsTransform(parentJointTracker);
wrist->activeFrame();
parentJointTracker = wrist;
}
JointTracker* palmJoint = NULL;
{
glm::vec3 pos = vec3FromLeapVector(hand.palmPosition());
glm::quat ori = quatFromLeapBase(float(side), hand.basis());
palmJoint = editJointTracker(evalJointIndex((side > 0), rootBranchIndex, 0)); // 0 is the index of the palm joint
palmJoint->editAbsFrame().setTranslation(pos);
palmJoint->editAbsFrame().setRotation(ori);
palmJoint->updateLocFromAbsTransform(parentJointTracker);
palmJoint->activeFrame();
}
// Check if the hand has any fingers
const Leap::FingerList fingers = hand.fingers();
if (!fingers.isEmpty()) {
// For every fingers in the list
for (int i = 0; i < fingers.count(); ++i) {
// Reset the parent joint to the palmJoint for every finger traversal
parentJointTracker = palmJoint;
// surprisingly, Leap::Finger::Type start at 0 for thumb a until 4 for the pinky
Index fingerIndex = evalJointIndex((side > 0), int(fingers[i].type()), 0);
// let's update the finger's joints
for (int b = 0; b < FINGER_NUM_JOINTS; b++) {
Leap::Bone::Type type = Leap::Bone::Type(b + Leap::Bone::TYPE_METACARPAL);
Leap::Bone bone = fingers[i].bone(type);
JointTracker* ljointTracker = editJointTracker(fingerIndex + b);
if (bone.isValid()) {
Leap::Vector bp = bone.nextJoint();
ljointTracker->editAbsFrame().setTranslation(vec3FromLeapVector(bp));
ljointTracker->editAbsFrame().setRotation(quatFromLeapBase(float(side), bone.basis()));
ljointTracker->updateLocFromAbsTransform(parentJointTracker);
ljointTracker->activeFrame();
}
parentJointTracker = ljointTracker;
}
}
}
}
}
lastFrameID = newFrameID;
#endif
}

View file

@ -1,48 +0,0 @@
//
// Created by Sam Cake on 6/2/2014
// Copyright 2014 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
//
#ifndef hifi_Leapmotion_h
#define hifi_Leapmotion_h
#include <QDateTime>
#include <trackers/MotionTracker.h>
#ifdef HAVE_LEAPMOTION
#include <Leap.h>
#endif
/// Handles interaction with the Leapmotion skeleton tracking suit.
class Leapmotion : public MotionTracker {
public:
static const Name NAME;
static void init();
static void destroy();
/// Leapmotion MotionTracker factory
static Leapmotion* getInstance();
bool isActive() const { return _active; }
virtual void update() override;
protected:
Leapmotion();
virtual ~Leapmotion();
private:
#ifdef HAVE_LEAPMOTION
Leap::Listener _listener;
Leap::Controller _controller;
#endif
bool _active;
};
#endif // hifi_Leapmotion_h

View file

@ -17,7 +17,6 @@
#include <plugins/PluginManager.h>
#include "Application.h"
#include <trackers/MotionTracker.h>
void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) {
if (event->type() == HFActionEvent::startType()) {
@ -97,86 +96,6 @@ QVariant ControllerScriptingInterface::getRecommendedOverlayRect() const {
return qRectToVariant(rect);
}
controller::InputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) {
// This is where we retrieve the Device Tracker category and then the sub tracker within it
auto icIt = _inputControllers.find(0);
if (icIt != _inputControllers.end()) {
return (*icIt).second.get();
}
// Look for device
DeviceTracker::ID deviceID = DeviceTracker::getDeviceID(deviceName.toStdString());
if (deviceID < 0) {
deviceID = 0;
}
// TODO in this current implementation, we just pick the device assuming there is one (normally the Leapmotion)
// in the near future we need to change that to a real mapping between the devices and the deviceName
// ALso we need to expand the spec so we can fall back on the "default" controller per categories
if (deviceID >= 0) {
// TODO here again the assumption it's the LeapMotion and so it's a MOtionTracker, this would need to be changed to support different types of devices
MotionTracker* motionTracker = dynamic_cast< MotionTracker* > (DeviceTracker::getDevice(deviceID));
if (motionTracker) {
MotionTracker::Index trackerID = motionTracker->findJointIndex(tracker.toStdString());
if (trackerID >= 0) {
controller::InputController::Pointer inputController = std::make_shared<InputController>(deviceID, trackerID, this);
controller::InputController::Key key = inputController->getKey();
_inputControllers.insert(InputControllerMap::value_type(key, inputController));
return inputController.get();
}
}
}
return nullptr;
}
void ControllerScriptingInterface::releaseInputController(controller::InputController* input) {
_inputControllers.erase(input->getKey());
}
void ControllerScriptingInterface::updateInputControllers() {
for (auto it = _inputControllers.begin(); it != _inputControllers.end(); it++) {
(*it).second->update();
}
}
InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) :
_deviceTrackerId(deviceTrackerId),
_subTrackerId(subTrackerId),
_isActive(false)
{
}
void InputController::update() {
_isActive = false;
// TODO for now the InputController is only supporting a JointTracker from a MotionTracker
MotionTracker* motionTracker = dynamic_cast< MotionTracker*> (DeviceTracker::getDevice(_deviceTrackerId));
if (motionTracker) {
if ((int)_subTrackerId < motionTracker->numJointTrackers()) {
const MotionTracker::JointTracker* joint = motionTracker->getJointTracker(_subTrackerId);
if (joint->isActive()) {
joint->getAbsFrame().getTranslation(_eventCache.absTranslation);
joint->getAbsFrame().getRotation(_eventCache.absRotation);
joint->getLocFrame().getTranslation(_eventCache.locTranslation);
joint->getLocFrame().getRotation(_eventCache.locRotation);
_isActive = true;
//emit spatialEvent(_eventCache);
}
}
}
}
const unsigned int INPUTCONTROLLER_KEY_DEVICE_OFFSET = 16;
const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16;
InputController::Key InputController::getKey() const {
return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId);
}
void ControllerScriptingInterface::emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); }
void ControllerScriptingInterface::emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); }

View file

@ -25,38 +25,6 @@
#include <WheelEvent.h>
class ScriptEngine;
class PalmData;
class InputController : public controller::InputController {
Q_OBJECT
public:
InputController(int deviceTrackerId, int subTrackerId, QObject* parent = NULL);
virtual void update() override;
virtual Key getKey() const override;
public slots:
virtual bool isActive() const override { return _isActive; }
virtual glm::vec3 getAbsTranslation() const override { return _eventCache.absTranslation; }
virtual glm::quat getAbsRotation() const override { return _eventCache.absRotation; }
virtual glm::vec3 getLocTranslation() const override { return _eventCache.locTranslation; }
virtual glm::quat getLocRotation() const override { return _eventCache.locRotation; }
private:
int _deviceTrackerId;
uint _subTrackerId;
// cache for the spatial
SpatialEvent _eventCache;
bool _isActive;
signals:
};
/// handles scripting of input controller commands from JS
class ControllerScriptingInterface : public controller::ScriptingInterface {
Q_OBJECT
@ -86,8 +54,6 @@ public:
bool isJoystickCaptured(int joystickIndex) const;
bool areEntityClicksCaptured() const;
void updateInputControllers();
public slots:
virtual void captureKeyEvents(const KeyEvent& event);
@ -102,10 +68,6 @@ public slots:
virtual glm::vec2 getViewportDimensions() const;
virtual QVariant getRecommendedOverlayRect() const;
/// Factory to create an InputController
virtual controller::InputController* createInputController(const QString& deviceName, const QString& tracker);
virtual void releaseInputController(controller::InputController* input);
signals:
void keyPressEvent(const KeyEvent& event);
void keyReleaseEvent(const KeyEvent& event);
@ -135,8 +97,6 @@ private:
bool _captureEntityClicks;
using InputKey = controller::InputController::Key;
using InputControllerMap = std::map<InputKey, controller::InputController::Pointer>;
InputControllerMap _inputControllers;
};
const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip

View file

@ -59,6 +59,48 @@ namespace controller {
makePosePair(Action::SPINE2, "Spine2"),
makePosePair(Action::HEAD, "Head"),
makePosePair(Action::LEFT_HAND_THUMB1, "LeftHandThumb1"),
makePosePair(Action::LEFT_HAND_THUMB2, "LeftHandThumb2"),
makePosePair(Action::LEFT_HAND_THUMB3, "LeftHandThumb3"),
makePosePair(Action::LEFT_HAND_THUMB4, "LeftHandThumb4"),
makePosePair(Action::LEFT_HAND_INDEX1, "LeftHandIndex1"),
makePosePair(Action::LEFT_HAND_INDEX2, "LeftHandIndex2"),
makePosePair(Action::LEFT_HAND_INDEX3, "LeftHandIndex3"),
makePosePair(Action::LEFT_HAND_INDEX4, "LeftHandIndex4"),
makePosePair(Action::LEFT_HAND_MIDDLE1, "LeftHandMiddle1"),
makePosePair(Action::LEFT_HAND_MIDDLE2, "LeftHandMiddle2"),
makePosePair(Action::LEFT_HAND_MIDDLE3, "LeftHandMiddle3"),
makePosePair(Action::LEFT_HAND_MIDDLE4, "LeftHandMiddle4"),
makePosePair(Action::LEFT_HAND_RING1, "LeftHandRing1"),
makePosePair(Action::LEFT_HAND_RING2, "LeftHandRing2"),
makePosePair(Action::LEFT_HAND_RING3, "LeftHandRing3"),
makePosePair(Action::LEFT_HAND_RING4, "LeftHandRing4"),
makePosePair(Action::LEFT_HAND_PINKY1, "LeftHandPinky1"),
makePosePair(Action::LEFT_HAND_PINKY2, "LeftHandPinky2"),
makePosePair(Action::LEFT_HAND_PINKY3, "LeftHandPinky3"),
makePosePair(Action::LEFT_HAND_PINKY4, "LeftHandPinky4"),
makePosePair(Action::RIGHT_HAND_THUMB1, "RightHandThumb1"),
makePosePair(Action::RIGHT_HAND_THUMB2, "RightHandThumb2"),
makePosePair(Action::RIGHT_HAND_THUMB3, "RightHandThumb3"),
makePosePair(Action::RIGHT_HAND_THUMB4, "RightHandThumb4"),
makePosePair(Action::RIGHT_HAND_INDEX1, "RightHandIndex1"),
makePosePair(Action::RIGHT_HAND_INDEX2, "RightHandIndex2"),
makePosePair(Action::RIGHT_HAND_INDEX3, "RightHandIndex3"),
makePosePair(Action::RIGHT_HAND_INDEX4, "RightHandIndex4"),
makePosePair(Action::RIGHT_HAND_MIDDLE1, "RightHandMiddle1"),
makePosePair(Action::RIGHT_HAND_MIDDLE2, "RightHandMiddle2"),
makePosePair(Action::RIGHT_HAND_MIDDLE3, "RightHandMiddle3"),
makePosePair(Action::RIGHT_HAND_MIDDLE4, "RightHandMiddle4"),
makePosePair(Action::RIGHT_HAND_RING1, "RightHandRing1"),
makePosePair(Action::RIGHT_HAND_RING2, "RightHandRing2"),
makePosePair(Action::RIGHT_HAND_RING3, "RightHandRing3"),
makePosePair(Action::RIGHT_HAND_RING4, "RightHandRing4"),
makePosePair(Action::RIGHT_HAND_PINKY1, "RightHandPinky1"),
makePosePair(Action::RIGHT_HAND_PINKY2, "RightHandPinky2"),
makePosePair(Action::RIGHT_HAND_PINKY3, "RightHandPinky3"),
makePosePair(Action::RIGHT_HAND_PINKY4, "RightHandPinky4"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"),

View file

@ -104,6 +104,47 @@ enum class Action {
LEFT_ARM,
RIGHT_ARM,
LEFT_HAND_THUMB1,
LEFT_HAND_THUMB2,
LEFT_HAND_THUMB3,
LEFT_HAND_THUMB4,
LEFT_HAND_INDEX1,
LEFT_HAND_INDEX2,
LEFT_HAND_INDEX3,
LEFT_HAND_INDEX4,
LEFT_HAND_MIDDLE1,
LEFT_HAND_MIDDLE2,
LEFT_HAND_MIDDLE3,
LEFT_HAND_MIDDLE4,
LEFT_HAND_RING1,
LEFT_HAND_RING2,
LEFT_HAND_RING3,
LEFT_HAND_RING4,
LEFT_HAND_PINKY1,
LEFT_HAND_PINKY2,
LEFT_HAND_PINKY3,
LEFT_HAND_PINKY4,
RIGHT_HAND_THUMB1,
RIGHT_HAND_THUMB2,
RIGHT_HAND_THUMB3,
RIGHT_HAND_THUMB4,
RIGHT_HAND_INDEX1,
RIGHT_HAND_INDEX2,
RIGHT_HAND_INDEX3,
RIGHT_HAND_INDEX4,
RIGHT_HAND_MIDDLE1,
RIGHT_HAND_MIDDLE2,
RIGHT_HAND_MIDDLE3,
RIGHT_HAND_MIDDLE4,
RIGHT_HAND_RING1,
RIGHT_HAND_RING2,
RIGHT_HAND_RING3,
RIGHT_HAND_RING4,
RIGHT_HAND_PINKY1,
RIGHT_HAND_PINKY2,
RIGHT_HAND_PINKY3,
RIGHT_HAND_PINKY4,
NUM_ACTIONS,
};

View file

@ -101,7 +101,47 @@ Input::NamedVector StandardController::getAvailableInputs() const {
// Poses
makePair(LEFT_HAND, "LeftHand"),
makePair(LEFT_HAND_THUMB1, "LeftHandThumb1"),
makePair(LEFT_HAND_THUMB2, "LeftHandThumb2"),
makePair(LEFT_HAND_THUMB3, "LeftHandThumb3"),
makePair(LEFT_HAND_THUMB4, "LeftHandThumb4"),
makePair(LEFT_HAND_INDEX1, "LeftHandIndex1"),
makePair(LEFT_HAND_INDEX2, "LeftHandIndex2"),
makePair(LEFT_HAND_INDEX3, "LeftHandIndex3"),
makePair(LEFT_HAND_INDEX4, "LeftHandIndex4"),
makePair(LEFT_HAND_MIDDLE1, "LeftHandMiddle1"),
makePair(LEFT_HAND_MIDDLE2, "LeftHandMiddle2"),
makePair(LEFT_HAND_MIDDLE3, "LeftHandMiddle3"),
makePair(LEFT_HAND_MIDDLE4, "LeftHandMiddle4"),
makePair(LEFT_HAND_RING1, "LeftHandRing1"),
makePair(LEFT_HAND_RING2, "LeftHandRing2"),
makePair(LEFT_HAND_RING3, "LeftHandRing3"),
makePair(LEFT_HAND_RING4, "LeftHandRing4"),
makePair(LEFT_HAND_PINKY1, "LeftHandPinky1"),
makePair(LEFT_HAND_PINKY2, "LeftHandPinky2"),
makePair(LEFT_HAND_PINKY3, "LeftHandPinky3"),
makePair(LEFT_HAND_PINKY4, "LeftHandPinky4"),
makePair(RIGHT_HAND, "RightHand"),
makePair(RIGHT_HAND_THUMB1, "RightHandThumb1"),
makePair(RIGHT_HAND_THUMB2, "RightHandThumb2"),
makePair(RIGHT_HAND_THUMB3, "RightHandThumb3"),
makePair(RIGHT_HAND_THUMB4, "RightHandThumb4"),
makePair(RIGHT_HAND_INDEX1, "RightHandIndex1"),
makePair(RIGHT_HAND_INDEX2, "RightHandIndex2"),
makePair(RIGHT_HAND_INDEX3, "RightHandIndex3"),
makePair(RIGHT_HAND_INDEX4, "RightHandIndex4"),
makePair(RIGHT_HAND_MIDDLE1, "RightHandMiddle1"),
makePair(RIGHT_HAND_MIDDLE2, "RightHandMiddle2"),
makePair(RIGHT_HAND_MIDDLE3, "RightHandMiddle3"),
makePair(RIGHT_HAND_MIDDLE4, "RightHandMiddle4"),
makePair(RIGHT_HAND_RING1, "RightHandRing1"),
makePair(RIGHT_HAND_RING2, "RightHandRing2"),
makePair(RIGHT_HAND_RING3, "RightHandRing3"),
makePair(RIGHT_HAND_RING4, "RightHandRing4"),
makePair(RIGHT_HAND_PINKY1, "RightHandPinky1"),
makePair(RIGHT_HAND_PINKY2, "RightHandPinky2"),
makePair(RIGHT_HAND_PINKY3, "RightHandPinky3"),
makePair(RIGHT_HAND_PINKY4, "RightHandPinky4"),
makePair(LEFT_FOOT, "LeftFoot"),
makePair(RIGHT_FOOT, "RightFoot"),
makePair(RIGHT_ARM, "RightArm"),

View file

@ -1,93 +0,0 @@
//
// Created by Sam Cake on 6/20/14.
// Copyright 2014 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
//
#include "DeviceTracker.h"
DeviceTracker::SingletonData::~SingletonData() {
// Destroy all the device registered
//TODO C++11 for (auto device = _devicesVector.begin(); device != _devicesVector.end(); device++) {
for (Vector::iterator device = _devicesVector.begin(); device != _devicesVector.end(); device++) {
delete (*device);
}
}
int DeviceTracker::getNumDevices() {
return (int)Singleton::get()->_devicesMap.size();
}
DeviceTracker::ID DeviceTracker::getDeviceID(const Name& name) {
//TODO C++11 auto deviceIt = Singleton::get()->_devicesMap.find(name);
Map::iterator deviceIt = Singleton::get()->_devicesMap.find(name);
if (deviceIt != Singleton::get()->_devicesMap.end()) {
return (*deviceIt).second;
} else {
return INVALID_DEVICE;
}
}
DeviceTracker* DeviceTracker::getDevice(const Name& name) {
return getDevice(getDeviceID(name));
}
DeviceTracker* DeviceTracker::getDevice(DeviceTracker::ID deviceID) {
if ((deviceID >= 0) && (deviceID < (int)(Singleton::get()->_devicesVector.size()))) {
return Singleton::get()->_devicesVector[ deviceID ];
} else {
return NULL;
}
}
DeviceTracker::ID DeviceTracker::registerDevice(const Name& name, DeviceTracker* device) {
// Check that the device exists, if not exit
if (!device) {
return INVALID_DEVICE;
}
// Look if the name is not already used
ID deviceID = getDeviceID(name);
if (deviceID >= 0) {
return INVALID_DEVICE_NAME;
}
// Good to register the device
deviceID = (ID)Singleton::get()->_devicesVector.size();
Singleton::get()->_devicesMap.insert(Map::value_type(name, deviceID));
Singleton::get()->_devicesVector.push_back(device);
device->assignIDAndName(deviceID, name);
return deviceID;
}
void DeviceTracker::destroyDevice(const Name& name) {
DeviceTracker::ID deviceID = getDeviceID(name);
if (deviceID != INVALID_DEVICE) {
delete Singleton::get()->_devicesVector[getDeviceID(name)];
Singleton::get()->_devicesVector[getDeviceID(name)] = nullptr;
}
}
void DeviceTracker::updateAll() {
//TODO C++11 for (auto deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
for (Vector::iterator deviceIt = Singleton::get()->_devicesVector.begin(); deviceIt != Singleton::get()->_devicesVector.end(); deviceIt++) {
if ((*deviceIt))
(*deviceIt)->update();
}
}
// Core features of the Device Tracker
DeviceTracker::DeviceTracker() :
_ID(INVALID_DEVICE),
_name("Unkown")
{
}
DeviceTracker::~DeviceTracker() {
}
void DeviceTracker::update() {
}

View file

@ -1,115 +0,0 @@
//
// Created by Sam Cake on 6/20/14.
// Copyright 2014 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
//
#ifndef hifi_DeviceTracker_h
#define hifi_DeviceTracker_h
#include <string>
#include <vector>
#include <map>
// Singleton template class
template < typename T >
class TemplateSingleton {
public:
static T* get() {
if ( !_singleton._one ) {
_singleton._one = new T();
}
return _singleton._one;
}
TemplateSingleton() :
_one(0)
{
}
~TemplateSingleton() {
if ( _one ) {
delete _one;
_one = 0;
}
}
private:
static TemplateSingleton< T > _singleton;
T* _one;
};
template <typename T>
TemplateSingleton<T> TemplateSingleton<T>::_singleton;
/// Base class for device trackers.
class DeviceTracker {
public:
// THe ID and Name types used to manage the pool of devices
typedef std::string Name;
typedef int ID;
static const ID INVALID_DEVICE = -1;
static const ID INVALID_DEVICE_NAME = -2;
// Singleton interface to register and query the devices currently connected
static int getNumDevices();
static ID getDeviceID(const Name& name);
static DeviceTracker* getDevice(ID deviceID);
static DeviceTracker* getDevice(const Name& name);
/// Update all the devices calling for their update() function
/// This should be called every frame by the main loop to update all the devices that pull their state
static void updateAll();
/// Register a device tracker to the factory
/// Right after creating a new DeviceTracker, it should be registered
/// This is why, it's recommended to use a factory static call in the specialized class
/// to create a new input device
///
/// \param name The Name under wich registering the device
/// \param parent The DeviceTracker
///
/// \return The Index of the newly registered device.
/// Valid if everything went well.
/// INVALID_DEVICE if the device is not valid (NULL)
/// INVALID_DEVICE_NAME if the name is already taken
static ID registerDevice(const Name& name, DeviceTracker* tracker);
static void destroyDevice(const Name& name);
// DeviceTracker interface
virtual void update();
/// Get the ID assigned to the Device when registered after creation, or INVALID_DEVICE if it hasn't been registered which should not happen.
ID getID() const { return _ID; }
/// Get the name assigned to the Device when registered after creation, or "Unknown" if it hasn't been registered which should not happen.
const Name& getName() const { return _name; }
typedef std::map< Name, ID > Map;
static const Map& getDevices() { return Singleton::get()->_devicesMap; }
protected:
DeviceTracker();
virtual ~DeviceTracker();
private:
ID _ID;
Name _name;
// this call is used by the singleton when the device tracker is currently beeing registered and beeing assigned an ID
void assignIDAndName( const ID id, const Name& name ) { _ID = id; _name = name; }
typedef std::vector< DeviceTracker* > Vector;
struct SingletonData {
Map _devicesMap;
Vector _devicesVector;
~SingletonData();
};
typedef TemplateSingleton< SingletonData > Singleton;
};
#endif // hifi_DeviceTracker_h

View file

@ -1,186 +0,0 @@
//
// Created by Sam Cake on 6/20/14.
// Copyright 2014 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
//
#include "MotionTracker.h"
// glm::mult(mat43, mat43) just the composition of the 2 matrices assuming they are in fact mat44 with the last raw = { 0, 0, 0, 1 }
namespace glm {
mat4x3 mult(const mat4& lhs, const mat4x3& rhs) {
vec3 lrx(lhs[0].x, lhs[1].x, lhs[2].x);
vec3 lry(lhs[0].y, lhs[1].y, lhs[2].y);
vec3 lrz(lhs[0].z, lhs[1].z, lhs[2].z);
return mat4x3(
dot(lrx, rhs[0]),
dot(lry, rhs[0]),
dot(lrz, rhs[0]),
dot(lrx, rhs[1]),
dot(lry, rhs[1]),
dot(lrz, rhs[1]),
dot(lrx, rhs[2]),
dot(lry, rhs[2]),
dot(lrz, rhs[2]),
dot(lrx, rhs[3]) + lhs[3].x,
dot(lry, rhs[3]) + lhs[3].y,
dot(lrz, rhs[3]) + lhs[3].z
);
}
mat4x3 mult(const mat4x3& lhs, const mat4x3& rhs) {
vec3 lrx(lhs[0].x, lhs[1].x, lhs[2].x);
vec3 lry(lhs[0].y, lhs[1].y, lhs[2].y);
vec3 lrz(lhs[0].z, lhs[1].z, lhs[2].z);
return mat4x3(
dot(lrx, rhs[0]),
dot(lry, rhs[0]),
dot(lrz, rhs[0]),
dot(lrx, rhs[1]),
dot(lry, rhs[1]),
dot(lrz, rhs[1]),
dot(lrx, rhs[2]),
dot(lry, rhs[2]),
dot(lrz, rhs[2]),
dot(lrx, rhs[3]) + lhs[3].x,
dot(lry, rhs[3]) + lhs[3].y,
dot(lrz, rhs[3]) + lhs[3].z
);
}
}
// MotionTracker
MotionTracker::MotionTracker() :
DeviceTracker()
{
_jointsArray.resize(1);
_jointsMap.insert(JointTracker::Map::value_type(Semantic("Root"), 0));
}
MotionTracker::~MotionTracker()
{
}
bool MotionTracker::isConnected() const {
return false;
}
MotionTracker::Index MotionTracker::addJoint(const Semantic& semantic, Index parent) {
// Check the parent
if (int(parent) < 0)
return INVALID_PARENT;
// Check that the semantic is not already in use
Index foundIndex = findJointIndex(semantic);
if (foundIndex >= 0) {
return INVALID_SEMANTIC;
}
// All good then allocate the joint
Index newIndex = (Index)_jointsArray.size();
_jointsArray.push_back(JointTracker(semantic, parent));
_jointsMap.insert(JointTracker::Map::value_type(semantic, newIndex));
return newIndex;
}
MotionTracker::Index MotionTracker::findJointIndex(const Semantic& semantic) const {
// TODO C++11 auto jointIt = _jointsMap.find(semantic);
JointTracker::Map::const_iterator jointIt = _jointsMap.find(semantic);
if (jointIt != _jointsMap.end()) {
return (*jointIt).second;
}
return INVALID_SEMANTIC;
}
void MotionTracker::updateAllAbsTransform() {
_jointsArray[0].updateAbsFromLocTransform(0);
// Because we know the hierarchy is stored from root down the branches let's just traverse and update
for (Index i = 1; i < (Index)(_jointsArray.size()); i++) {
JointTracker* joint = _jointsArray.data() + i;
joint->updateAbsFromLocTransform(_jointsArray.data() + joint->getParent());
}
}
// MotionTracker::JointTracker
MotionTracker::JointTracker::JointTracker() :
_locFrame(),
_absFrame(),
_semantic(""),
_parent(INVALID_PARENT),
_lastUpdate(1) // Joint inactive
{
}
MotionTracker::JointTracker::JointTracker(const Semantic& semantic, Index parent) :
_semantic(semantic),
_parent(parent),
_lastUpdate(1) // Joint inactive
{
}
MotionTracker::JointTracker::JointTracker(const JointTracker& tracker) :
_locFrame(tracker._locFrame),
_absFrame(tracker._absFrame),
_semantic(tracker._semantic),
_parent(tracker._parent),
_lastUpdate(tracker._lastUpdate)
{
}
void MotionTracker::JointTracker::updateAbsFromLocTransform(const JointTracker* parentJoint) {
if (parentJoint) {
editAbsFrame()._transform = (parentJoint->getAbsFrame()._transform * getLocFrame()._transform);
} else {
editAbsFrame()._transform = getLocFrame()._transform;
}
}
void MotionTracker::JointTracker::updateLocFromAbsTransform(const JointTracker* parentJoint) {
if (parentJoint) {
glm::mat4 ip = glm::inverse(parentJoint->getAbsFrame()._transform);
editLocFrame()._transform = (ip * getAbsFrame()._transform);
} else {
editLocFrame()._transform = getAbsFrame()._transform;
}
}
//--------------------------------------------------------------------------------------
// MotionTracker::Frame
//--------------------------------------------------------------------------------------
MotionTracker::Frame::Frame() :
_transform()
{
}
void MotionTracker::Frame::setRotation(const glm::quat& rotation) {
glm::mat3x3 rot = glm::mat3_cast(rotation);
_transform[0] = glm::vec4(rot[0], 0.0f);
_transform[1] = glm::vec4(rot[1], 0.0f);
_transform[2] = glm::vec4(rot[2], 0.0f);
}
void MotionTracker::Frame::getRotation(glm::quat& rotation) const {
rotation = glm::quat_cast(_transform);
}
void MotionTracker::Frame::setTranslation(const glm::vec3& translation) {
_transform[3] = glm::vec4(translation, 1.0f);
}
void MotionTracker::Frame::getTranslation(glm::vec3& translation) const {
translation = extractTranslation(_transform);
}

View file

@ -1,116 +0,0 @@
//
// Created by Sam Cake on 6/20/14.
// Copyright 2014 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
//
#ifndef hifi_MotionTracker_h
#define hifi_MotionTracker_h
#include "DeviceTracker.h"
#include <GLMHelpers.h>
/// Base class for device trackers.
class MotionTracker : public DeviceTracker {
public:
class Frame {
public:
Frame();
glm::mat4 _transform;
void setRotation(const glm::quat& rotation);
void getRotation(glm::quat& rotatio) const;
void setTranslation(const glm::vec3& translation);
void getTranslation(glm::vec3& translation) const;
};
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
typedef std::string Semantic;
typedef int32_t Index;
static const Index INVALID_SEMANTIC = -1;
static const Index INVALID_PARENT = -2;
class JointTracker {
public:
typedef std::vector< JointTracker > Vector;
typedef std::map< Semantic, Index > Map;
JointTracker();
JointTracker(const JointTracker& tracker);
JointTracker(const Semantic& semantic, Index parent);
const Frame& getLocFrame() const { return _locFrame; }
Frame& editLocFrame() { return _locFrame; }
void setLocFrame(const Frame& frame) { editLocFrame() = frame; }
const Frame& getAbsFrame() const { return _absFrame; }
Frame& editAbsFrame() { return _absFrame; }
void setAbsFrame(const Frame& frame) { editAbsFrame() = frame; }
const Semantic& getSemantic() const { return _semantic; }
const Index& getParent() const { return _parent; }
bool isActive() const { return (_lastUpdate <= 0); }
void tickNewFrame() { _lastUpdate++; }
void activeFrame() { _lastUpdate = 0; }
/// Update the loc/abs transform for this joint from the current abs/loc value and the specified parent joint abs frame
void updateLocFromAbsTransform(const JointTracker* parentJoint);
void updateAbsFromLocTransform(const JointTracker* parentJoint);
protected:
Frame _locFrame;
Frame _absFrame;
Semantic _semantic;
Index _parent;
int _lastUpdate;
};
virtual bool isConnected() const;
Index numJointTrackers() const { return (Index)_jointsArray.size(); }
/// Access a Joint from it's index.
/// Index 0 is always the "Root".
/// if the index is Invalid then returns NULL.
const JointTracker* getJointTracker(Index index) const { return ((index > 0) && (index < (Index)(_jointsArray.size())) ? _jointsArray.data() + index : NULL); }
JointTracker* editJointTracker(Index index) { return ((index > 0) && (index < (Index)(_jointsArray.size())) ? _jointsArray.data() + index : NULL); }
/// From a semantic, find the Index of the Joint.
/// \return the index of the mapped Joint or INVALID_SEMANTIC if the semantic is not knowned.
Index findJointIndex(const Semantic& semantic) const;
protected:
MotionTracker();
virtual ~MotionTracker();
JointTracker::Vector _jointsArray;
JointTracker::Map _jointsMap;
/// Adding joint is only done from the specialized Motion Tracker, hence this function is protected.
/// The hierarchy of joints must be created from the top down to the branches.
/// The "Root" node is at index 0 and exists at creation of the Motion Tracker.
///
/// \param semantic A joint is defined by it's semantic, the unique name mapping to it
/// \param parent The parent's index, the parent must be valid and correspond to a Joint that has been previously created
///
/// \return The Index of the newly created Joint.
/// Valid if everything went well.
/// INVALID_SEMANTIC if the semantic is already in use
/// INVALID_PARENT if the parent is not valid
Index addJoint(const Semantic& semantic, Index parent);
/// Update the absolute transform stack traversing the hierarchy from the root down the branches
/// This is a generic way to update all the Joint's absFrame by combining the locFrame going down the hierarchy branch.
void updateAllAbsTransform();
};
#endif // hifi_MotionTracker_h

View file

@ -30,6 +30,8 @@ if (NOT SERVER_ONLY AND NOT ANDROID)
add_subdirectory(${DIR})
set(DIR "steamClient")
add_subdirectory(${DIR})
set(DIR "hifiLeapMotion")
add_subdirectory(${DIR})
endif()
# server-side plugins

View file

@ -0,0 +1,15 @@
#
# Created by David Rowe on 15 Jun 2017.
# 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
#
find_package(LEAPMOTION)
if (LEAPMOTION_FOUND)
set(TARGET_NAME hifiLeapMotion)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins input-plugins)
target_leapmotion()
endif()

View file

@ -0,0 +1,493 @@
//
// LeapMotionPlugin.cpp
//
// Created by David Rowe on 15 Jun 2017.
// 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
//
#include "LeapMotionPlugin.h"
#include <QLoggingCategory>
#include <controllers/UserInputMapper.h>
#include <NumericalConstants.h>
#include <PathUtils.h>
#include <Preferences.h>
#include <SettingHandle.h>
Q_DECLARE_LOGGING_CATEGORY(inputplugins)
Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
const char* LeapMotionPlugin::NAME = "Leap Motion";
const char* LeapMotionPlugin::LEAPMOTION_ID_STRING = "Leap Motion";
const bool DEFAULT_ENABLED = false;
const char* SENSOR_ON_DESKTOP = "Desktop";
const char* SENSOR_ON_HMD = "HMD";
const char* DEFAULT_SENSOR_LOCATION = SENSOR_ON_DESKTOP;
enum LeapMotionJointIndex {
LeftHand = 0,
LeftHandThumb1,
LeftHandThumb2,
LeftHandThumb3,
LeftHandThumb4,
LeftHandIndex1,
LeftHandIndex2,
LeftHandIndex3,
LeftHandIndex4,
LeftHandMiddle1,
LeftHandMiddle2,
LeftHandMiddle3,
LeftHandMiddle4,
LeftHandRing1,
LeftHandRing2,
LeftHandRing3,
LeftHandRing4,
LeftHandPinky1,
LeftHandPinky2,
LeftHandPinky3,
LeftHandPinky4,
RightHand,
RightHandThumb1,
RightHandThumb2,
RightHandThumb3,
RightHandThumb4,
RightHandIndex1,
RightHandIndex2,
RightHandIndex3,
RightHandIndex4,
RightHandMiddle1,
RightHandMiddle2,
RightHandMiddle3,
RightHandMiddle4,
RightHandRing1,
RightHandRing2,
RightHandRing3,
RightHandRing4,
RightHandPinky1,
RightHandPinky2,
RightHandPinky3,
RightHandPinky4,
Size
};
static controller::StandardPoseChannel LeapMotionJointIndexToPoseIndexMap[LeapMotionJointIndex::Size] = {
controller::LEFT_HAND,
controller::LEFT_HAND_THUMB1,
controller::LEFT_HAND_THUMB2,
controller::LEFT_HAND_THUMB3,
controller::LEFT_HAND_THUMB4,
controller::LEFT_HAND_INDEX1,
controller::LEFT_HAND_INDEX2,
controller::LEFT_HAND_INDEX3,
controller::LEFT_HAND_INDEX4,
controller::LEFT_HAND_MIDDLE1,
controller::LEFT_HAND_MIDDLE2,
controller::LEFT_HAND_MIDDLE3,
controller::LEFT_HAND_MIDDLE4,
controller::LEFT_HAND_RING1,
controller::LEFT_HAND_RING2,
controller::LEFT_HAND_RING3,
controller::LEFT_HAND_RING4,
controller::LEFT_HAND_PINKY1,
controller::LEFT_HAND_PINKY2,
controller::LEFT_HAND_PINKY3,
controller::LEFT_HAND_PINKY4,
controller::RIGHT_HAND,
controller::RIGHT_HAND_THUMB1,
controller::RIGHT_HAND_THUMB2,
controller::RIGHT_HAND_THUMB3,
controller::RIGHT_HAND_THUMB4,
controller::RIGHT_HAND_INDEX1,
controller::RIGHT_HAND_INDEX2,
controller::RIGHT_HAND_INDEX3,
controller::RIGHT_HAND_INDEX4,
controller::RIGHT_HAND_MIDDLE1,
controller::RIGHT_HAND_MIDDLE2,
controller::RIGHT_HAND_MIDDLE3,
controller::RIGHT_HAND_MIDDLE4,
controller::RIGHT_HAND_RING1,
controller::RIGHT_HAND_RING2,
controller::RIGHT_HAND_RING3,
controller::RIGHT_HAND_RING4,
controller::RIGHT_HAND_PINKY1,
controller::RIGHT_HAND_PINKY2,
controller::RIGHT_HAND_PINKY3,
controller::RIGHT_HAND_PINKY4
};
#define UNKNOWN_JOINT (controller::StandardPoseChannel)0
static controller::StandardPoseChannel LeapMotionJointIndexToPoseIndex(LeapMotionJointIndex i) {
assert(i >= 0 && i < LeapMotionJointIndex::Size);
if (i >= 0 && i < LeapMotionJointIndex::Size) {
return LeapMotionJointIndexToPoseIndexMap[i];
} else {
return UNKNOWN_JOINT;
}
}
QStringList controllerJointNames = {
"Hips",
"RightUpLeg",
"RightLeg",
"RightFoot",
"LeftUpLeg",
"LeftLeg",
"LeftFoot",
"Spine",
"Spine1",
"Spine2",
"Spine3",
"Neck",
"Head",
"RightShoulder",
"RightArm",
"RightForeArm",
"RightHand",
"RightHandThumb1",
"RightHandThumb2",
"RightHandThumb3",
"RightHandThumb4",
"RightHandIndex1",
"RightHandIndex2",
"RightHandIndex3",
"RightHandIndex4",
"RightHandMiddle1",
"RightHandMiddle2",
"RightHandMiddle3",
"RightHandMiddle4",
"RightHandRing1",
"RightHandRing2",
"RightHandRing3",
"RightHandRing4",
"RightHandPinky1",
"RightHandPinky2",
"RightHandPinky3",
"RightHandPinky4",
"LeftShoulder",
"LeftArm",
"LeftForeArm",
"LeftHand",
"LeftHandThumb1",
"LeftHandThumb2",
"LeftHandThumb3",
"LeftHandThumb4",
"LeftHandIndex1",
"LeftHandIndex2",
"LeftHandIndex3",
"LeftHandIndex4",
"LeftHandMiddle1",
"LeftHandMiddle2",
"LeftHandMiddle3",
"LeftHandMiddle4",
"LeftHandRing1",
"LeftHandRing2",
"LeftHandRing3",
"LeftHandRing4",
"LeftHandPinky1",
"LeftHandPinky2",
"LeftHandPinky3",
"LeftHandPinky4"
};
static const char* getControllerJointName(controller::StandardPoseChannel i) {
if (i >= 0 && i < controller::NUM_STANDARD_POSES) {
return qPrintable(controllerJointNames[i]);
}
return "unknown";
}
void LeapMotionPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
if (!_enabled) {
return;
}
const auto frame = _controller.frame();
const auto frameID = frame.id();
if (_lastFrameID >= frameID) {
// Leap Motion not connected or duplicate frame.
return;
}
if (!_hasLeapMotionBeenConnected) {
emit deviceConnected(getName());
_hasLeapMotionBeenConnected = true;
}
processFrame(frame); // Updates _joints.
auto joints = _joints;
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints);
});
_prevJoints = joints;
_lastFrameID = frameID;
}
controller::Input::NamedVector LeapMotionPlugin::InputDevice::getAvailableInputs() const {
static controller::Input::NamedVector availableInputs;
if (availableInputs.size() == 0) {
for (int i = 0; i < LeapMotionJointIndex::Size; i++) {
auto channel = LeapMotionJointIndexToPoseIndex(static_cast<LeapMotionJointIndex>(i));
availableInputs.push_back(makePair(channel, getControllerJointName(channel)));
}
};
return availableInputs;
}
QString LeapMotionPlugin::InputDevice::getDefaultMappingConfig() const {
static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/leapmotion.json";
return MAPPING_JSON;
}
void LeapMotionPlugin::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
const std::vector<LeapMotionPlugin::LeapMotionJoint>& joints,
const std::vector<LeapMotionPlugin::LeapMotionJoint>& prevJoints) {
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
glm::quat controllerToAvatarRotation = glmExtractRotation(controllerToAvatar);
glm::vec3 hmdSensorPosition; // HMD
glm::quat hmdSensorOrientation; // HMD
glm::vec3 leapMotionOffset; // Desktop
if (_isLeapOnHMD) {
hmdSensorPosition = extractTranslation(inputCalibrationData.hmdSensorMat);
hmdSensorOrientation = extractRotation(inputCalibrationData.hmdSensorMat);
} else {
// Desktop "zero" position is some distance above the Leap Motion sensor and half the avatar's shoulder-to-hand length
// in front of avatar.
float halfShouldToHandLength = fabsf(extractTranslation(inputCalibrationData.defaultLeftHand).x
- extractTranslation(inputCalibrationData.defaultLeftArm).x) / 2.0f;
leapMotionOffset = glm::vec3(0.0f, _desktopHeightOffset, halfShouldToHandLength);
}
for (size_t i = 0; i < joints.size(); i++) {
int poseIndex = LeapMotionJointIndexToPoseIndex((LeapMotionJointIndex)i);
if (joints[i].position == Vectors::ZERO) {
_poseStateMap[poseIndex] = controller::Pose();
continue;
}
glm::vec3 pos;
glm::quat rot;
if (_isLeapOnHMD) {
auto jointPosition = joints[i].position;
const glm::vec3 HMD_EYE_TO_LEAP_OFFSET = glm::vec3(0.0f, 0.0f, -0.09f); // Eyes to surface of Leap Motion.
jointPosition = glm::vec3(-jointPosition.x, -jointPosition.z, -jointPosition.y) + HMD_EYE_TO_LEAP_OFFSET;
jointPosition = hmdSensorPosition + hmdSensorOrientation * jointPosition;
pos = transformPoint(controllerToAvatar, jointPosition);
glm::quat jointOrientation = joints[i].orientation;
jointOrientation = glm::quat(jointOrientation.w, -jointOrientation.x, -jointOrientation.z, -jointOrientation.y);
rot = controllerToAvatarRotation * hmdSensorOrientation * jointOrientation;
} else {
pos = controllerToAvatarRotation * (joints[i].position - leapMotionOffset);
const glm::quat ZERO_HAND_ORIENTATION = glm::quat(glm::vec3(PI_OVER_TWO, PI, 0.0f));
rot = controllerToAvatarRotation * joints[i].orientation * ZERO_HAND_ORIENTATION;
}
glm::vec3 linearVelocity, angularVelocity;
if (i < prevJoints.size()) {
linearVelocity = (pos - (prevJoints[i].position * METERS_PER_CENTIMETER)) / deltaTime; // m/s
// quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation.
glm::quat d = glm::log(rot * glm::inverse(prevJoints[i].orientation));
angularVelocity = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s
}
_poseStateMap[poseIndex] = controller::Pose(pos, rot, linearVelocity, angularVelocity);
}
}
void LeapMotionPlugin::init() {
loadSettings();
auto preferences = DependencyManager::get<Preferences>();
static const QString LEAPMOTION_PLUGIN { "Leap Motion" };
{
auto getter = [this]()->bool { return _enabled; };
auto setter = [this](bool value) {
_enabled = value;
saveSettings();
if (!_enabled) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->withLock([&, this]() {
_inputDevice->clearState();
});
}
};
auto preference = new CheckPreference(LEAPMOTION_PLUGIN, "Enabled", getter, setter);
preferences->addPreference(preference);
}
{
auto getter = [this]()->QString { return _sensorLocation; };
auto setter = [this](QString value) {
_sensorLocation = value;
saveSettings();
applySensorLocation();
};
auto preference = new ComboBoxPreference(LEAPMOTION_PLUGIN, "Sensor location", getter, setter);
QStringList list = { SENSOR_ON_DESKTOP, SENSOR_ON_HMD };
preference->setItems(list);
preferences->addPreference(preference);
}
{
auto getter = [this]()->float { return _desktopHeightOffset; };
auto setter = [this](float value) {
_desktopHeightOffset = value;
saveSettings();
applyDesktopHeightOffset();
};
auto preference = new SpinnerPreference(LEAPMOTION_PLUGIN, "Desktop height for horizontal forearms", getter, setter);
float MIN_VALUE = 0.0f;
float MAX_VALUE = 1.0f;
float DECIMALS = 2.0f;
float STEP = 0.01f;
preference->setMin(MIN_VALUE);
preference->setMax(MAX_VALUE);
preference->setDecimals(DECIMALS);
preference->setStep(STEP);
preferences->addPreference(preference);
}
}
bool LeapMotionPlugin::activate() {
InputPlugin::activate();
// Nothing required to be done to start up Leap Motion library.
_joints.resize(LeapMotionJointIndex::Size, { glm::vec3(), glm::quat() });
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->registerDevice(_inputDevice);
return true;
}
void LeapMotionPlugin::deactivate() {
if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) {
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
userInputMapper->removeDevice(_inputDevice->_deviceID);
}
InputPlugin::deactivate();
}
const char* SETTINGS_ENABLED_KEY = "enabled";
const char* SETTINGS_SENSOR_LOCATION_KEY = "sensorLocation";
const char* SETTINGS_DESKTOP_HEIGHT_OFFSET_KEY = "desktopHeightOffset";
void LeapMotionPlugin::saveSettings() const {
Settings settings;
QString idString = getID();
settings.beginGroup(idString);
{
settings.setValue(QString(SETTINGS_ENABLED_KEY), _enabled);
settings.setValue(QString(SETTINGS_SENSOR_LOCATION_KEY), _sensorLocation);
settings.setValue(QString(SETTINGS_DESKTOP_HEIGHT_OFFSET_KEY), _desktopHeightOffset);
}
settings.endGroup();
}
void LeapMotionPlugin::loadSettings() {
Settings settings;
QString idString = getID();
settings.beginGroup(idString);
{
_enabled = settings.value(SETTINGS_ENABLED_KEY, QVariant(DEFAULT_ENABLED)).toBool();
_sensorLocation = settings.value(SETTINGS_SENSOR_LOCATION_KEY, QVariant(DEFAULT_SENSOR_LOCATION)).toString();
_desktopHeightOffset =
settings.value(SETTINGS_DESKTOP_HEIGHT_OFFSET_KEY, QVariant(DEFAULT_DESKTOP_HEIGHT_OFFSET)).toFloat();
applySensorLocation();
applyDesktopHeightOffset();
}
settings.endGroup();
}
void LeapMotionPlugin::InputDevice::clearState() {
for (size_t i = 0; i < LeapMotionJointIndex::Size; i++) {
int poseIndex = LeapMotionJointIndexToPoseIndex((LeapMotionJointIndex)i);
_poseStateMap[poseIndex] = controller::Pose();
}
}
void LeapMotionPlugin::applySensorLocation() {
bool isLeapOnHMD = _sensorLocation == SENSOR_ON_HMD;
_controller.setPolicyFlags(isLeapOnHMD ? Leap::Controller::POLICY_OPTIMIZE_HMD : Leap::Controller::POLICY_DEFAULT);
_inputDevice->setIsLeapOnHMD(isLeapOnHMD);
}
void LeapMotionPlugin::applyDesktopHeightOffset() {
_inputDevice->setDektopHeightOffset(_desktopHeightOffset);
}
const float LEFT_SIDE_SIGN = -1.0f;
const float RIGHT_SIDE_SIGN = 1.0f;
glm::quat LeapBasisToQuat(float sideSign, const Leap::Matrix& basis) {
glm::vec3 xAxis = sideSign * glm::vec3(basis.xBasis.x, basis.xBasis.y, basis.xBasis.z);
glm::vec3 yAxis = glm::vec3(basis.yBasis.x, basis.yBasis.y, basis.yBasis.z);
glm::vec3 zAxis = glm::vec3(basis.zBasis.x, basis.zBasis.y, basis.zBasis.z);
glm::quat orientation = (glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis)));
return orientation;
}
glm::vec3 LeapVectorToVec3(const Leap::Vector& vec) {
return glm::vec3(vec.x * METERS_PER_MILLIMETER, vec.y * METERS_PER_MILLIMETER, vec.z * METERS_PER_MILLIMETER);
}
void LeapMotionPlugin::processFrame(const Leap::Frame& frame) {
// Default to uncontrolled.
for (int i = 0; i < _joints.size(); i++) {
_joints[i].position = glm::vec3();
}
auto hands = frame.hands();
const int MAX_NUMBER_OF_HANDS = 2;
for (int i = 0; i < hands.count() && i < MAX_NUMBER_OF_HANDS; i++) {
auto hand = hands[i];
int sideSign = hand.isLeft() ? LEFT_SIDE_SIGN : RIGHT_SIDE_SIGN;
int jointIndex = hand.isLeft() ? LeapMotionJointIndex::LeftHand : LeapMotionJointIndex::RightHand;
// Hand.
_joints[jointIndex].position = LeapVectorToVec3(hand.wristPosition());
_joints[jointIndex].orientation = LeapBasisToQuat(sideSign, hand.basis());
// Fingers.
// Leap Motion SDK guarantees full set of fingers and finger joints so can straightforwardly process them all.
Leap::FingerList fingers = hand.fingers();
for (int j = Leap::Finger::Type::TYPE_THUMB; j <= Leap::Finger::Type::TYPE_PINKY; j++) {
Leap::Finger finger;
finger = fingers[j];
Leap::Bone bone;
bone = finger.bone(Leap::Bone::Type::TYPE_PROXIMAL);
jointIndex++;
_joints[jointIndex].position = LeapVectorToVec3(bone.prevJoint());
_joints[jointIndex].orientation = LeapBasisToQuat(sideSign, bone.basis());
bone = finger.bone(Leap::Bone::Type::TYPE_INTERMEDIATE);
jointIndex++;
_joints[jointIndex].position = LeapVectorToVec3(bone.prevJoint());
_joints[jointIndex].orientation = LeapBasisToQuat(sideSign, bone.basis());
bone = finger.bone(Leap::Bone::Type::TYPE_DISTAL);
jointIndex++;
_joints[jointIndex].position = LeapVectorToVec3(bone.prevJoint());
_joints[jointIndex].orientation = LeapBasisToQuat(sideSign, bone.basis());
jointIndex++;
_joints[jointIndex].position = LeapVectorToVec3(bone.nextJoint());
_joints[jointIndex].orientation = LeapBasisToQuat(sideSign, bone.basis());
}
}
}

View file

@ -0,0 +1,99 @@
//
// LeapMotionPlugin.h
//
// Created by David Rowe on 15 Jun 2017.
// 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
//
#ifndef hifi_LeapMotionPlugin_h
#define hifi_LeapMotionPlugin_h
#include <controllers/InputDevice.h>
#include <plugins/InputPlugin.h>
// LeapMotion SDK
#include <Leap.h>
class LeapMotionPlugin : public InputPlugin {
Q_OBJECT
public:
// InputPlugin methods
virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
bool isHandController() const override { return true; }
// Plugin methods
virtual const QString getName() const override { return NAME; }
const QString getID() const override { return LEAPMOTION_ID_STRING; }
virtual void init() override;
virtual bool activate() override;
virtual void deactivate() override;
virtual void saveSettings() const override;
virtual void loadSettings() override;
protected:
static const char* NAME;
static const char* LEAPMOTION_ID_STRING;
const float DEFAULT_DESKTOP_HEIGHT_OFFSET = 0.2f;
bool _enabled { false };
QString _sensorLocation;
float _desktopHeightOffset { DEFAULT_DESKTOP_HEIGHT_OFFSET };
struct LeapMotionJoint {
glm::vec3 position;
glm::quat orientation;
};
std::vector<LeapMotionJoint> _joints;
std::vector<LeapMotionJoint> _prevJoints;
class InputDevice : public controller::InputDevice {
public:
friend class LeapMotionPlugin;
InputDevice() : controller::InputDevice("LeapMotion") {}
// Device functions
virtual controller::Input::NamedVector getAvailableInputs() const override;
virtual QString getDefaultMappingConfig() const override;
virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override {};
virtual void focusOutEvent() override {};
void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
const std::vector<LeapMotionPlugin::LeapMotionJoint>& joints,
const std::vector<LeapMotionPlugin::LeapMotionJoint>& prevJoints);
void clearState();
void setDektopHeightOffset(float desktopHeightOffset) { _desktopHeightOffset = desktopHeightOffset; };
void setIsLeapOnHMD(bool isLeapOnHMD) { _isLeapOnHMD = isLeapOnHMD; };
private:
float _desktopHeightOffset { 0.0f };
bool _isLeapOnHMD { false };
};
std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() };
private:
void applySensorLocation();
void applyDesktopHeightOffset();
void processFrame(const Leap::Frame& frame);
Leap::Controller _controller;
bool _hasLeapMotionBeenConnected { false };
int64_t _lastFrameID { -1 };
};
#endif // hifi_LeapMotionPlugin_h

View file

@ -0,0 +1,51 @@
//
// LeapMotionProvider.cpp
//
// Created by David Rowe on 15 Jun 2017.
// 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
//
#include <mutex>
#include <QtCore/QObject>
#include <QtCore/QtPlugin>
#include <QtCore/QStringList>
#include <plugins/RuntimePlugin.h>
#include <plugins/InputPlugin.h>
#include "LeapMotionPlugin.h"
class LeapMotionProvider : public QObject, public InputProvider
{
Q_OBJECT
Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json")
Q_INTERFACES(InputProvider)
public:
LeapMotionProvider(QObject* parent = nullptr) : QObject(parent) {}
virtual ~LeapMotionProvider() {}
virtual InputPluginList getInputPlugins() override {
static std::once_flag once;
std::call_once(once, [&] {
InputPluginPointer plugin(new LeapMotionPlugin());
if (plugin->isSupported()) {
_inputPlugins.push_back(plugin);
}
});
return _inputPlugins;
}
virtual void destroyInputPlugins() override {
_inputPlugins.clear();
}
private:
InputPluginList _inputPlugins;
};
#include "LeapMotionProvider.moc"

View file

@ -0,0 +1 @@
{"name":"Leap Motion"}

View file

@ -1,527 +0,0 @@
//
// 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 leftTriggerValue = 0;
var rightTriggerValue = 0;
var LEAP_TRIGGER_START_ANGLE = 15.0;
var LEAP_TRIGGER_END_ANGLE = 40.0;
function getLeapMotionLeftTrigger() {
//print("left trigger = " + leftTriggerValue);
return leftTriggerValue;
}
function getLeapMotionRightTrigger() {
//print("right trigger = " + rightTriggerValue);
return rightTriggerValue;
}
var leapHands = (function () {
var isOnHMD,
LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD",
LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip
HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief
hasHandAndWristJoints,
handToWristOffset = [], // For avatars without a wrist joint we control an estimate of a proper hand joint position
HAND_OFFSET = 0.4, // Relative distance of wrist to hand versus wrist to index finger knuckle
handAnimationStateHandlers,
handAnimationStateFunctions,
handAnimationStateProperties,
hands,
wrists,
NUM_HANDS = 2, // 0 = left; 1 = right
fingers,
NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky
THUMB = 0,
MIDDLE_FINGER = 2,
NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal joint
MAX_HAND_INACTIVE_COUNT = 20,
calibrationStatus,
UNCALIBRATED = 0,
CALIBRATING = 1,
CALIBRATED = 2,
CALIBRATION_TIME = 1000, // milliseconds
avatarScale,
avatarFaceModelURL,
avatarSkeletonModelURL,
settingsTimer,
HMD_CAMERA_TO_AVATAR_ROTATION = [
Quat.angleAxis(180.0, { x: 0, y: 0, z: 1 }),
Quat.angleAxis(-180.0, { x: 0, y: 0, z: 1 })
],
DESKTOP_CAMERA_TO_AVATAR_ROTATION =
Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 })),
LEAP_THUMB_ROOT_ADJUST = [Quat.fromPitchYawRollDegrees(0, 0, 20), Quat.fromPitchYawRollDegrees(0, 0, -20)];
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");
}
function animateLeftHand() {
var ROTATION_AND_POSITION = 0;
return {
leftHandType: ROTATION_AND_POSITION,
leftHandPosition: hands[0].position,
leftHandRotation: hands[0].rotation
};
}
function animateRightHand() {
var ROTATION_AND_POSITION = 0;
return {
rightHandType: ROTATION_AND_POSITION,
rightHandPosition: hands[1].position,
rightHandRotation: hands[1].rotation
};
}
function finishCalibration() {
var avatarPosition,
handPosition,
middleFingerPosition,
leapHandHeight,
h;
if (!isOnHMD) {
if (hands[0].controller.isActive() && hands[1].controller.isActive()) {
leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0;
} else {
calibrationStatus = UNCALIBRATED;
return;
}
}
avatarPosition = MyAvatar.position;
for (h = 0; h < NUM_HANDS; h += 1) {
handPosition = MyAvatar.getJointPosition(hands[h].jointName);
if (!hasHandAndWristJoints) {
middleFingerPosition = MyAvatar.getJointPosition(fingers[h][MIDDLE_FINGER][0].jointName);
handToWristOffset[h] = Vec3.multiply(Vec3.subtract(handPosition, middleFingerPosition), 1.0 - HAND_OFFSET);
}
if (isOnHMD) {
// Offset of Leap Motion origin from physical eye position
hands[h].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
} else {
hands[h].zeroPosition = {
x: handPosition.x - avatarPosition.x,
y: handPosition.y - avatarPosition.y,
z: avatarPosition.z - handPosition.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("RightHand");
MyAvatar.clearJointData("RightForeArm");
calibrationStatus = CALIBRATED;
print("Leap Motion: Calibrated");
}
function calibrate() {
var jointNames,
i;
calibrationStatus = CALIBRATING;
avatarScale = MyAvatar.scale;
avatarFaceModelURL = MyAvatar.faceModelURL;
avatarSkeletonModelURL = MyAvatar.skeletonModelURL;
// Does this skeleton have both wrist and hand joints?
hasHandAndWristJoints = false;
jointNames = MyAvatar.getJointNames();
for (i = 0; i < jointNames.length; i += 1) {
hasHandAndWristJoints = hasHandAndWristJoints || jointNames[i].toLowerCase() === "leftwrist";
}
// Set avatar arms vertical, forearms horizontal, as "zero" position for calibration
MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, 90.0));
MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollDegrees(0.0, 90.0, 0.0));
MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0));
MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollDegrees(0.0, -90.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 setIsOnHMD() {
isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM);
print("Leap Motion: " + (isOnHMD ? "Is on HMD" : "Is on desk"));
}
function checkSettings() {
if (calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale
|| MyAvatar.faceModelURL !== avatarFaceModelURL
|| MyAvatar.skeletonModelURL !== avatarSkeletonModelURL
|| Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM) !== isOnHMD)) {
print("Leap Motion: Recalibrate...");
calibrationStatus = UNCALIBRATED;
setIsOnHMD();
}
}
function setUp() {
wrists = [
{
jointName: "LeftWrist",
controller: Controller.createInputController("Spatial", "joint_L_wrist")
},
{
jointName: "RightWrist",
controller: Controller.createInputController("Spatial", "joint_R_wrist")
}
];
hands = [
{
jointName: "LeftHand",
controller: Controller.createInputController("Spatial", "joint_L_hand"),
inactiveCount: 0
},
{
jointName: "RightHand",
controller: Controller.createInputController("Spatial", "joint_R_hand"),
inactiveCount: 0
}
];
// The Leap controller's first joint is the hand-metacarpal joint but this joint's data is not used because it's too
// dependent on the model skeleton exactly matching the Leap skeleton; using just the second and subsequent joints
// seems to work better over all.
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") }
]
];
handAnimationStateHandlers = [null, null];
handAnimationStateFunctions = [animateLeftHand, animateRightHand];
handAnimationStateProperties = [
["leftHandType", "leftHandPosition", "leftHandRotation"],
["rightHandType", "rightHandPosition", "rightHandPosition"]
];
setIsOnHMD();
settingsTimer = Script.setInterval(checkSettings, 2000);
calibrationStatus = UNCALIBRATED;
{
var mapping = Controller.newMapping("LeapmotionTrigger");
mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT);
mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT);
mapping.enable();
}
}
function moveHands() {
var h,
i,
j,
side,
handOffset,
wristOffset,
handRotation,
locRotation,
cameraOrientation,
inverseAvatarOrientation;
for (h = 0; h < NUM_HANDS; h += 1) {
side = h === 0 ? -1.0 : 1.0;
if (hands[h].controller.isActive()) {
// Calibrate if necessary.
if (!checkCalibration()) {
return;
}
// Hand animation handlers ...
if (handAnimationStateHandlers[h] === null) {
handAnimationStateHandlers[h] = MyAvatar.addAnimationStateHandler(handAnimationStateFunctions[h],
handAnimationStateProperties[h]);
}
// Hand position ...
handOffset = hands[h].controller.getAbsTranslation();
handRotation = hands[h].controller.getAbsRotation();
if (isOnHMD) {
// Adjust to control wrist position if "hand" joint is at wrist ...
if (!hasHandAndWristJoints) {
wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]);
handOffset = Vec3.sum(handOffset, wristOffset);
}
// Hand offset in camera coordinates ...
handOffset = {
x: -handOffset.x,
y: -handOffset.z,
z: -handOffset.y - hands[h].zeroPosition.z
};
// Hand offset in world coordinates ...
cameraOrientation = Camera.getOrientation();
handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset));
// Hand offset in avatar coordinates ...
inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation);
handOffset = Vec3.subtract(handOffset, MyAvatar.position);
handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset);
handOffset.z = -handOffset.z;
handOffset.x = -handOffset.x;
// Hand rotation in camera coordinates ...
handRotation = {
x: -handRotation.y,
y: -handRotation.z,
z: -handRotation.x,
w: handRotation.w
};
// Hand rotation in avatar coordinates ...
handRotation = Quat.multiply(HMD_CAMERA_TO_AVATAR_ROTATION[h], handRotation);
cameraOrientation = {
x: cameraOrientation.z,
y: cameraOrientation.y,
z: cameraOrientation.x,
w: cameraOrientation.w
};
cameraOrientation = Quat.multiply(cameraOrientation, Quat.inverse(MyAvatar.orientation));
handRotation = Quat.multiply(handRotation, cameraOrientation); // Works!!!
} else {
// Adjust to control wrist position if "hand" joint is at wrist ...
if (!hasHandAndWristJoints) {
wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]);
handOffset = Vec3.sum(handOffset, wristOffset);
}
// Hand offset in camera coordinates ...
handOffset = {
x: -handOffset.x,
y: hands[h].zeroPosition.y + handOffset.y,
z: hands[h].zeroPosition.z - handOffset.z
};
// Hand rotation in camera coordinates ...
handRotation = {
x: handRotation.z,
y: handRotation.y,
z: handRotation.x,
w: handRotation.w
};
// Hand rotation in avatar coordinates ...
handRotation = Quat.multiply(DESKTOP_CAMERA_TO_AVATAR_ROTATION, handRotation);
}
// Set hand position and orientation for animation state handler ...
hands[h].position = handOffset;
hands[h].rotation = handRotation;
// Set finger joints ...
var summed = 0;
var closeAngle = 0;
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();
var eulers = Quat.safeEulerAngles(locRotation);
closeAngle += eulers.x;
summed++;
if (i === THUMB) {
locRotation = {
x: side * locRotation.y,
y: side * -locRotation.z,
z: side * -locRotation.x,
w: locRotation.w
};
if (j === 0) {
// Adjust avatar thumb root joint rotation to make avatar hands look better
locRotation = Quat.multiply(LEAP_THUMB_ROOT_ADJUST[h], locRotation);
}
} else {
locRotation = {
x: -locRotation.x,
y: -locRotation.z,
z: -locRotation.y,
w: locRotation.w
};
}
MyAvatar.setJointRotation(fingers[h][i][j].jointName, locRotation);
}
}
}
hands[h].inactiveCount = 0;
if (summed > 0) {
closeAngle /= summed;
}
var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE);
triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0));
if (h == 0) {
leftTriggerValue = triggerValue;
} else {
rightTriggerValue = triggerValue;
}
} else {
if (hands[h].inactiveCount < MAX_HAND_INACTIVE_COUNT) {
hands[h].inactiveCount += 1;
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
if (handAnimationStateHandlers[h] !== null) {
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
handAnimationStateHandlers[h] = null;
leftTriggerValue = 0.0;
rightTriggerValue = 0.0;
}
}
}
}
}
}
function tearDown() {
var h,
i,
j;
Script.clearInterval(settingsTimer);
for (h = 0; h < NUM_HANDS; h += 1) {
Controller.releaseInputController(hands[h].controller);
Controller.releaseInputController(wrists[h].controller);
if (handAnimationStateHandlers[h] !== null) {
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
}
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);