Revert "Revert "Add LeapMotion SDK support""

This reverts commit 8ce145085b3783782fafd051becbb0e339d5dfa1.
This commit is contained in:
samcake 2014-06-11 00:44:33 -07:00
parent 6b549a5269
commit fe03743163
5 changed files with 609 additions and 0 deletions

View file

@ -0,0 +1,44 @@
# Try to find the LeapMotion library
#
# You must provide a LEAPMOTION_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# LEAPMOTION_FOUND - system found LEAPMOTION
# LEAPMOTION_INCLUDE_DIRS - the LEAPMOTION include directory
# LEAPMOTION_LIBRARIES - Link this to use LEAPMOTION
#
# Created on 6/2/2014 by Sam Cake
# Copyright (c) 2014 High Fidelity
#
if (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)
# in cache already
set(LEAPMOTION_FOUND TRUE)
else (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)
set(LEAPMOTION_SEARCH_DIRS "${LEAPMOTION_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/leapmotion")
find_path(LEAPMOTION_INCLUDE_DIRS Leap.h ${LEAPMOTION_ROOT_DIR}/include)
if (WIN32)
find_library(LEAPMOTION_LIBRARIES Leap.lib ${LEAPMOTION_ROOT_DIR}/lib/x86)
endif (WIN32)
if (LEAPMOTION_INCLUDE_DIRS AND LEAPMOTION_LIBRARIES)
set(LEAPMOTION_FOUND TRUE)
endif (LEAPMOTION_INCLUDE_DIRS AND LEAPMOTION_LIBRARIES)
if (LEAPMOTION_FOUND)
if (NOT LEAPMOTION_FIND_QUIETLY)
message(STATUS "Found LEAPMOTION... ${LEAPMOTION_LIBRARIES}")
endif (NOT LEAPMOTION_FIND_QUIETLY)
else ()
if (LEAPMOTION_FIND_REQUIRED)
message(FATAL_ERROR "Could not find LEAPMOTION")
endif (LEAPMOTION_FIND_REQUIRED)
endif ()
# show the LEAPMOTION_INCLUDE_DIRS and LEAPMOTION_LIBRARIES variables only in the advanced view
mark_as_advanced(LEAPMOTION_INCLUDE_DIRS LEAPMOTION_LIBRARIES)
endif (LEAPMOTION_LIBRARIES AND LEAPMOTION_INCLUDE_DIRS)

View file

@ -18,6 +18,7 @@ set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus")
set(PRIOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/priovr")
set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense")
set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage")
set(LEAPMOTION_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/leapmotion")
find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5LinguistToolsMacros)
@ -138,6 +139,7 @@ find_package(PrioVR)
find_package(SDL)
find_package(Sixense)
find_package(Visage)
find_package(LeapMotion)
find_package(ZLIB)
find_package(Qxmpp)
@ -193,6 +195,13 @@ if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
# and with LeapMotion library
if (LEAPMOTION_FOUND AND NOT DISABLE_LEAPMOTION)
add_definitions(-DHAVE_LEAPMOTION)
include_directories(SYSTEM "${LEAPMOTION_INCLUDE_DIRS}")
target_link_libraries(${TARGET_NAME} "${LEAPMOTION_LIBRARIES}")
endif (LEAPMOTION_FOUND AND NOT DISABLE_LEAPMOTION)
# and with SDL for joysticks
if (SDL_FOUND AND NOT DISABLE_SDL)
add_definitions(-DHAVE_SDL)

View file

@ -0,0 +1,27 @@
Instructions for adding the Leap Motion library (LeapSDK) to Interface
Sam Cake, June 10, 2014
You can download the Leap Developer Kit from https://developer.leapmotion.com/ (account creation required). Interface has been tested with SDK version LeapDeveloperKit_2.0.2+16391_win.
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:
include/
- Leap.h
- Leap.i
- LeapMath.h
lib/
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)
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'leapmotion' that contains the 2 folders mentioned above (Include, Lib).
2. Clear your build directory, run cmake and build, and you should be all set.

