From fe037431638d067887c7e3fd851409d8fd07e2d4 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 11 Jun 2014 00:44:33 -0700 Subject: [PATCH] Revert "Revert "Add LeapMotion SDK support"" This reverts commit 8ce145085b3783782fafd051becbb0e339d5dfa1. --- cmake/modules/FindLeapMotion.cmake | 44 ++ interface/CMakeLists.txt | 9 + interface/external/leapmotion/readme.txt | 27 ++ interface/src/devices/LeapMotionManager.cpp | 441 ++++++++++++++++++++ interface/src/devices/LeapMotionManager.h | 88 ++++ 5 files changed, 609 insertions(+) create mode 100644 cmake/modules/FindLeapMotion.cmake create mode 100644 interface/external/leapmotion/readme.txt create mode 100644 interface/src/devices/LeapMotionManager.cpp create mode 100644 interface/src/devices/LeapMotionManager.h diff --git a/cmake/modules/FindLeapMotion.cmake b/cmake/modules/FindLeapMotion.cmake new file mode 100644 index 0000000000..d1c0c81773 --- /dev/null +++ b/cmake/modules/FindLeapMotion.cmake @@ -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) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index cf203c41d9..44fd8fd5d1 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -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) diff --git a/interface/external/leapmotion/readme.txt b/interface/external/leapmotion/readme.txt new file mode 100644 index 0000000000..3abad9d7b1 --- /dev/null +++ b/interface/external/leapmotion/readme.txt @@ -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. \ No newline at end of file diff --git a/interface/src/devices/LeapMotionManager.cpp b/interface/src/devices/LeapMotionManager.cpp new file mode 100644 index 0000000000..5b5e724b1e --- /dev/null +++ b/interface/src/devices/LeapMotionManager.cpp @@ -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 +#include + +#include + +#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 +} diff --git a/interface/src/devices/LeapMotionManager.h b/interface/src/devices/LeapMotionManager.h new file mode 100644 index 0000000000..2aaddc555e --- /dev/null +++ b/interface/src/devices/LeapMotionManager.h @@ -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 +#include +#include + +#include + +#ifdef HAVE_LEAPMOTION +#include +/*extern "C" { +#include +}*/ +#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& getHumanIKJointIndices() const { return _humanIKJointIndices; } + const QVector& 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 _hands; + + QVector _humanIKJointIndices; + QVector _jointRotations; + QVector _lastJointRotations; + + QDateTime _calibrationCountdownStarted; +}; + +#endif // hifi_LeapMotionManager_h