View file

@ -0,0 +1,441 @@
//
// LeapMotionManager.cpp
// interface/src/devices
//
// 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 <QTimer>
#include <QtDebug>
#include <FBXReader.h>
#include "Application.h"
#include "LeapMotionManager.h"
#include "ui/TextRenderer.h"
#ifdef HAVE_LEAPMOTION
LeapMotionManager::SampleListener::SampleListener() : ::Leap::Listener()
{
// std::cout << __FUNCTION__ << std::endl;
}
LeapMotionManager::SampleListener::~SampleListener()
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onConnect(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onDisconnect(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onExit(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onFocusGained(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onFocusLost(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onFrame(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onInit(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onServiceConnect(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
void LeapMotionManager::SampleListener::onServiceDisconnect(const ::Leap::Controller &)
{
// std::cout << __FUNCTION__ << std::endl;
}
/*const unsigned int SERIAL_LIST[] = { 0x00000001, 0x00000000, 0x00000008, 0x00000009, 0x0000000A,
0x0000000C, 0x0000000D, 0x0000000E, 0x00000004, 0x00000005, 0x00000010, 0x00000011 };
const unsigned char AXIS_LIST[] = { 9, 43, 37, 37, 37, 13, 13, 13, 52, 52, 28, 28 };
const int LIST_LENGTH = sizeof(SERIAL_LIST) / sizeof(SERIAL_LIST[0]);
const char* JOINT_NAMES[] = { "Neck", "Spine", "LeftArm", "LeftForeArm", "LeftHand", "RightArm",
"RightForeArm", "RightHand", "LeftUpLeg", "LeftLeg", "RightUpLeg", "RightLeg" };
static int indexOfHumanIKJoint(const char* jointName) {
for (int i = 0;; i++) {
QByteArray humanIKJoint = HUMANIK_JOINTS[i];
if (humanIKJoint.isEmpty()) {
return -1;
}
if (humanIKJoint == jointName) {
return i;
}
}
}
static void setPalm(float deltaTime, int index) {
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();
PalmData* palm;
bool foundHand = false;
for (size_t j = 0; j < hand->getNumPalms(); j++) {
if (hand->getPalms()[j].getSixenseID() == index) {
palm = &(hand->getPalms()[j]);
foundHand = true;
}
}
if (!foundHand) {
PalmData newPalm(hand);
hand->getPalms().push_back(newPalm);
palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
palm->setSixenseID(index);
}
palm->setActive(true);
// Read controller buttons and joystick into the hand
if (!Application::getInstance()->getJoystickManager()->getJoystickStates().isEmpty()) {
const JoystickState& state = Application::getInstance()->getJoystickManager()->getJoystickStates().at(0);
if (state.axes.size() >= 4 && state.buttons.size() >= 4) {
if (index == LEFT_HAND_INDEX) {
palm->setControllerButtons(state.buttons.at(1) ? BUTTON_FWD : 0);
palm->setTrigger(state.buttons.at(0) ? 1.0f : 0.0f);
palm->setJoystick(state.axes.at(0), -state.axes.at(1));
} else {
palm->setControllerButtons(state.buttons.at(3) ? BUTTON_FWD : 0);
palm->setTrigger(state.buttons.at(2) ? 1.0f : 0.0f);
palm->setJoystick(state.axes.at(2), -state.axes.at(3));
}
}
}
glm::vec3 position;
glm::quat rotation;
SkeletonModel* skeletonModel = &Application::getInstance()->getAvatar()->getSkeletonModel();
int jointIndex;
glm::quat inverseRotation = glm::inverse(Application::getInstance()->getAvatar()->getOrientation());
if (index == LEFT_HAND_INDEX) {
jointIndex = skeletonModel->getLeftHandJointIndex();
skeletonModel->getJointRotation(jointIndex, rotation, true);
rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
} else {
jointIndex = skeletonModel->getRightHandJointIndex();
skeletonModel->getJointRotation(jointIndex, rotation, true);
rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, -PI_OVER_TWO, 0.0f));
}
skeletonModel->getJointPosition(jointIndex, position);
position = inverseRotation * (position - skeletonModel->getTranslation());
palm->setRawRotation(rotation);
// Compute current velocity from position change
glm::vec3 rawVelocity;
if (deltaTime > 0.f) {
rawVelocity = (position - palm->getRawPosition()) / deltaTime;
} else {
rawVelocity = glm::vec3(0.0f);
}
palm->setRawVelocity(rawVelocity);
palm->setRawPosition(position);
// Store the one fingertip in the palm structure so we can track velocity
const float FINGER_LENGTH = 0.3f; // meters
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
glm::vec3 oldTipPosition = palm->getTipRawPosition();
if (deltaTime > 0.f) {
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
} else {
palm->setTipVelocity(glm::vec3(0.f));
}
palm->setTipPosition(newTipPosition);
}
*/
// default (expected) location of neck in sixense space
const float LEAP_X = 0.25f; // meters
const float LEAP_Y = 0.3f; // meters
const float LEAP_Z = 0.3f; // meters
#endif
LeapMotionManager::LeapMotionManager() {
#ifdef HAVE_LEAPMOTION
// Have the sample listener receive events from the controller
_controller.addListener(_listener);
// By default we assume the _neckBase (in orb frame) is as high above the orb
// as the "torso" is below it.
_leapBasePos = glm::vec3(0, -LEAP_Y, LEAP_Z);
glm::vec3 xAxis(1.f, 0.f, 0.f);
glm::vec3 yAxis(0.f, 1.f, 0.f);
glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, yAxis));
xAxis = glm::normalize(glm::cross(yAxis, zAxis));
_leapBaseOri = glm::inverse(glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis)));
#endif
}
LeapMotionManager::~LeapMotionManager() {
#ifdef HAVE_LEAPMOTION
// Remove the sample listener when done
_controller.removeListener(_listener);
#endif
}
const int HEAD_ROTATION_INDEX = 0;
glm::vec3 LeapMotionManager::getHandPos( unsigned int handNb ) const
{
if ( handNb < _hands.size() )
{
return _hands[ handNb ];
}
else
return glm::vec3(0.f);
}
void LeapMotionManager::update(float deltaTime) {
#ifdef HAVE_LEAPMOTION
if ( !_controller.isConnected() )
return;
// Get the most recent frame and report some basic information
const Leap::Frame frame = _controller.frame();
static _int64 lastFrame = -1;
_hands.clear();
_int64 newFrameNb = frame.id();
if ( (lastFrame >= newFrameNb) )
return;
glm::vec3 delta(0.f);
glm::quat handOri;
if (!frame.hands().isEmpty())
{
// Get the first hand
const Leap::Hand hand = frame.hands()[0];
Leap::Vector lp = hand.palmPosition();
glm::vec3 p(lp.x * METERS_PER_MILLIMETER, lp.y * METERS_PER_MILLIMETER, lp.z * METERS_PER_MILLIMETER );
Leap::Vector n = hand.palmNormal();
glm::vec3 xAxis(n.x, n.y, n.z);
glm::vec3 yAxis(0.f, 1.f, 0.f);
glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, yAxis));
xAxis = glm::normalize(glm::cross(yAxis, zAxis));
handOri = glm::inverse(glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis)));
_hands.push_back( p );
//Leap::Vector dp = hand.translation( _controller.frame( lastFrame ) );
Leap::Vector dp = hand.palmVelocity();
delta = glm::vec3( dp.x * METERS_PER_MILLIMETER, dp.y * METERS_PER_MILLIMETER, dp.z * METERS_PER_MILLIMETER);
}
lastFrame = newFrameNb;
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();
// for ( int h = 0; h < frame.hands().count(); h++ )
if ( _hands.size() )
{
// Set palm position and normal based on Hydra position/orientation
// Either find a palm matching the sixense controller, or make a new one
PalmData* palm;
bool foundHand = false;
for (size_t j = 0; j < hand->getNumPalms(); j++) {
if (hand->getPalms()[j].getSixenseID() == 28) {
palm = &(hand->getPalms()[j]);
foundHand = true;
}
}
if (!foundHand) {
PalmData newPalm(hand);
hand->getPalms().push_back(newPalm);
palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
palm->setSixenseID(28);
qDebug("Found new LeapMotion hand, ID %i", 28);
}
palm->setActive(true);
// Read controller buttons and joystick into the hand
//palm->setControllerButtons(data->buttons);
//palm->setTrigger(data->trigger);
//palm->setJoystick(data->joystick_x, data->joystick_y);
glm::vec3 position(_hands[0]);
// Transform the measured position into body frame.
glm::vec3 neck = _leapBasePos;
// Zeroing y component of the "neck" effectively raises the measured position a little bit.
//neck.y = 0.f;
position = _leapBaseOri * (position - neck);
// Rotation of Palm
glm::quat rotation(handOri[3], -handOri[0], handOri[1], -handOri[2]);
rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _leapBaseOri * rotation;
// Compute current velocity from position change
glm::vec3 rawVelocity;
if (deltaTime > 0.f) {
// rawVelocity = (position - palm->getRawPosition()) / deltaTime;
rawVelocity = delta / deltaTime;
} else {
rawVelocity = glm::vec3(0.0f);
}
palm->setRawVelocity(rawVelocity); // meters/sec
// Use a velocity sensitive filter to damp small motions and preserve large ones with
// no latency.
float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f);
palm->setRawPosition(palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter));
palm->setRawRotation(safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter));
// use the velocity to determine whether there's any movement (if the hand isn't new)
/* const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f;
_amountMoved += rawVelocity * deltaTime;
if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) {
_lastMovement = usecTimestampNow();
_amountMoved = glm::vec3(0.0f);
}*/
// Store the one fingertip in the palm structure so we can track velocity
/* const float FINGER_LENGTH = 0.3f; // meters
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
glm::vec3 oldTipPosition = palm->getTipRawPosition();
if (deltaTime > 0.f) {
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
} else {
palm->setTipVelocity(glm::vec3(0.f));
}
palm->setTipPosition(newTipPosition);*/
}
else
{
// Either find a palm matching the sixense controller, or make a new one
PalmData* palm;
bool foundHand = false;
for (size_t j = 0; j < hand->getNumPalms(); j++) {
if (hand->getPalms()[j].getSixenseID() == 28) {
palm = &(hand->getPalms()[j]);
foundHand = true;
}
}
if (foundHand) {
palm->setRawPosition(palm->getRawPosition());
palm->setRawRotation(palm->getRawRotation());
palm->setActive(false);
}
}
/* if (numActiveControllers == 2) {
updateCalibration(controllers);
}
*/
// }
if ( false )
{
std::cout << "Frame id: " << frame.id()
<< ", timestamp: " << frame.timestamp()
<< ", hands: " << frame.hands().count()
<< ", fingers: " << frame.fingers().count()
<< ", tools: " << frame.tools().count() << std::endl;
if (!frame.hands().isEmpty()) {
// Get the first hand
const Leap::Hand hand = frame.hands()[0];
// Check if the hand has any fingers
const Leap::FingerList fingers = hand.fingers();
if (!fingers.isEmpty()) {
// Calculate the hand's average finger tip position
Leap::Vector avgPos;
for (int i = 0; i < fingers.count(); ++i) {
avgPos += fingers[i].tipPosition();
}
avgPos /= (float)fingers.count();
std::cout << "Hand has " << fingers.count()
<< " fingers, average finger tip position" << avgPos << std::endl;
}
// Get the hand's sphere radius and palm position
std::cout << "Hand sphere radius: " << hand.sphereRadius()
<< " mm, palm position: " << hand.palmPosition() << std::endl;
// Get the hand's normal vector and direction
const Leap::Vector normal = hand.palmNormal();
const Leap::Vector direction = hand.direction();
// Calculate the hand's pitch, roll, and yaw angles
const float RAD_TO_DEG = 180.0 / 3.1415;
std::cout << "Hand pitch: " << direction.pitch() * RAD_TO_DEG << " degrees, "
<< "roll: " << normal.roll() * RAD_TO_DEG << " degrees, "
<< "yaw: " << direction.yaw() * RAD_TO_DEG << " degrees" << std::endl << std::endl;
}
}
#endif
}
void LeapMotionManager::reset() {
#ifdef HAVE_LEAPMOTION
if (!_controller.isConnected()) {
return;
}
/* connect(Application::getInstance(), SIGNAL(renderingOverlay()), SLOT(renderCalibrationCountdown()));
_calibrationCountdownStarted = QDateTime::currentDateTime();
*/
#endif
}
void LeapMotionManager::renderCalibrationCountdown() {
#ifdef HAVE_LEAPMOTION
/* const int COUNTDOWN_SECONDS = 3;
int secondsRemaining = COUNTDOWN_SECONDS - _calibrationCountdownStarted.secsTo(QDateTime::currentDateTime());
if (secondsRemaining == 0) {
yei_tareSensors(_skeletalDevice);
Application::getInstance()->disconnect(this);
return;
}
static TextRenderer textRenderer(MONO_FONT_FAMILY, 18, QFont::Bold, false, TextRenderer::OUTLINE_EFFECT, 2);
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
textRenderer.draw((Application::getInstance()->getGLWidget()->width() - textRenderer.computeWidth(text.constData())) / 2,
Application::getInstance()->getGLWidget()->height() / 2,
text);
*/
#endif
}

View file

@ -0,0 +1,88 @@
//
// LeapMotionManager.h
// interface/src/devices
//
// 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_LeapMotionManager_h
#define hifi_LeapMotionManager_h
#include <QDateTime>
#include <QObject>
#include <QVector>
#include <glm/gtc/quaternion.hpp>
#ifdef HAVE_LEAPMOTION
#include <Leap.h>
/*extern "C" {
#include <yei_skeletal_api.h>
}*/
#endif
/// Handles interaction with the LeapMotionManager skeleton tracking suit.
class LeapMotionManager : public QObject {
Q_OBJECT
public:
LeapMotionManager();
virtual ~LeapMotionManager();
bool isActive() const { return !_hands.isEmpty(); }
int nbHands() const { return _hands.size(); }
glm::vec3 getHandPos( unsigned int handNb ) const;
const QVector<int>& getHumanIKJointIndices() const { return _humanIKJointIndices; }
const QVector<glm::quat>& getJointRotations() const { return _jointRotations; }
void update(float deltaTime);
void reset();
private slots:
void renderCalibrationCountdown();
private:
#ifdef HAVE_LEAPMOTION
class SampleListener : public ::Leap::Listener
{
public:
SampleListener();
virtual ~SampleListener();
virtual void onConnect(const ::Leap::Controller &);
virtual void onDisconnect(const ::Leap::Controller &);
virtual void onExit(const ::Leap::Controller &);
virtual void onFocusGained(const ::Leap::Controller &);
virtual void onFocusLost(const ::Leap::Controller &);
virtual void onFrame(const ::Leap::Controller &);
virtual void onInit(const ::Leap::Controller &);
virtual void onServiceConnect(const ::Leap::Controller &);
virtual void onServiceDisconnect(const ::Leap::Controller &);
};
SampleListener _listener;
Leap::Controller _controller;
#endif
glm::vec3 _leapBasePos;
glm::quat _leapBaseOri;
QVector<glm::vec3> _hands;
QVector<int> _humanIKJointIndices;
QVector<glm::quat> _jointRotations;
QVector<glm::quat> _lastJointRotations;
QDateTime _calibrationCountdownStarted;
};
#endif // hifi_LeapMotionManager_h