├── drivers └── driver_leap │ ├── pch.cpp │ ├── pch.h │ ├── targetver.h │ ├── dllmain.cpp │ ├── driver_leap.vcxproj.filters │ ├── GestureMatcher.h │ ├── driver_leap.h │ ├── GestureMatcher.cpp │ ├── driver_leap.vcxproj │ └── driver_leap.cpp ├── tools ├── leap_monitor │ ├── small.ico │ ├── leap_monitor.ico │ ├── leap_monitor.rc │ ├── leap_monitor.h │ ├── stdafx.cpp │ ├── targetver.h │ ├── stdafx.h │ ├── Resource.h │ ├── leap_monitor.vcxproj.filters │ ├── leap_monitor.vcxproj │ └── leap_monitor.cpp ├── leap_installer │ └── leap.vrsettings ├── config_tool │ ├── config_tool.vcxproj.filters │ ├── config_tool.vcxproj │ └── config_tool.cpp └── gesture_checker │ ├── gesture_checker.vcxproj.filters │ ├── gesture_checker.cpp │ └── gesture_checker.vcxproj ├── .gitignore ├── Paths.props ├── LICENSE ├── driver_leap.sln └── README.md /drivers/driver_leap/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /tools/leap_monitor/small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbuchner1/driver_leap/HEAD/tools/leap_monitor/small.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Debug 2 | Release 3 | *.user 4 | ipch 5 | *.sdf 6 | .vs 7 | *.opensdf 8 | *.opendb 9 | *.VC.db 10 | -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbuchner1/driver_leap/HEAD/tools/leap_monitor/leap_monitor.ico -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbuchner1/driver_leap/HEAD/tools/leap_monitor/leap_monitor.rc -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //========= Copyright Valve Corporation ============// 4 | 5 | #include "resource.h" 6 | -------------------------------------------------------------------------------- /drivers/driver_leap/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "targetver.h" 4 | 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif 8 | 9 | #define NOMINMAX 10 | #include 11 | -------------------------------------------------------------------------------- /tools/leap_monitor/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // leap_monitor.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /tools/leap_monitor/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /drivers/driver_leap/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /drivers/driver_leap/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // dllmain.cpp : Defines the entry point for the DLL application. 2 | #include "pch.h" 3 | 4 | BOOL APIENTRY DllMain(HMODULE /* hModule */, DWORD ul_reason_for_call, LPVOID /* lpReserved */) 5 | { 6 | switch (ul_reason_for_call) 7 | { 8 | case DLL_PROCESS_ATTACH: 9 | case DLL_THREAD_ATTACH: 10 | case DLL_THREAD_DETACH: 11 | case DLL_PROCESS_DETACH: 12 | break; 13 | } 14 | return TRUE; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tools/leap_monitor/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 11 | // Windows Header Files: 12 | #include 13 | 14 | // C RunTime Header Files 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | // TODO: reference additional headers your program requires here 22 | -------------------------------------------------------------------------------- /tools/leap_monitor/Resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by leap_monitor.rc 4 | // 5 | 6 | #define IDS_APP_TITLE 103 7 | 8 | #define IDR_MAINFRAME 128 9 | #define IDD_LEAP_MONITOR_DIALOG 102 10 | #define IDD_ABOUTBOX 103 11 | #define IDM_ABOUT 104 12 | #define IDM_EXIT 105 13 | #define IDI_LEAP_MONITOR 107 14 | #define IDI_SMALL 108 15 | #define IDC_LEAP_MONITOR 109 16 | #define IDC_MYICON 2 17 | #ifndef IDC_STATIC 18 | #define IDC_STATIC -1 19 | #endif 20 | // Next default values for new objects 21 | // 22 | #ifdef APSTUDIO_INVOKED 23 | #ifndef APSTUDIO_READONLY_SYMBOLS 24 | 25 | #define _APS_NO_MFC 130 26 | #define _APS_NEXT_RESOURCE_VALUE 129 27 | #define _APS_NEXT_COMMAND_VALUE 32771 28 | #define _APS_NEXT_CONTROL_VALUE 1000 29 | #define _APS_NEXT_SYMED_VALUE 110 30 | #endif 31 | #endif 32 | -------------------------------------------------------------------------------- /tools/leap_installer/leap.vrsettings: -------------------------------------------------------------------------------- 1 | { 2 | "leap" : { 3 | "gripAngleOffset_lefthand" : 0, 4 | "gripAngleOffset_righthand" : 0, 5 | "renderModel_lefthand" : "vr_controller_vive_1_5", 6 | "renderModel_righthand" : "vr_controller_vive_1_5" 7 | }, 8 | "leap_gestures" : { 9 | "GrabPressed" : "GestureWithin:LowerFist[0.5,1.0]", 10 | "MenuPressed" : "GestureWithin:FlatHandPalmTowards[0.5,1.0]", 11 | "SystemPressed" : "GestureWithin:FlatHandPalmAway[0.5,1.0]", 12 | "TouchpadAxis[0]" : "GestureAxis:ThumbPositionX[-1.0,-1.0]", 13 | "TouchpadAxis[1]" : "GestureAxis:ThumbPositionY[-1.0,-1.0]", 14 | "TouchpadPressed" : "GestureWithin:Thumbpress[0.5,1.0]", 15 | "TouchpadTouched" : "GestureWithin:Thumbpress[0.0,0.5]", 16 | "TriggerAxis[0]" : "GestureAxis:TriggerFinger[0.0,1.0]", 17 | "TriggerAxis[1]" : 0, 18 | "TriggerPressed" : "GestureWithin:TriggerFinger[0.5,1.0]" 19 | }, 20 | "steamvr" : { 21 | "activateMultipleDrivers" : true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tools/config_tool/config_tool.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /drivers/driver_leap/driver_leap.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 6 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Resource Files 24 | 25 | 26 | -------------------------------------------------------------------------------- /Paths.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | D:\Steam\steamapps\common\SteamVR 6 | C:\Users\Christian Buchner\Documents\Visual Studio 2015\Projects\Leap\openvr-1.0.0 7 | C:\Users\Christian Buchner\Documents\Visual Studio 2015\Projects\Leap\LeapSDK 8 | D:\Steam\steamapps\common\SteamVR\drivers\leap 9 | 10 | 11 | 12 | 13 | 14 | $(SteamVRRuntimeDir) 15 | 16 | 17 | $(OpenVRDir) 18 | 19 | 20 | $(LeapSDKDir) 21 | 22 | 23 | $(InstallDir) 24 | 25 | 26 | -------------------------------------------------------------------------------- /tools/gesture_checker/gesture_checker.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Valve Corporation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | 40 | 41 | Resource Files 42 | 43 | 44 | 45 | 46 | Resource Files 47 | 48 | 49 | Resource Files 50 | 51 | 52 | -------------------------------------------------------------------------------- /driver_leap.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driver_leap", "drivers\driver_leap\driver_leap.vcxproj", "{52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "leap_monitor", "tools\leap_monitor\leap_monitor.vcxproj", "{BC06AF9C-36D6-455A-B421-00A9635684AD}" 9 | EndProject 10 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "leap_installer", "tools\leap_installer\leap_installer.vdproj", "{60517323-2772-4341-9161-56C776DC1840}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gesture_checker", "tools\gesture_checker\gesture_checker.vcxproj", "{9C28E205-C4CD-43D4-91BC-4852D7A588EC}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "config_tool", "tools\config_tool\config_tool.vcxproj", "{FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|x64 = Debug|x64 19 | Debug|x86 = Debug|x86 20 | Release|x64 = Release|x64 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Debug|x64.ActiveCfg = Debug|x64 25 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Debug|x64.Build.0 = Debug|x64 26 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Debug|x86.ActiveCfg = Debug|Win32 27 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Debug|x86.Build.0 = Debug|Win32 28 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Release|x64.ActiveCfg = Release|x64 29 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Release|x64.Build.0 = Release|x64 30 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Release|x86.ActiveCfg = Release|Win32 31 | {52D3F16D-A7A5-4D6F-8F17-5E4B459A1440}.Release|x86.Build.0 = Release|Win32 32 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Debug|x64.ActiveCfg = Debug|x64 33 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Debug|x64.Build.0 = Debug|x64 34 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Debug|x86.ActiveCfg = Debug|Win32 35 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Debug|x86.Build.0 = Debug|Win32 36 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Release|x64.ActiveCfg = Release|x64 37 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Release|x64.Build.0 = Release|x64 38 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Release|x86.ActiveCfg = Release|Win32 39 | {BC06AF9C-36D6-455A-B421-00A9635684AD}.Release|x86.Build.0 = Release|Win32 40 | {60517323-2772-4341-9161-56C776DC1840}.Debug|x64.ActiveCfg = Debug 41 | {60517323-2772-4341-9161-56C776DC1840}.Debug|x64.Build.0 = Debug 42 | {60517323-2772-4341-9161-56C776DC1840}.Debug|x86.ActiveCfg = Debug 43 | {60517323-2772-4341-9161-56C776DC1840}.Debug|x86.Build.0 = Debug 44 | {60517323-2772-4341-9161-56C776DC1840}.Release|x64.ActiveCfg = Release 45 | {60517323-2772-4341-9161-56C776DC1840}.Release|x64.Build.0 = Release 46 | {60517323-2772-4341-9161-56C776DC1840}.Release|x86.ActiveCfg = Release 47 | {60517323-2772-4341-9161-56C776DC1840}.Release|x86.Build.0 = Release 48 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Debug|x64.ActiveCfg = Debug|x64 49 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Debug|x64.Build.0 = Debug|x64 50 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Debug|x86.ActiveCfg = Debug|Win32 51 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Debug|x86.Build.0 = Debug|Win32 52 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Release|x64.ActiveCfg = Release|x64 53 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Release|x64.Build.0 = Release|x64 54 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Release|x86.ActiveCfg = Release|Win32 55 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC}.Release|x86.Build.0 = Release|Win32 56 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Debug|x64.ActiveCfg = Debug|x64 57 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Debug|x64.Build.0 = Debug|x64 58 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Debug|x86.ActiveCfg = Debug|Win32 59 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Debug|x86.Build.0 = Debug|Win32 60 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Release|x64.ActiveCfg = Release|x64 61 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Release|x64.Build.0 = Release|x64 62 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Release|x86.ActiveCfg = Release|Win32 63 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1}.Release|x86.Build.0 = Release|Win32 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | EndGlobal 69 | -------------------------------------------------------------------------------- /drivers/driver_leap/GestureMatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Leap.h" 4 | 5 | #include 6 | 7 | using namespace Leap; 8 | 9 | /** 10 | * Hand gesture recognizer that will return an array of matches against 11 | * a list of predefined finger and hand poses. 12 | */ 13 | class GestureMatcher 14 | { 15 | public: 16 | GestureMatcher(); 17 | ~GestureMatcher(); 18 | 19 | enum WhichHand 20 | { 21 | AnyHand, 22 | LeftHand, 23 | RightHand 24 | }; 25 | 26 | enum GestureType 27 | { 28 | // Finger gestures (these would not throw your hand's orientation off much) 29 | TriggerFinger, // bend your index finger as if pulling a trigger 30 | LowerFist, // grab with your middle, ring, pinky fingers 31 | Pinch, // pinch with your thumb and index fingers 32 | Thumbpress, // point the thumb towards the direction of your pinky 33 | 34 | // Hand gestures (these would significantly change the orientation of your hand) 35 | FlippingTheBird, // flip someone off with your middle finger 36 | ILY, // pinky and index finger extended, middle and ring bent 37 | Victory, // V shape with your index, middle fingers, other fingers curled 38 | FlatHandPalmUp, // flat hand, palm points upwards (relative to alignment of Leap!) 39 | FlatHandPalmDown, // flat hand, palm points downwards (relative to alignment of Leap!) 40 | FlatHandPalmAway, // flat hand, palm points away from self (relative to alignment of Leap!) 41 | FlatHandPalmTowards, // flat hand, palm points towards self (relative to alignment of Leap!) 42 | ThumbUp, // thumb points up, remaining fingers form a fist 43 | ThumbInward, // thumb points towards the left for the right hand and vice versa 44 | 45 | TODO_LiveLongAndProsper, // Spock's trademark greeting 46 | TODO_DiverOkay, // Diver's "I'm Okay" sign. 47 | TODO_FistBump, // a Fist Bump gesture 48 | 49 | // Two handed gestures 50 | Timeout, // both Hands form a T shape, signals a Timeout in sports 51 | TouchpadAxisX, // Touchpad emulation: index finger of other hand points towards palm 52 | TouchpadAxisY, // Touchpad emulation: index finger of other hand points towards palm 53 | 54 | NUM_GESTURES, 55 | 56 | INVALID_GESTURE = -1 57 | }; 58 | 59 | // default orientation vectors with respect to the Leap's coordinate system 60 | // in Head Mounted Mode. 61 | static const Vector RightVector; 62 | static const Vector InVector; 63 | static const Vector UpVector; 64 | 65 | /** 66 | * Perform gesture detection and quantification for the specified hand. 67 | * If AnyHand is specified, the gesture classifications will be merged together (typically std::max) 68 | */ 69 | bool MatchGestures(const Frame &frame, WhichHand which, float (&result)[NUM_GESTURES], 70 | Vector right = RightVector, Vector in = InVector, Vector up = UpVector); 71 | 72 | /** 73 | * Map the GestureType enum to a string name. 74 | */ 75 | static std::string GestureNameFromType(GestureType gesture) 76 | { 77 | switch (gesture) 78 | { 79 | case TriggerFinger: return "TriggerFinger"; break; 80 | case LowerFist: return "LowerFist"; break; 81 | case Pinch: return "Pinch"; break; 82 | case Thumbpress: return "Thumbpress"; break; 83 | case ILY: return "ILY"; break; 84 | case FlippingTheBird: return "FlippingTheBird"; break; 85 | case Victory: return "Victory"; break; 86 | case FlatHandPalmUp: return "FlatHandPalmUp"; break; 87 | case FlatHandPalmDown: return "FlatHandPalmDown"; break; 88 | case FlatHandPalmAway: return "FlatHandPalmAway"; break; 89 | case FlatHandPalmTowards: return "FlatHandPalmTowards"; break; 90 | case ThumbUp: return "ThumbUp"; break; 91 | case ThumbInward: return "ThumbInward"; break; 92 | case Timeout: return "Timeout"; break; 93 | case TouchpadAxisX: return "TouchpadAxisX"; break; 94 | case TouchpadAxisY: return "TouchpadAxisY"; break; 95 | default: return ""; break; 96 | } 97 | } 98 | 99 | /** 100 | * Map a string name to the GestureType enum. Case Sensitive! 101 | * Be sure to check the return code for INVALID_GESTURE 102 | */ 103 | GestureType GestureTypeFromName(std::string &name) 104 | { 105 | if (name.compare("TriggerFinger") == 0) return TriggerFinger; 106 | else if (name.compare("LowerFist") == 0) return LowerFist; 107 | else if (name.compare("Pinch") == 0) return Pinch; 108 | else if (name.compare("Thumbpress") == 0) return Thumbpress; 109 | else if (name.compare("ILY") == 0) return ILY; 110 | else if (name.compare("FlippingTheBird") == 0) return FlippingTheBird; 111 | else if (name.compare("Victory") == 0) return Victory; 112 | else if (name.compare("FlatHandPalmUp") == 0) return FlatHandPalmUp; 113 | else if (name.compare("FlatHandPalmDown") == 0) return FlatHandPalmDown; 114 | else if (name.compare("FlatHandPalmAway") == 0) return FlatHandPalmAway; 115 | else if (name.compare("FlatHandPalmTowards") == 0) return FlatHandPalmTowards; 116 | else if (name.compare("ThumbUp") == 0) return ThumbUp; 117 | else if (name.compare("ThumbInward") == 0) return ThumbInward; 118 | else if (name.compare("Timeout") == 0) return Timeout; 119 | else if (name.compare("TouchpadAxisX") == 0) return TouchpadAxisX; 120 | else if (name.compare("TouchpadAxisY") == 0) return TouchpadAxisY; 121 | else return INVALID_GESTURE; 122 | } 123 | 124 | protected: 125 | 126 | // some utility functions 127 | 128 | float maprange(float input, float minimum, float maximum) 129 | { 130 | float mapped = (input - minimum) / (maximum - minimum); 131 | return std::max(std::min(mapped, 1.0f), 0.0f); 132 | } 133 | 134 | void merge(float &result, float value) 135 | { 136 | result = std::max(result, value); 137 | } 138 | 139 | protected: 140 | 141 | // place state variables here (if any) 142 | }; 143 | -------------------------------------------------------------------------------- /drivers/driver_leap/driver_leap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //========= Copyright Valve Corporation ============// 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "Leap.h" 14 | #include "GestureMatcher.h" 15 | 16 | using namespace Leap; 17 | 18 | class CLeapHmdLatest; 19 | 20 | class CServerDriver_Leap : public vr::IServerTrackedDeviceProvider, public Listener 21 | { 22 | public: 23 | CServerDriver_Leap(); 24 | virtual ~CServerDriver_Leap(); 25 | 26 | // Inherited via IServerTrackedDeviceProvider 27 | virtual vr::EVRInitError Init( vr::IDriverLog * pDriverLog, vr::IServerDriverHost * pDriverHost, const char * pchUserDriverConfigDir, const char * pchDriverInstallDir ) override; 28 | virtual void Cleanup() override; 29 | virtual uint32_t GetTrackedDeviceCount() override; 30 | virtual vr::ITrackedDeviceServerDriver * GetTrackedDeviceDriver( uint32_t unWhich ) override; 31 | virtual vr::ITrackedDeviceServerDriver * FindTrackedDeviceDriver( const char * pchId ) override; 32 | virtual const char * const *GetInterfaceVersions() { return vr::k_InterfaceVersions; } 33 | virtual void RunFrame() override; 34 | 35 | virtual bool ShouldBlockStandbyMode() override; 36 | virtual void EnterStandby() override; 37 | virtual void LeaveStandby() override; 38 | 39 | void LaunchLeapMonitor(); 40 | 41 | // Leap::Listener interface 42 | void onInit(const Controller&); 43 | void onConnect(const Controller&); 44 | void onDisconnect(const Controller&); 45 | void onExit(const Controller&); 46 | void onFrame(const Controller&); 47 | void onFocusGained(const Controller&); 48 | void onFocusLost(const Controller&); 49 | void onServiceConnect(const Controller&); 50 | void onServiceDisconnect(const Controller&); 51 | void onDeviceChange(const Controller&); 52 | void onImages(const Controller&); 53 | void onServiceChange(const Controller&); 54 | void onDeviceFailure(const Controller&); 55 | void onLogMessage(const Controller&, MessageSeverity severity, int64_t timestamp, const char* msg); 56 | 57 | 58 | private: 59 | void ScanForNewControllers( bool bNotifyServer ); 60 | 61 | void LaunchLeapMonitor( const char * pchDriverInstallDir ); 62 | 63 | vr::IServerDriverHost* m_pDriverHost; 64 | std::string m_strDriverInstallDir; 65 | 66 | bool m_bLaunchedLeapMonitor; 67 | PROCESS_INFORMATION m_pInfoStartedProcess; 68 | 69 | // SteamVR's tracked controller objects 70 | std::vector< CLeapHmdLatest * > m_vecControllers; 71 | 72 | // Leap Motion's Controller object 73 | Controller *m_Controller; 74 | 75 | // a mutex for thread safety (Leap::Listener callbacks arrive from different threads) 76 | // std::recursive_mutex m_Mutex; 77 | // typedef std::lock_guard scope_lock; 78 | }; 79 | 80 | class CClientDriver_Leap : public vr::IClientTrackedDeviceProvider 81 | { 82 | public: 83 | CClientDriver_Leap(); 84 | virtual ~CClientDriver_Leap(); 85 | 86 | // Inherited via IClientTrackedDeviceProvider 87 | virtual vr::EVRInitError Init( vr::IDriverLog * pDriverLog, vr::IClientDriverHost * pDriverHost, const char * pchUserDriverConfigDir, const char * pchDriverInstallDir ) override; 88 | virtual void Cleanup() override; 89 | virtual bool BIsHmdPresent( const char * pchUserConfigDir ) override; 90 | virtual vr::EVRInitError SetDisplayId( const char * pchDisplayId ) override; 91 | virtual vr::HiddenAreaMesh_t GetHiddenAreaMesh( vr::EVREye eEye ) override; 92 | virtual uint32_t GetMCImage( uint32_t *pImgWidth, uint32_t *pImgHeight, uint32_t *pChannels, void *pDataBuffer, uint32_t unBufferLen ) override; 93 | 94 | private: 95 | vr::IClientDriverHost* m_pDriverHost; 96 | 97 | }; 98 | 99 | class CLeapHmdLatest : public vr::ITrackedDeviceServerDriver, public vr::IVRControllerComponent 100 | { 101 | public: 102 | CLeapHmdLatest( vr::IServerDriverHost * pDriverHost, int base, int n ); 103 | virtual ~CLeapHmdLatest(); 104 | 105 | // Implementation of vr::ITrackedDeviceServerDriver 106 | virtual vr::EVRInitError Activate( uint32_t unObjectId ) override; 107 | virtual void Deactivate() override; 108 | virtual void PowerOff() override; 109 | void *GetComponent( const char *pchComponentNameAndVersion ) override; 110 | virtual void DebugRequest( const char * pchRequest, char * pchResponseBuffer, uint32_t unResponseBufferSize ) override; 111 | virtual vr::DriverPose_t GetPose() override; 112 | virtual bool GetBoolTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) override; 113 | virtual float GetFloatTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) override; 114 | virtual int32_t GetInt32TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) override; 115 | virtual uint64_t GetUint64TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) override; 116 | virtual vr::HmdMatrix34_t GetMatrix34TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError *pError ) override; 117 | virtual uint32_t GetStringTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, char * pchValue, uint32_t unBufferSize, vr::ETrackedPropertyError * pError ) override; 118 | 119 | // Implementation of vr::IVRControllerComponent 120 | virtual vr::VRControllerState_t GetControllerState() override; 121 | virtual bool TriggerHapticPulse( uint32_t unAxisId, uint16_t usPulseDurationMicroseconds ) override; 122 | 123 | bool IsActivated() const; 124 | bool HasControllerId( int nBase, int nId ); 125 | bool Update(Frame &frame); 126 | const char *GetSerialNumber(); 127 | 128 | static void RealignCoordinates( CLeapHmdLatest * pLeapA, CLeapHmdLatest * pLeapB ); 129 | void FinishRealignCoordinates( float (*m)[3], float *v ); 130 | void UpdateHmdPose(float *v, vr::HmdQuaternion_t q); 131 | 132 | uint32_t GetDeviceId() { return m_unSteamVRTrackedDeviceId; } 133 | 134 | private: 135 | static const std::chrono::milliseconds k_TrackingLatency; 136 | 137 | typedef void ( vr::IServerDriverHost::*ButtonUpdate )( uint32_t unWhichDevice, vr::EVRButtonId eButtonId, double eventTimeOffset ); 138 | 139 | void SendButtonUpdates( ButtonUpdate ButtonEvent, uint64_t ulMask ); 140 | void UpdateControllerState(Frame &frame); 141 | void UpdateTrackingState(Frame &frame); 142 | 143 | // Handle for calling back into vrserver with events and updates 144 | vr::IServerDriverHost *m_pDriverHost; 145 | 146 | // Which Leap controller 147 | int m_nBase; 148 | int m_nId; 149 | std::string m_strSerialNumber; 150 | 151 | // To main structures for passing state to vrserver 152 | vr::DriverPose_t m_Pose; 153 | vr::VRControllerState_t m_ControllerState; 154 | 155 | // Ancillary tracking state 156 | bool m_bCalibrated; 157 | float m_hmdPos[3]; 158 | vr::HmdQuaternion_t m_hmdRot; 159 | 160 | // Other controller with from the last realignment 161 | CLeapHmdLatest *m_pAlignmentPartner; 162 | 163 | // Cached for answering version queries from vrserver 164 | unsigned short m_firmware_revision; 165 | unsigned short m_hardware_revision; 166 | 167 | // Assigned by vrserver upon Activate(). The same ID visible to clients 168 | uint32_t m_unSteamVRTrackedDeviceId; 169 | 170 | // The rendermodel used by the device. Check the contents of "c:\Program Files (x86)\Steam\steamapps\common\SteamVR\resources\rendermodels" for available models. 171 | std::string m_strRenderModel; 172 | 173 | // with this angle offset you can emulate the angle of a gun grip. Positive values tilt the controller up by N degrees. 174 | float m_gripAngleOffset; 175 | 176 | // a helper object to identify hand poses 177 | GestureMatcher matcher; 178 | }; -------------------------------------------------------------------------------- /tools/gesture_checker/gesture_checker.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************\ 2 | * Copyright (C) 2012-2016 Leap Motion, Inc. All rights reserved. * 3 | * Leap Motion proprietary and confidential. Not for distribution. * 4 | * Use subject to the terms of the Leap Motion SDK Agreement available at * 5 | * https://developer.leapmotion.com/sdk_agreement, or another agreement * 6 | * between Leap Motion and you, your company or other organization. * 7 | \******************************************************************************/ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "GestureMatcher.h" 13 | 14 | using namespace Leap; 15 | 16 | class SampleListener : public Listener { 17 | public: 18 | virtual void onInit(const Controller&); 19 | virtual void onConnect(const Controller&); 20 | virtual void onDisconnect(const Controller&); 21 | virtual void onExit(const Controller&); 22 | virtual void onFrame(const Controller&); 23 | virtual void onFocusGained(const Controller&); 24 | virtual void onFocusLost(const Controller&); 25 | virtual void onDeviceChange(const Controller&); 26 | virtual void onServiceConnect(const Controller&); 27 | virtual void onServiceDisconnect(const Controller&); 28 | virtual void onServiceChange(const Controller&); 29 | virtual void onDeviceFailure(const Controller&); 30 | virtual void onLogMessage(const Controller&, MessageSeverity severity, int64_t timestamp, const char* msg); 31 | }; 32 | 33 | const std::string fingerNames[] = { "Thumb", "Index", "Middle", "Ring", "Pinky" }; 34 | const std::string boneNames[] = { "Metacarpal", "Proximal", "Middle", "Distal" }; 35 | 36 | void SampleListener::onInit(const Controller& controller) { 37 | std::cout << "Initialized" << std::endl; 38 | } 39 | 40 | void SampleListener::onConnect(const Controller& controller) { 41 | std::cout << "Connected" << std::endl; 42 | } 43 | 44 | void SampleListener::onDisconnect(const Controller& controller) { 45 | // Note: not dispatched when running in a debugger. 46 | std::cout << "Disconnected" << std::endl; 47 | } 48 | 49 | void SampleListener::onExit(const Controller& controller) { 50 | std::cout << "Exited" << std::endl; 51 | } 52 | 53 | static float maprange(float input, float minimum, float maximum) 54 | { 55 | float mapped = (input - minimum) / (maximum - minimum); 56 | return std::max(std::min(mapped, 1.0f), 0.0f); 57 | } 58 | 59 | #include 60 | 61 | void cls(HANDLE hConsole) 62 | { 63 | COORD coordScreen = { 0, 0 }; // home for the cursor 64 | DWORD cCharsWritten; 65 | CONSOLE_SCREEN_BUFFER_INFO csbi; 66 | DWORD dwConSize; 67 | 68 | // Get the number of character cells in the current buffer. 69 | 70 | if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) 71 | { 72 | return; 73 | } 74 | 75 | dwConSize = csbi.dwSize.X * csbi.dwSize.Y; 76 | 77 | // Fill the entire screen with blanks. 78 | 79 | if (!FillConsoleOutputCharacter(hConsole, // Handle to console screen buffer 80 | (TCHAR) ' ', // Character to write to the buffer 81 | dwConSize, // Number of cells to write 82 | coordScreen, // Coordinates of first cell 83 | &cCharsWritten))// Receive number of characters written 84 | { 85 | return; 86 | } 87 | 88 | // Get the current text attribute. 89 | 90 | if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) 91 | { 92 | return; 93 | } 94 | 95 | // Set the buffer's attributes accordingly. 96 | 97 | if (!FillConsoleOutputAttribute(hConsole, // Handle to console screen buffer 98 | csbi.wAttributes, // Character attributes to use 99 | dwConSize, // Number of cells to set attribute 100 | coordScreen, // Coordinates of first cell 101 | &cCharsWritten)) // Receive number of characters written 102 | { 103 | return; 104 | } 105 | 106 | // Put the cursor at its home coordinates. 107 | 108 | SetConsoleCursorPosition(hConsole, coordScreen); 109 | } 110 | void SampleListener::onFrame(const Controller& controller) { 111 | // Get the most recent frame and report some basic information 112 | const Frame frame = controller.frame(); 113 | 114 | cls(GetStdHandle(STD_OUTPUT_HANDLE)); 115 | 116 | static GestureMatcher matcher; 117 | 118 | for (int i = 0; i < 2; i++) 119 | { 120 | float scores[GestureMatcher::NUM_GESTURES]; 121 | bool handFound = matcher.MatchGestures(frame, (GestureMatcher::WhichHand)(i + 1), scores); 122 | if (handFound) 123 | { 124 | for (int j = 0; j < GestureMatcher::NUM_GESTURES; j++) 125 | { 126 | std::string tmp = GestureMatcher::GestureNameFromType((GestureMatcher::GestureType)j); 127 | if (!tmp.empty()) 128 | fprintf(stderr, "%-10s %-30s - %4.2f\n", i ? "Right Hand" : "Left Hand", tmp.c_str(), scores[j]); 129 | } 130 | 131 | // Go through the hands in the dataset 132 | HandList &hands = frame.hands(); 133 | for (int h = 0; h < hands.count(); h++) 134 | { 135 | Hand &hand = hands[h]; 136 | } 137 | } 138 | } 139 | 140 | Sleep(50); 141 | } 142 | 143 | void SampleListener::onFocusGained(const Controller& controller) { 144 | std::cout << "Focus Gained" << std::endl; 145 | } 146 | 147 | void SampleListener::onFocusLost(const Controller& controller) { 148 | std::cout << "Focus Lost" << std::endl; 149 | } 150 | 151 | void SampleListener::onDeviceChange(const Controller& controller) { 152 | #if 0 153 | std::cout << "Device Changed" << std::endl; 154 | const DeviceList devices = controller.devices(); 155 | 156 | for (int i = 0; i < devices.count(); ++i) { 157 | std::cout << "id: " << devices[i].toString() << std::endl; 158 | std::cout << " isStreaming: " << (devices[i].isStreaming() ? "true" : "false") << std::endl; 159 | std::cout << " isSmudged:" << (devices[i].isSmudged() ? "true" : "false") << std::endl; 160 | std::cout << " isLightingBad:" << (devices[i].isLightingBad() ? "true" : "false") << std::endl; 161 | } 162 | #endif 163 | } 164 | 165 | void SampleListener::onServiceConnect(const Controller& controller) { 166 | std::cout << "Service Connected" << std::endl; 167 | } 168 | 169 | void SampleListener::onServiceDisconnect(const Controller& controller) { 170 | std::cout << "Service Disconnected" << std::endl; 171 | } 172 | 173 | void SampleListener::onServiceChange(const Controller& controller) { 174 | std::cout << "Service Changed" << std::endl; 175 | } 176 | 177 | void SampleListener::onDeviceFailure(const Controller& controller) { 178 | std::cout << "Device Error" << std::endl; 179 | const Leap::FailedDeviceList devices = controller.failedDevices(); 180 | 181 | for (FailedDeviceList::const_iterator dl = devices.begin(); dl != devices.end(); ++dl) { 182 | const FailedDevice device = *dl; 183 | std::cout << " PNP ID:" << device.pnpId(); 184 | std::cout << " Failure type:" << device.failure(); 185 | } 186 | } 187 | 188 | void SampleListener::onLogMessage(const Controller&, MessageSeverity s, int64_t t, const char* msg) { 189 | switch (s) { 190 | case Leap::MESSAGE_CRITICAL: 191 | std::cout << "[Critical]"; 192 | break; 193 | case Leap::MESSAGE_WARNING: 194 | std::cout << "[Warning]"; 195 | break; 196 | case Leap::MESSAGE_INFORMATION: 197 | std::cout << "[Info]"; 198 | break; 199 | case Leap::MESSAGE_UNKNOWN: 200 | std::cout << "[Unknown]"; 201 | } 202 | std::cout << "[" << t << "] "; 203 | std::cout << msg << std::endl; 204 | } 205 | 206 | int main(int argc, char** argv) { 207 | // Create a sample listener and controller 208 | SampleListener listener; 209 | Controller controller; 210 | 211 | // Have the sample listener receive events from the controller 212 | controller.addListener(listener); 213 | 214 | controller.setPolicy(Leap::Controller::POLICY_BACKGROUND_FRAMES); 215 | 216 | controller.setPolicy(Leap::Controller::POLICY_ALLOW_PAUSE_RESUME); 217 | 218 | // Keep this process running until Enter is pressed 219 | std::cout << "Press Enter to quit, or enter 'p' to pause or unpause the service..." << std::endl; 220 | 221 | bool paused = false; 222 | while (true) { 223 | char c = std::cin.get(); 224 | if (c == 'p') { 225 | paused = !paused; 226 | controller.setPaused(paused); 227 | std::cin.get(); //skip the newline 228 | } 229 | else 230 | break; 231 | } 232 | 233 | // Remove the sample listener when done 234 | controller.removeListener(listener); 235 | 236 | return 0; 237 | } 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leap Motion Driver for SteamVR 2 | 3 | ## Installation of this driver. 4 | 5 | - Get the Leap Motion Orion Beta runtimes https://developer.leapmotion.com/get-started 6 | - Install the Visual C++ 2015 Update 2 redistributables (32 and 64 bits) https://www.microsoft.com/en-us/download/details.aspx?id=51682 7 | - Download the most recent zip file from the Releases section of this project and run the contained exe setup program https://github.com/cbuchner1/driver_leap/releases 8 | 9 | Start SteamVR to see if two additional controllers show up (they should be blinking if your hands are 10 | not in the field of view of the Leap Motion, solid otherwise). 11 | 12 | ### Troubleshooting 13 | 14 | If you experience frequent crashes of SteamVR on exit and this bothers you, uninstall my driver. I will try to fix this ASAP, but at the moment I have no clue why SteamVR is crashing. 15 | 16 | 17 | ## Note about WORK IN PROGRESS 18 | 19 | You're seeing an early version of this software. I've got positional tracking established now as well as hand pose tracking. Some experimental mappings of hand gestures to triggers and buttons were added: 20 | 21 | Trigger: 22 | - bending of the index finger maps to the trigger button, like you would fire a gun. 23 | 24 | Grip: 25 | - clenching the middle, ring, pinky finger to a fist maps to the grabbing buttons 26 | 27 | Trackpad: 28 | - the thumbpress gesture (just point the thumb in the direction of your palm) touches and clicks the trackpad, depending on the intensity of your gesture. 29 | - pointing the index finger towards the are of the other hand's palm will emulate the touchpad. To press the touchpad in the desired position use the tumbpress gesture simultaneously while pointing. 30 | 31 | Menu buttons: 32 | - Flat hand held in front of you, palm towards face is used for application menu button 33 | - the Timeout pose (as used in sports), registers as the system menu button 34 | 35 | 36 | Swapped hands? 37 | 38 | when SteamVR confuses the left and right controller hands (which will be indicated by little hand icons shown near the bottom of the displayed Wand controllers), simply cross your hands the first time you bring them into view after starting SteamVR. This reverses the hand assignment and can improve the gaming experience for example in Audioshield. 39 | 40 | 41 | 42 | 43 | I am working on allowing to freely map gestures to buttons in the steamvr.vrsettings config file in your Steam\config folder. However note that the "leap_gestures" section is currently not parsed yet. It's merely a sign of things to come. 44 | 45 | ## Supported gestures 46 | 47 | There are other gestures detected currently, but not mapped to buttons. If you want to try these out, click on the application "gesture_checker.exe" in the directory C:\Program Files (x86)\SteamVR Leap Motion driver\leap\bin\Win32 48 | 49 | Then I also recommend that you simultaneously bring up your Leap Motion's settings and from there start the diagnostic visualizer (the windowed version, not the VR one). Press "v" once to switch it to headmount optimized mode. 50 | 51 | Now pull both windows side by side and bring a hand into view. The command prompt running the gesture_checker program should output a series of numbers next to the names of the gestures. A "1.0" means confidential detection, a "0.0" means no detection. 52 | 53 | You can practise some gestures this way and also cross-check your pose in the Leap Motion diagnostic visualizer against the detection confidence. 54 | 55 | // Finger gestures (these would not throw your hand's orientation off much) 56 | TriggerFinger, // bend your index finger as if pulling a trigger 57 | LowerFist, // grab with your middle, ring, pinky fingers 58 | Pinch, // pinch with your thumb and index fingers 59 | Thumbpress, // point the thumb towards the direction of your pinky 60 | 61 | // Hand gestures (these would significantly change the orientation of your hand) 62 | FlippingTheBird, // flip someone off with your middle finger 63 | ILY, // pinky and index finger extended, middle and ring bent 64 | Victory, // V shape with your index, middle fingers, other fingers curled 65 | FlatHandPalmUp, // flat hand, palm points upwards (relative to alignment of Leap!) 66 | FlatHandPalmDown, // flat hand, palm points downwards (relative to alignment of Leap!) 67 | FlatHandPalmAway, // flat hand, palm points away from self (relative to alignment of Leap!) 68 | FlatHandPalmTowards, // flat hand, palm points towards self (relative to alignment of Leap!) 69 | ThumbUp, // thumb points up, remaining fingers form a fist 70 | ThumbInward, // thumb points towards the left for the right hand and vice versa 71 | 72 | // Two handed gestures 73 | Timeout, // both Hands form a T shape, signals a Timeout in sports 74 | TouchpadAxisX, // Touchpad emulation: index finger of other hand points towards palm 75 | TouchpadAxisY, // Touchpad emulation: index finger of other hand points towards palm 76 | 77 | 78 | ### Games/Experiences that work mostly 79 | 80 | - the Blu (all three stages) 81 | - Irrational Exuberance: Prologue 82 | - the Rose and I 83 | - The Lab (some experiences work, others are tricky) 84 | - Final Approach 85 | - Audioshield: somehow the controllers are swapped? Control is tricky and not very precise. Semi-playable though. 86 | 87 | ### Games/Experiences that are starting but not quite playable yet. 88 | 89 | - Tilt Brush: starts and you can start doing things, but there is lack of complete trackpad support in my driver. 90 | - Brookhaven Experiment: tracking only works while SteamVR window is in focus. Why? Gun in right hand needs a 60 degree uptilt angle (define this in steamvr.vrsettings config file in Steam config folder). Trigger gesture detection is way to imprecise, you won't even survive the first wave of Zombies. 91 | 92 | 93 | ### Demos that won't work at all 94 | - n/a 95 | 96 | 97 | ## Known Issues 98 | 99 | I am seeing SteamVR Server crash on shutdown a lot. This could be related to my driver, but I have not yet found the root cause for the crash. 100 | 101 | The Brookhaven experiment seems to steal focus from StreamVR, so that Steam does not get any position tracking. Clicking on the SteamVR window restores tracking, but mutes the audio on Brookhaven. Meh. 102 | 103 | Some games work better when no grip angle is added to the controller pose, other games actually require a steep angle to be playable (Brookhaven, Audioshield). We may have to add a feature to chose the preferred default pose at runtime. 104 | 105 | Tracking is not quite reliable to always detect my trigger gestures. I think we will have to integrate small handheld controllers like the Wiimote or the Playstation Move Navigation controller in the future. 106 | 107 | I do not think I will be able to get animated hands into the 3D view, as the render model you can assign to each controller is mostly a static object. There are some JSON files to map joystick axes and triggers to animated parts of the displayed controller. But the fingers do not directly map to joystick axes directly and hence cannot be shown. Also not all games make use of SteamVR's internal controller visualization. 108 | 109 | 110 | ## Building from Sourcecode (Developers only) 111 | 112 | ### Install Dependencies 113 | 114 | 1. Install SteamVR. It is under "Tools" in everyone's Steam Library. steam://install/250820 115 | 2. Install "Leap Motion Orion SDK V3.1.2". https://developer.leapmotion.com/get-started 116 | 3. Fetch the OpenVR SDK 1.0.0 from https://github.com/ValveSoftware/openvr . 117 | 118 | The solution and project files are for Visual Studio 2015. 119 | 120 | ### Configure Paths 121 | 122 | Under "Property Manager" in Visual Studio, expand any of the configurations and find "Paths". Right click and select "Properties" and then "User Macros". Modify the paths to match your setup. InstallDir will be created, and will be configured as an external driver path for SteamVR. 123 | 124 | ### Build 125 | 126 | You will probably want to build Release x86. You can also build x64. The post-build step will install the binaries and copy the resources to the configured InstallDir and register that path with SteamVR. 127 | 128 | ## Preapring The Leap Motion Driver for use (Developers only) 129 | 130 | After building, the InstallDir should be a complete binary distribution. To use it: 131 | 132 | 1. Register it with the SteamVR runtime via "vrpathreg adddriver $(InstallDir)". This is done automatically by a Post-Build step, but if you copy the files elsewhere you will have to do it by hand. 133 | 2. Edit your config/steamvr.vrsettings to enable "activateMultipleDrivers". This is what allows the hydra driver to co-exist with any HMD. **Be sure to mind your commas.** Check vrserver.txt log to see if there were parse errors. Many of the settings are described at https://developer.valvesoftware.com/wiki/SteamVR/steamvr.vrsettings . 134 | ```{ 135 | ... 136 | "steamvr" : { 137 | "activateMultipleDrivers" : true 138 | } 139 | }``` 140 | 3. If you are trying to use the Hydra driver without an HMD, you might want to enable driver_null (no HMD) or set "requireHmd": false. 141 | 142 | After starting SteamVR, you should see controllers blinking in the status window until you move your hands into the field of view. 143 | 144 | You can use "vrcmd" (with no arguments) to see a list of devices to verify things are working. 145 | use "vrcmd" to verify things are loading: 146 | 147 | ```... 148 | Driver leap : 2 displays 149 | leap (Serial number leap0_lefthand) 150 | leap (Serial number leap0_righthand) 151 | ... 152 | ``` 153 | 154 | You can also use "vrcmd --pollposes" (followed by an index number to limit the output) to see if things are working. 155 | 156 | ## Licenses 157 | 158 | The code in this distribution is distributed under the terms of the LICENSE file in the root directory. 159 | 160 | The compiled driver and the install directory use the Leap Motion Orion SDK. Use subject to the terms of the Leap Motion SDK Agreement available at 161 | https://developer.leapmotion.com/sdk_agreement. 162 | -------------------------------------------------------------------------------- /tools/config_tool/config_tool.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {FD2D3DBF-C82D-4333-9710-7DD2C51FA8E1} 23 | Win32Proj 24 | config_tool 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | true 90 | 91 | 92 | true 93 | 94 | 95 | false 96 | 97 | 98 | false 99 | 100 | 101 | 102 | 103 | 104 | Level3 105 | Disabled 106 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 107 | ../../../rapidjson-1.0.2/include 108 | 109 | 110 | Console 111 | true 112 | RequireAdministrator 113 | 114 | 115 | mkdir "$(InstallDir)\bin\Win32" 116 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 117 | 118 | 119 | 120 | 121 | 122 | 123 | Level3 124 | Disabled 125 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 126 | ../../../rapidjson-1.0.2/include 127 | 128 | 129 | Console 130 | true 131 | RequireAdministrator 132 | 133 | 134 | mkdir "$(InstallDir)\bin\Win64" 135 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 136 | 137 | 138 | 139 | 140 | Level3 141 | 142 | 143 | MaxSpeed 144 | true 145 | true 146 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 147 | ../../../rapidjson-1.0.2/include 148 | 149 | 150 | Console 151 | true 152 | true 153 | true 154 | RequireAdministrator 155 | 156 | 157 | mkdir "$(InstallDir)\bin\Win32" 158 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 159 | 160 | 161 | 162 | 163 | Level3 164 | 165 | 166 | MaxSpeed 167 | true 168 | true 169 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 170 | ../../../rapidjson-1.0.2/include 171 | 172 | 173 | Console 174 | true 175 | true 176 | true 177 | RequireAdministrator 178 | 179 | 180 | mkdir "$(InstallDir)\bin\Win64" 181 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /tools/gesture_checker/gesture_checker.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {9C28E205-C4CD-43D4-91BC-4852D7A588EC} 23 | Win32Proj 24 | gesture_checker 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | false 87 | 88 | 89 | 90 | 91 | 92 | Level3 93 | Disabled 94 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 95 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;..\..\drivers\driver_leap;%(AdditionalIncludeDirectories) 96 | 97 | 98 | Console 99 | true 100 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x86;%(AdditionalLibraryDirectories) 101 | Leap.lib;%(AdditionalDependencies) 102 | 103 | 104 | mkdir "$(InstallDir)\bin\Win32" 105 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(TargetDir)" 106 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 107 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(InstallDir)\bin\Win32" 108 | 109 | 110 | 111 | 112 | 113 | 114 | Level3 115 | Disabled 116 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 117 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;..\..\drivers\driver_leap;%(AdditionalIncludeDirectories) 118 | 119 | 120 | Console 121 | true 122 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x64;%(AdditionalLibraryDirectories) 123 | Leap.lib;%(AdditionalDependencies) 124 | 125 | 126 | mkdir "$(InstallDir)\bin\Win64" 127 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(TargetDir)" 128 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 129 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(InstallDir)\bin\Win64" 130 | 131 | 132 | 133 | 134 | Level3 135 | 136 | 137 | MaxSpeed 138 | true 139 | true 140 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 141 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;..\..\drivers\driver_leap;%(AdditionalIncludeDirectories) 142 | 143 | 144 | Console 145 | true 146 | true 147 | true 148 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x86;%(AdditionalLibraryDirectories) 149 | Leap.lib;%(AdditionalDependencies) 150 | 151 | 152 | mkdir "$(InstallDir)\bin\Win32" 153 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(TargetDir)" 154 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 155 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(InstallDir)\bin\Win32" 156 | 157 | 158 | 159 | 160 | Level3 161 | 162 | 163 | MaxSpeed 164 | true 165 | true 166 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 167 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;..\..\drivers\driver_leap;%(AdditionalIncludeDirectories) 168 | 169 | 170 | Console 171 | true 172 | true 173 | true 174 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x64;%(AdditionalLibraryDirectories) 175 | Leap.lib;%(AdditionalDependencies) 176 | 177 | 178 | mkdir "$(InstallDir)\bin\Win64" 179 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(TargetDir)" 180 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 181 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(InstallDir)\bin\Win64" 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {BC06AF9C-36D6-455A-B421-00A9635684AD} 23 | Win32Proj 24 | leap_monitor 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | false 87 | 88 | 89 | 90 | Use 91 | Level3 92 | Disabled 93 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 94 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 95 | 96 | 97 | Windows 98 | true 99 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x86;%(AdditionalLibraryDirectories) 100 | openvr_api.lib;%(AdditionalDependencies) 101 | 102 | 103 | mkdir "$(InstallDir)\bin\Win32" 104 | copy "$(OpenVRDir)\bin\Win32\openvr_api.dll" "$(TargetDir)" 105 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 106 | copy "$(OpenVRDir)\bin\Win32\openvr_api.dll" "$(InstallDir)\bin\Win32" 107 | 108 | 109 | 110 | 111 | Use 112 | Level3 113 | Disabled 114 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 115 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 116 | 117 | 118 | Windows 119 | true 120 | openvr_api.lib;%(AdditionalDependencies) 121 | $(OpenVRDir)\lib\win64;$(LeapSDKDir)\lib\x64;%(AdditionalLibraryDirectories) 122 | 123 | 124 | mkdir "$(InstallDir)\bin\Win64" 125 | copy "$(OpenVRDir)\bin\Win64\openvr_api.dll" "$(TargetDir)" 126 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 127 | copy "$(OpenVRDir)\bin\Win64\openvr_api.dll" "$(InstallDir)\bin\Win64" 128 | 129 | 130 | 131 | 132 | Level3 133 | Use 134 | MaxSpeed 135 | true 136 | true 137 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 138 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 139 | 140 | 141 | Windows 142 | true 143 | true 144 | true 145 | $(OpenVRDir)\lib\win32;$(LeapSDKDir)\lib\x86;%(AdditionalLibraryDirectories) 146 | openvr_api.lib;%(AdditionalDependencies) 147 | 148 | 149 | mkdir "$(InstallDir)\bin\Win32" 150 | copy "$(OpenVRDir)\bin\Win32\openvr_api.dll" "$(TargetDir)" 151 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 152 | copy "$(OpenVRDir)\bin\Win32\openvr_api.dll" "$(InstallDir)\bin\Win32" 153 | 154 | 155 | 156 | 157 | Level3 158 | NotUsing 159 | MaxSpeed 160 | true 161 | true 162 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 163 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 164 | stdafx.h 165 | 166 | 167 | Windows 168 | true 169 | true 170 | true 171 | openvr_api.lib;%(AdditionalDependencies) 172 | $(OpenVRDir)\lib\win64;$(LeapSDKDir)\lib\x64;%(AdditionalLibraryDirectories) 173 | 174 | 175 | mkdir "$(InstallDir)\bin\Win64" 176 | copy "$(OpenVRDir)\bin\Win64\openvr_api.dll" "$(TargetDir)" 177 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 178 | copy "$(OpenVRDir)\bin\Win64\openvr_api.dll" "$(InstallDir)\bin\Win64" 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Create 191 | Create 192 | Create 193 | Create 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /tools/leap_monitor/leap_monitor.cpp: -------------------------------------------------------------------------------- 1 | //========= Copyright Valve Corporation ============// 2 | // 3 | // leap_monitor.cpp : Interacts with driver_leap to provide overlay instructions at startup 4 | // 5 | 6 | #include "stdafx.h" 7 | #include "leap_monitor.h" 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | const std::chrono::milliseconds k_MonitorInterval( 10 ); 16 | 17 | class CLeapMonitor 18 | { 19 | public: 20 | CLeapMonitor( const std::string & path ) 21 | : m_strOverlayImagePath( path ) 22 | , m_OverlayHandle( vr::k_ulOverlayHandleInvalid ) 23 | , m_eCurrentOverlay( k_eNone ) 24 | {} 25 | 26 | ~CLeapMonitor() {} 27 | 28 | void Run() 29 | { 30 | if ( Init() ) 31 | { 32 | MainLoop(); 33 | } 34 | 35 | Shutdown(); 36 | } 37 | 38 | protected: 39 | 40 | enum EOverlayToDisplay { k_eNone, k_ePointAtBaseForHemisphereTracking, k_eHoldAtShouldersForCoordinateAlignment }; 41 | 42 | bool Init() 43 | { 44 | // Start as "background" application. This prevents vrserver from being started 45 | // on our behalf, and prevents us from keeping vrserver alive when everything else 46 | // exits. This is very important because we're spawning from a driver, and any 47 | // class besides "background" would keep vrserver running forever 48 | vr::EVRInitError eVRInitError; 49 | vr::VR_Init( &eVRInitError, vr::VRApplication_Background ); 50 | if ( !vr::VRSystem() || eVRInitError != vr::VRInitError_None ) 51 | return false; 52 | 53 | // Keep track of which devices use driver_leap 54 | for ( int i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i ) 55 | { 56 | UpdateTrackedDevice( i ); 57 | } 58 | 59 | return true; 60 | } 61 | 62 | void MainLoop() 63 | { 64 | while ( true ) 65 | { 66 | std::this_thread::sleep_for( k_MonitorInterval ); 67 | 68 | #if defined( WIN32 ) 69 | MSG msg = { 0 }; 70 | while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) 71 | { 72 | TranslateMessage( &msg ); 73 | DispatchMessage( &msg ); 74 | } 75 | 76 | if ( msg.message == WM_QUIT ) 77 | break; 78 | #endif 79 | 80 | // Display instructions for user if we find any devices that need them 81 | ShowOverlay( BestOverlayForLeapDevices() ); 82 | 83 | vr::VREvent_t Event; 84 | while ( vr::VRSystem()->PollNextEvent( &Event, sizeof( Event ) ) ) 85 | { 86 | switch ( Event.eventType ) 87 | { 88 | case vr::VREvent_Quit: 89 | exit( 0 ); 90 | // NOTREAHED 91 | 92 | case vr::VREvent_TrackedDeviceActivated: 93 | case vr::VREvent_TrackedDeviceUpdated: 94 | UpdateTrackedDevice( Event.trackedDeviceIndex ); 95 | break; 96 | 97 | case vr::VREvent_VendorSpecific_Reserved_Start + 0: 98 | // User has made the "align" gesture. The driver can't see the HMD 99 | // coordinates, so we forward those from our client view. 100 | if ( IsLeapDevice( Event.trackedDeviceIndex ) ) 101 | { 102 | // printf("received event vr::VREvent_VendorSpecific_Reserved_Start + 0 for device %d\n", Event.trackedDeviceIndex); 103 | TriggerRealignCoordinates( Event ); 104 | } 105 | break; 106 | } 107 | } 108 | } 109 | } 110 | 111 | /** Create and show an overlay nailed to the user's face */ 112 | bool ShowOverlay( EOverlayToDisplay eOverlay ) 113 | { 114 | if ( m_eCurrentOverlay == eOverlay && m_OverlayHandle != vr::k_ulOverlayHandleInvalid ) 115 | return true; 116 | 117 | // Hiding or changing, so destroy old overlay 118 | HideOverlay(); 119 | 120 | if ( eOverlay == k_eNone ) 121 | { 122 | m_eCurrentOverlay = eOverlay; 123 | return true; 124 | } 125 | 126 | // Compositor must be initialized to create overlays 127 | if ( !vr::VRCompositor() ) 128 | return false; 129 | 130 | vr::EVROverlayError eOverlayError = vr::VROverlay()->CreateOverlay( "leap_monitor", "Leap Monitor", &m_OverlayHandle ); 131 | if ( eOverlayError != vr::VROverlayError_None ) 132 | return false; 133 | 134 | vr::HmdMatrix34_t matInFrontOfHead; 135 | memset( &matInFrontOfHead, 0, sizeof( matInFrontOfHead ) ); 136 | float scale = 1.4f; 137 | matInFrontOfHead.m[0][0] = matInFrontOfHead.m[1][1] = matInFrontOfHead.m[2][2] = scale; 138 | matInFrontOfHead.m[2][3] = -2.0f; 139 | eOverlayError = vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative( m_OverlayHandle, vr::k_unTrackedDeviceIndex_Hmd, &matInFrontOfHead ); 140 | if ( eOverlayError != vr::VROverlayError_None ) 141 | return false; 142 | 143 | std::string image; 144 | switch ( eOverlay ) 145 | { 146 | case k_ePointAtBaseForHemisphereTracking: 147 | image = m_strOverlayImagePath + "need_hemisphere_tracking.png"; 148 | break; 149 | 150 | case k_eHoldAtShouldersForCoordinateAlignment: 151 | image = m_strOverlayImagePath + "need_alignment_gesture.png"; 152 | break; 153 | 154 | default: 155 | HideOverlay(); 156 | return false; 157 | } 158 | 159 | eOverlayError = vr::VROverlay()->SetOverlayFromFile( m_OverlayHandle, image.c_str() ); 160 | if ( eOverlayError != vr::VROverlayError_None ) 161 | return false; 162 | 163 | eOverlayError = vr::VROverlay()->ShowOverlay( m_OverlayHandle ); 164 | if ( eOverlayError != vr::VROverlayError_None ) 165 | return false; 166 | 167 | m_eCurrentOverlay = eOverlay; 168 | 169 | return true; 170 | } 171 | 172 | void HideOverlay() 173 | { 174 | if ( m_OverlayHandle == vr::k_ulOverlayHandleInvalid ) 175 | return; 176 | 177 | vr::VRCompositor(); // Required to call overlays... 178 | vr::VROverlay()->HideOverlay( m_OverlayHandle ); 179 | vr::VROverlay()->DestroyOverlay( m_OverlayHandle ); 180 | m_OverlayHandle = vr::k_ulOverlayHandleInvalid; 181 | } 182 | 183 | /** Send a message to the driver with the HMD coordinates (which are not available to the server side) */ 184 | bool TriggerRealignCoordinates( const vr::VREvent_t & Event ) 185 | { 186 | vr::TrackedDevicePose_t hmdPose; 187 | vr::VRSystem()->GetDeviceToAbsoluteTrackingPose( vr::TrackingUniverseRawAndUncalibrated, -Event.eventAgeSeconds, &hmdPose, 1 ); 188 | if ( !hmdPose.bPoseIsValid ) 189 | return false; 190 | 191 | std::ostringstream ss; 192 | char rgchReplyBuf[256]; 193 | 194 | ss << "leap:realign_coordinates"; 195 | for ( int i = 0; i < 3; ++i ) 196 | { 197 | for ( int j = 0; j < 4; ++j ) 198 | { 199 | ss << " " << hmdPose.mDeviceToAbsoluteTracking.m[i][j]; 200 | } 201 | } 202 | // printf("%s\n", ss.str().c_str()); 203 | vr::VRSystem()->DriverDebugRequest( Event.trackedDeviceIndex, ss.str().c_str(), rgchReplyBuf, sizeof( rgchReplyBuf ) ); 204 | return true; 205 | } 206 | 207 | void Shutdown() 208 | { 209 | vr::VR_Shutdown(); 210 | } 211 | 212 | /** Keep track of which devices are using driver_leap */ 213 | void UpdateTrackedDevice( uint32_t unTrackedDeviceIndex ) 214 | { 215 | char rgchTrackingSystemName[vr::k_unTrackingStringSize]; 216 | vr::ETrackedPropertyError eError; 217 | 218 | uint32_t size = vr::VRSystem()->GetStringTrackedDeviceProperty( unTrackedDeviceIndex, vr::Prop_TrackingSystemName_String, rgchTrackingSystemName, sizeof( rgchTrackingSystemName ), &eError ); 219 | if ( eError == vr::TrackedProp_Success ) 220 | { 221 | if ( strcmp( rgchTrackingSystemName, "leap" ) == 0 ) 222 | { 223 | m_setLeapDevices.insert( unTrackedDeviceIndex ); 224 | } 225 | } 226 | } 227 | 228 | /** If any Leap devices need automatic hemisphere tracking enabled, prompt the user to do that. 229 | * Otherwise, if any devices do not know how to transform into the global coordinate system, show 230 | * instructions for that. */ 231 | EOverlayToDisplay BestOverlayForLeapDevices() 232 | { 233 | bool bNeedCoordinateAlignment = false; 234 | vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount]; 235 | 236 | // The "raw and uncalibrated" universe gives us coordinates in the HMD's native tracking space. 237 | // Adjustments like room setup and seated zero position will be applied equally to the HMD and 238 | // the coordinates we return, so the "raw" space is what we want our driver to match. 239 | vr::VRSystem()->GetDeviceToAbsoluteTrackingPose( vr::TrackingUniverseRawAndUncalibrated, 0, poses, vr::k_unMaxTrackedDeviceCount ); 240 | for ( auto it = m_setLeapDevices.begin(); it != m_setLeapDevices.end(); ++it ) 241 | { 242 | if ( poses[*it].bDeviceIsConnected ) 243 | { 244 | switch ( poses[*it].eTrackingResult ) 245 | { 246 | case vr::TrackingResult_Uninitialized: 247 | // Getting all devices to have hemisphere tracking is high priority 248 | return k_ePointAtBaseForHemisphereTracking; 249 | 250 | case vr::TrackingResult_Calibrating_InProgress: 251 | bNeedCoordinateAlignment = true; 252 | break; 253 | } 254 | } 255 | } 256 | 257 | if ( bNeedCoordinateAlignment ) 258 | return k_eHoldAtShouldersForCoordinateAlignment; 259 | 260 | return k_eNone; 261 | } 262 | 263 | bool IsLeapDevice( uint32_t unTrackedDeviceIndex ) 264 | { 265 | return ( m_setLeapDevices.count( unTrackedDeviceIndex ) != 0 ); 266 | } 267 | 268 | private: 269 | std::string m_strOverlayImagePath; 270 | vr::VROverlayHandle_t m_OverlayHandle; 271 | EOverlayToDisplay m_eCurrentOverlay; 272 | std::set m_setLeapDevices; 273 | }; 274 | 275 | #if 0 276 | int main(int argc, char **argv) 277 | { 278 | // Find resource path 279 | HMODULE hModule = GetModuleHandleA(NULL); 280 | char path[MAX_PATH]; 281 | GetModuleFileNameA(hModule, path, MAX_PATH); 282 | 283 | char *snip = strstr(path, "\\bin\\"); 284 | if (snip) 285 | { 286 | *snip = '\0'; 287 | } 288 | 289 | std::string resources = std::string(path) + "\\resources\\overlays\\"; 290 | 291 | CLeapMonitor LeapMonitor(resources); 292 | 293 | LeapMonitor.Run(); 294 | } 295 | #else 296 | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 297 | _In_opt_ HINSTANCE hPrevInstance, 298 | _In_ LPWSTR lpCmdLine, 299 | _In_ int nCmdShow) 300 | { 301 | UNREFERENCED_PARAMETER(hPrevInstance); 302 | UNREFERENCED_PARAMETER(lpCmdLine); 303 | 304 | // Find resource path 305 | HMODULE hModule = GetModuleHandleA( NULL ); 306 | char path[MAX_PATH]; 307 | GetModuleFileNameA( hModule, path, MAX_PATH ); 308 | 309 | char *snip = strstr( path, "\\bin\\" ); 310 | if ( snip ) 311 | { 312 | *snip = '\0'; 313 | } 314 | 315 | std::string resources = std::string( path ) + "\\resources\\overlays\\"; 316 | 317 | CLeapMonitor LeapMonitor( resources ); 318 | 319 | LeapMonitor.Run(); 320 | } 321 | #endif 322 | -------------------------------------------------------------------------------- /drivers/driver_leap/GestureMatcher.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "GestureMatcher.h" 3 | 4 | const Vector GestureMatcher::RightVector = Vector(-1, 0, 0); 5 | const Vector GestureMatcher::InVector = Vector( 0, 1, 0); 6 | const Vector GestureMatcher::UpVector = Vector( 0, 0, -1); 7 | 8 | // invert a 3x3 matrix 9 | static void invert_3x3(const float(*A)[3], float(*R)[3]) { 10 | float det; 11 | 12 | det = A[0][0] * (A[2][2] * A[1][1] - A[2][1] * A[1][2]) 13 | - A[1][0] * (A[2][2] * A[0][1] - A[2][1] * A[0][2]) 14 | + A[2][0] * (A[1][2] * A[0][1] - A[1][1] * A[0][2]); 15 | 16 | R[0][0] = (A[2][2] * A[1][1] - A[2][1] * A[1][2]) / det; 17 | R[0][1] = -(A[2][2] * A[0][1] - A[2][1] * A[0][2]) / det; 18 | R[0][2] = (A[1][2] * A[0][1] - A[1][1] * A[0][2]) / det; 19 | 20 | R[1][0] = -(A[2][2] * A[1][0] - A[2][0] * A[1][2]) / det; 21 | R[1][1] = (A[2][2] * A[0][0] - A[2][0] * A[0][2]) / det; 22 | R[1][2] = -(A[1][2] * A[0][0] - A[1][0] * A[0][2]) / det; 23 | 24 | R[2][0] = (A[2][1] * A[1][0] - A[2][0] * A[1][1]) / det; 25 | R[2][1] = -(A[2][1] * A[0][0] - A[2][0] * A[0][1]) / det; 26 | R[2][2] = (A[1][1] * A[0][0] - A[1][0] * A[0][1]) / det; 27 | } 28 | 29 | // compute matrix * column vector product 30 | static Vector matrix_vector(const float(*A)[3], Vector &v) 31 | { 32 | Vector result; 33 | result.x = A[0][0] * v.x + A[0][1] * v.y + A[0][2] * v.z; 34 | result.y = A[1][0] * v.x + A[1][1] * v.y + A[1][2] * v.z; 35 | result.z = A[2][0] * v.x + A[2][1] * v.y + A[2][2] * v.z; 36 | return result; 37 | } 38 | 39 | 40 | 41 | GestureMatcher::GestureMatcher() 42 | { 43 | } 44 | 45 | GestureMatcher::~GestureMatcher() 46 | { 47 | } 48 | 49 | bool GestureMatcher::MatchGestures(const Frame &frame, WhichHand which, float(&result)[NUM_GESTURES], 50 | Vector right, Vector in, Vector up) 51 | { 52 | // first, set all gesture matches to zero 53 | bool success = false; 54 | memset(result, 0, sizeof(result)); 55 | 56 | // Go through the hands in the dataset 57 | HandList &hands = frame.hands(); 58 | 59 | for (int h = 0; h < hands.count(); h++) 60 | { 61 | Hand hand = hands[h]; 62 | 63 | // these are the conditions under which we do not want 64 | // to evaluate a hand from the Frame dataset. 65 | if (!hand.isValid()) continue; 66 | if (which == RightHand && hand.isLeft()) continue; 67 | if (which == LeftHand && hand.isRight()) continue; 68 | 69 | Hand otherhand; 70 | if (hands.count() == 2) otherhand = hands[(h + 1) % 2]; 71 | 72 | // okay, found a hand we want to look at. 73 | success = true; 74 | 75 | // stores the bend angles per finger and per joint 76 | float bends[5][3] = { 0 }; 77 | 78 | // total bend angle of a finger 79 | float sumbend[5] = { 0 }; 80 | 81 | Vector fingerdir[5]; 82 | Vector fingertip[5]; 83 | bool extended[5]; 84 | memset(fingerdir, 0, sizeof(fingerdir)); 85 | memset(fingertip, 0, sizeof(fingertip)); 86 | memset(extended, 0, sizeof(extended)); 87 | 88 | // Evaluate bending of all fingers 89 | const FingerList &fingers = hand.fingers(); 90 | for (auto fl = fingers.begin(); fl != fingers.end(); ++fl) { 91 | const Finger &finger = *fl; 92 | 93 | int f = finger.type(); // thumb, index, middle, ring, pinky 94 | if (finger.isFinger() && finger.isValid()) 95 | { 96 | fingertip[f] = finger.tipPosition(); 97 | extended[f] = finger.isExtended(); 98 | 99 | // go through the finger's bones: 100 | // metacarpal, proximal, intermediate, distal 101 | Vector prev_direction; 102 | for (int b = 0; b < 4; b++) 103 | { 104 | Bone &bone = finger.bone(Bone::Type(b)); 105 | Vector direction = -bone.direction(); // for some reaason bone directions point handinwards? 106 | 107 | if (b == Bone::TYPE_DISTAL) 108 | fingerdir[f] = direction; 109 | 110 | if (b > 0) 111 | { 112 | // get the bend angle of each finger joint 113 | bends[f][b-1] = 57.2957795f * direction.angleTo(prev_direction); // in degrees 114 | 115 | // also sum up the total 116 | sumbend[f] += bends[f][b - 1]; 117 | } 118 | prev_direction = direction; 119 | } 120 | } 121 | } 122 | 123 | // trigger figure gesture means the bend angles of the upper two joints 124 | // of the index finger exceed 70 degrees. 125 | float triggerbend = bends[Finger::TYPE_INDEX][1] + bends[Finger::TYPE_INDEX][2]; 126 | float trigger = maprange(triggerbend, 70.0, 100.0); 127 | merge(result[TriggerFinger], trigger); 128 | 129 | // lower first gesture means clenching middle, ring, pinky fingers beyond 90 degrees 130 | float grip = maprange((sumbend[Finger::TYPE_MIDDLE] + sumbend[Finger::TYPE_RING] + sumbend[Finger::TYPE_PINKY]) / 3, 90.0, 180.0); 131 | merge(result[LowerFist], grip); 132 | 133 | // pinch gesture means pinching the index and thumb (distance closer than 30mm) 134 | float pinch = maprange(hand.pinchDistance(), 40, 30); 135 | merge(result[Pinch], pinch); 136 | 137 | // Thumbpress gesture means that the thumb points the direction of the pinky 138 | Vector palmNormal = hand.palmNormal(); 139 | Vector direction = hand.direction(); 140 | Vector pinkyside; 141 | if (hand.isRight()) 142 | pinkyside = palmNormal.cross(direction); 143 | else 144 | pinkyside = direction.cross(palmNormal); 145 | merge(result[Thumbpress], maprange(pinkyside.dot(fingerdir[Finger::TYPE_THUMB]), 0.0f, 0.6f)); 146 | 147 | 148 | // *UNRELIABLE* ILY gesture means pinky and index finger extended, middle and ring finger curled up 149 | // Thumb doesn't matter. It's easier to point it inwards for many people. 150 | merge(result[ILY], std::min(maprange((sumbend[Finger::TYPE_PINKY] + sumbend[Finger::TYPE_INDEX]) / 2, 50.0, 40.0), 151 | maprange((sumbend[Finger::TYPE_MIDDLE] + sumbend[Finger::TYPE_RING]) / 2, 120.0, 150.0))); 152 | 153 | // *UNRELIABLE* Flipping the Bird: You know how to flip a bird. 154 | merge(result[FlippingTheBird], std::min(maprange(sumbend[Finger::TYPE_MIDDLE], 50.0, 40.0), 155 | maprange((sumbend[Finger::TYPE_INDEX] + sumbend[Finger::TYPE_RING] + sumbend[Finger::TYPE_PINKY]) / 3, 120.0, 150.0))); 156 | 157 | // Victory gesture: make a nice V sign with your index and middle finger 158 | float angle = fingerdir[Finger::TYPE_INDEX].angleTo(fingerdir[Finger::TYPE_MIDDLE]); 159 | merge(result[Victory], std::min(std::min(maprange((sumbend[Finger::TYPE_INDEX] + sumbend[Finger::TYPE_MIDDLE]) / 2, 50.0, 40.0), 160 | maprange((sumbend[Finger::TYPE_PINKY] + sumbend[Finger::TYPE_RING]) / 2, 120.0, 150.0)), 161 | maprange(57.2957795f * fingerdir[Finger::TYPE_INDEX].angleTo(fingerdir[Finger::TYPE_MIDDLE]), 10.0, 20.0) )); 162 | 163 | // FlatHand gestures 164 | float flatHand = maprange((sumbend[Finger::TYPE_THUMB] + sumbend[Finger::TYPE_INDEX] + sumbend[Finger::TYPE_MIDDLE] + sumbend[Finger::TYPE_RING] + sumbend[Finger::TYPE_PINKY]) / 5, 50.0, 40.0); 165 | merge(result[FlatHandPalmUp] , std::min(flatHand, maprange(( up).dot(palmNormal), 0.8f, 0.95f))); 166 | merge(result[FlatHandPalmDown] , std::min(flatHand, maprange((-up).dot(palmNormal), 0.8f, 0.95f))); 167 | merge(result[FlatHandPalmAway] , std::min(flatHand, maprange(( in).dot(palmNormal), 0.8f, 0.95f))); 168 | merge(result[FlatHandPalmTowards], std::min(flatHand, maprange((-in).dot(palmNormal), 0.8f, 0.95f))); 169 | 170 | // ThumbsUp/Inward gestures 171 | Vector inward = hand.isLeft() ? right : -right; 172 | float fistHand = maprange((sumbend[Finger::TYPE_INDEX] + sumbend[Finger::TYPE_MIDDLE] + sumbend[Finger::TYPE_RING] + sumbend[Finger::TYPE_PINKY]) / 5, 120.0, 150.0); 173 | float straightThumb = maprange(sumbend[Finger::TYPE_THUMB], 50.0, 40.0); 174 | merge(result[ThumbUp] , std::min(fistHand, std::min(straightThumb, maprange(( up).dot(fingerdir[Finger::TYPE_THUMB]), 0.8f, 0.95f)))); 175 | merge(result[ThumbInward] , std::min(fistHand, std::min(straightThumb, maprange((inward).dot(fingerdir[Finger::TYPE_THUMB]), 0.8f, 0.95f)))); 176 | 177 | if (otherhand.isValid()) // two handed gestures really need two hands 178 | { 179 | // Timeout gesture. Note that only the lower hand forming the T shape will register the gesture. 180 | // TODO: might also validate that the lower hand points upward 181 | // TODO: might as well check that the other hand is also flat 182 | merge(result[Timeout], std::min( flatHand, // I reuse the flatHand metric from above 183 | std::min( maprange(hand.direction().dot(-otherhand.palmNormal()), 0.8f, 0.95f), 184 | maprange(fingertip[Leap::Finger::TYPE_INDEX].distanceTo(otherhand.palmPosition()), 80.0f, 60.0f) ) 185 | )); 186 | 187 | // Touchpad emulation 188 | Finger otherIndex = otherhand.fingers().fingerType(Leap::Finger::TYPE_INDEX)[0]; 189 | if (otherIndex.isFinger() && otherIndex.isValid()) 190 | { 191 | // we need the index finger direction and the other hand's palm to face opposing directions 192 | if (otherIndex.direction().dot(hand.palmNormal()) < 0) 193 | { 194 | // Origin o of the plane is the hand's palm position 195 | Vector o = hand.palmPosition(); 196 | 197 | // sideways vector on the hand 198 | Vector uvec = direction.cross(palmNormal) * hand.palmWidth() / 2; 199 | 200 | // vvec going from palm towards the hand's pointing direction 201 | Vector vvec = hand.direction() * hand.palmWidth() / 2; 202 | 203 | // p and d form a line originating at the index finger's tip 204 | Vector p = otherIndex.tipPosition(); 205 | Vector d = otherIndex.direction(); 206 | 207 | // solve this linear equation system 208 | // p0 d0 o0 uvec0 vvec0 209 | // p1 + n * d1 = o1 + u * uvec1 + v * vvec1 210 | // p2 d2 o2 uvec1 vvec2 211 | 212 | // u u0 v0 d0 p0 o0 213 | // v = inv( u1 v1 d1 ) * ( p1 - o1 ) 214 | // n u2 v2 d2 p2 o2 215 | 216 | float A[3][3] = { {uvec.x, vvec.x, d.x}, 217 | {uvec.y, vvec.y, d.y}, 218 | {uvec.z, vvec.z, d.z} }; 219 | 220 | float invA[3][3]; 221 | invert_3x3(A, invA); 222 | Vector R = matrix_vector(invA, Vector(p.x - o.x, p.y - o.y, p.z - o.z)); 223 | 224 | // u and v are in R.x and R.y respectively and are expected to be within -1...+1 225 | // when the index finger points into the palm area 226 | // n is contained in R.z and represents the distance in mm between the index finger tip and the palm 227 | 228 | // compute length of u,v vector in plane 229 | float u = R.x; 230 | float v = R.y; 231 | float length = sqrtf(u*u + v*v); 232 | 233 | // ignore if are we pointing way out of bounds 234 | if (length < 5.0) 235 | { 236 | // limit vector to remain inside unit circle 237 | if (length > 1.0) { 238 | u /= length; 239 | v /= length; 240 | } 241 | result[TouchpadAxisX] = u; 242 | result[TouchpadAxisY] = v; 243 | } 244 | } 245 | } 246 | } 247 | 248 | #if 0 249 | fprintf(stderr, "handdir %f %f %f\n", hand.direction().x, hand.direction().y, hand.direction().z); 250 | fprintf(stderr, "thumbdir %f %f %f\n", fingerdir[Finger::TYPE_THUMB].x, fingerdir[Finger::TYPE_THUMB].y, fingerdir[Finger::TYPE_THUMB].z); 251 | fprintf(stderr, "indexdir %f %f %f\n", fingerdir[Finger::TYPE_INDEX].x, fingerdir[Finger::TYPE_INDEX].y, fingerdir[Finger::TYPE_INDEX].z); 252 | fprintf(stderr, "palmpos %f %f %f\n", hand.palmPosition().x, hand.palmPosition().y, hand.palmPosition().z); 253 | fprintf(stderr, "palmnormal %f %f %f\n", hand.palmNormal().x, hand.palmNormal().y, hand.palmNormal().z); 254 | #endif 255 | } 256 | 257 | return success; 258 | } 259 | -------------------------------------------------------------------------------- /drivers/driver_leap/driver_leap.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {52d3f16d-a7a5-4d6f-8f17-5e4b459a1440} 23 | DynamicLibrary 24 | driver_leap 25 | driver_leap 26 | en-US 27 | 12.0 28 | 8.1 29 | 8.1 30 | 31 | 32 | 33 | DynamicLibrary 34 | true 35 | v140 36 | 37 | 38 | DynamicLibrary 39 | true 40 | v140 41 | 42 | 43 | DynamicLibrary 44 | false 45 | true 46 | v140 47 | 48 | 49 | DynamicLibrary 50 | false 51 | true 52 | v140 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | false 79 | false 80 | 81 | 82 | false 83 | AllRules.ruleset 84 | 85 | 86 | false 87 | false 88 | 89 | 90 | false 91 | AllRules.ruleset 92 | 93 | 94 | false 95 | false 96 | 97 | 98 | false 99 | false 100 | 101 | 102 | 103 | NotUsing 104 | false 105 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 106 | false 107 | 108 | 109 | Windows 110 | false 111 | false 112 | $(LeapSDKDir)\lib\x86;$(OpenVRDir)\lib\win32;%(AdditionalLibraryDirectories) 113 | Leap.lib;%(AdditionalDependencies) 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | mkdir "$(InstallDir)\bin\Win32" 125 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 126 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(InstallDir)\bin\Win32" 127 | rem "$(SteamVRRuntimeDir)\bin\win32\vrpathreg" adddriver "$(InstallDir)" 128 | xcopy /s /i /y "$(SolutionDir)\resources" "$(InstallDir)\resources" 129 | 130 | 131 | Install Binary 132 | 133 | 134 | 135 | 136 | NotUsing 137 | false 138 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 139 | false 140 | 141 | 142 | Windows 143 | false 144 | false 145 | $(LeapSDKDir)\lib\x86;$(OpenVRDir)\lib\win32;%(AdditionalLibraryDirectories) 146 | Leap.lib;%(AdditionalDependencies) 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | mkdir "$(InstallDir)\bin\Win32" 158 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win32" 159 | copy "$(LeapSDKDir)\lib\x86\Leap.dll" "$(InstallDir)\bin\Win32" 160 | rem "$(SteamVRRuntimeDir)\bin\win32\vrpathreg" adddriver "$(InstallDir)" 161 | xcopy /s /i /y "$(SolutionDir)\resources" "$(InstallDir)\resources" 162 | 163 | 164 | Install Binary 165 | 166 | 167 | 168 | 169 | Use 170 | false 171 | 172 | 173 | Console 174 | false 175 | false 176 | 177 | 178 | 179 | 180 | Use 181 | false 182 | 183 | 184 | Console 185 | false 186 | false 187 | 188 | 189 | 190 | 191 | Use 192 | false 193 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 194 | pch.h 195 | 196 | 197 | Windows 198 | false 199 | false 200 | $(LeapSDKDir)\lib\x64;$(OpenVRDir)\lib\win64;%(AdditionalLibraryDirectories) 201 | Leap.lib;%(AdditionalDependencies) 202 | 203 | 204 | 205 | 206 | 207 | mkdir "$(InstallDir)\bin\Win64" 208 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 209 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(InstallDir)\bin\Win64" 210 | rem "$(SteamVRRuntimeDir)\bin\win32\vrpathreg" adddriver "$(InstallDir)" 211 | xcopy /s /i /y "$(SolutionDir)\resources" "$(InstallDir)\resources" 212 | Install Binary 213 | 214 | 215 | 216 | 217 | NotUsing 218 | false 219 | $(LeapSDKDir)\include;$(OpenVRDir)\headers;%(AdditionalIncludeDirectories) 220 | pch.h 221 | 222 | 223 | Windows 224 | false 225 | false 226 | $(LeapSDKDir)\lib\x64;$(OpenVRDir)\lib\win64;%(AdditionalLibraryDirectories) 227 | Leap.lib;%(AdditionalDependencies) 228 | 229 | 230 | 231 | 232 | 233 | mkdir "$(InstallDir)\bin\Win64" 234 | copy "$(OutDir)$(TargetName)$(TargetExt)" "$(InstallDir)\bin\Win64" 235 | copy "$(LeapSDKDir)\lib\x64\Leap.dll" "$(InstallDir)\bin\Win64" 236 | rem "$(SteamVRRuntimeDir)\bin\win32\vrpathreg" adddriver "$(InstallDir)" 237 | xcopy /s /i /y "$(SolutionDir)\resources" "$(InstallDir)\resources" 238 | Install Binary 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | NotUsing 253 | NotUsing 254 | Create 255 | Create 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /tools/config_tool/config_tool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "rapidjson/document.h" 11 | #include "rapidjson/filereadstream.h" 12 | #include "rapidjson/filewritestream.h" 13 | #include "rapidjson/prettywriter.h" 14 | 15 | using namespace std; 16 | 17 | // execute a command and return its output 18 | wstring exec(const wstring &cmd) { 19 | 20 | // escape the command to run in double quotes 21 | // and send it to a command shell 22 | wchar_t tmp[1024]; 23 | wsprintf(tmp, L"cmd.exe /S /C \"%s\"", cmd.c_str()); 24 | 25 | wchar_t buffer[128]; 26 | wstring result = L""; 27 | shared_ptr pipe(_wpopen(tmp, L"r"), _pclose); 28 | if (!pipe) throw runtime_error("popen() failed!"); 29 | while (!feof(pipe.get())) { 30 | if (fgetws(buffer, 128, pipe.get()) != NULL) 31 | result += buffer; 32 | } 33 | return result; 34 | } 35 | 36 | // read a registry key value of type REG_SZ 37 | bool ReadRegValue(HKEY root, wstring key, wstring name, wstring &value) 38 | { 39 | HKEY hKey; 40 | if (RegOpenKeyEx(root, key.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS) 41 | return false; 42 | 43 | DWORD type; 44 | DWORD cbData; 45 | if (RegQueryValueEx(hKey, name.c_str(), NULL, &type, NULL, &cbData) != ERROR_SUCCESS) 46 | { 47 | RegCloseKey(hKey); 48 | return false; 49 | } 50 | 51 | if (type != REG_SZ) 52 | { 53 | RegCloseKey(hKey); 54 | return false; 55 | } 56 | 57 | value = wstring(cbData / sizeof(wchar_t), L'\0'); 58 | if (RegQueryValueEx(hKey, name.c_str(), NULL, NULL, reinterpret_cast(&value[0]), &cbData) != ERROR_SUCCESS) 59 | { 60 | RegCloseKey(hKey); 61 | return false; 62 | } 63 | 64 | RegCloseKey(hKey); 65 | 66 | size_t firstNull = value.find_first_of(L'\0'); 67 | if (firstNull != string::npos) 68 | value.resize(firstNull); 69 | 70 | return true; 71 | } 72 | 73 | // check if a directory exists 74 | bool dirExists(const std::wstring& dirName_in) 75 | { 76 | DWORD ftyp = GetFileAttributes(dirName_in.c_str()); 77 | if (ftyp == INVALID_FILE_ATTRIBUTES) 78 | return false; //something is wrong with your path! 79 | 80 | if (ftyp & FILE_ATTRIBUTE_DIRECTORY) 81 | return true; // this is a directory! 82 | 83 | return false; // this is not a directory! 84 | } 85 | 86 | // get the SteamVR installation path 87 | // TODO: I just learnt about %LOCALAPPDATA%\OpenVR\openvrpaths.vrpath. Let's make use of it. 88 | bool SteamVRInstallLocation(wstring &location) 89 | { 90 | // guess where streamVR is located from its uninstall entry. Is there a better way? 91 | if (!ReadRegValue(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 250820", L"InstallLocation", location)) 92 | { 93 | // otherwise assume it's in the default Steam library 94 | if (ReadRegValue(HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam", L"SteamPath", location)) 95 | { 96 | replace(location.begin(), location.end(), L'/', L'\\'); 97 | 98 | if (dirExists(location + L"\\steamapps\\common\\SteamVR")) 99 | { 100 | location.append(L"\\steamapps\\common\\SteamVR"); 101 | return true; 102 | } 103 | else if (dirExists(location + L"\\steamapps\\common\\OpenVR")) 104 | { 105 | location.append(L"\\steamapps\\common\\OpenVR"); 106 | return true; 107 | } 108 | return false; 109 | } 110 | } 111 | else 112 | { 113 | return true; 114 | } 115 | return false; 116 | } 117 | 118 | // get the path to the vrpathreg utility (32bit) 119 | // (this will be enclosed in double quotes if path contains a space character) 120 | bool SteamVRPathReg(wstring &location) 121 | { 122 | if (SteamVRInstallLocation(location)) 123 | { 124 | location.append(L"\\bin\\win32\\vrpathreg.exe"); 125 | 126 | if (location.find(L' ') != wstring::npos) 127 | location = L"\"" + location + L"\""; 128 | 129 | return true; 130 | } 131 | return false; 132 | } 133 | 134 | // get the SteamVR config path from the vrpathreg utility 135 | bool SteamVRConfigPath(wstring &configpath) 136 | { 137 | wstring location; 138 | if (SteamVRPathReg(location)) 139 | { 140 | wstring result = exec(location); 141 | size_t pos = result.find(L"Config path = ", 0); 142 | if (pos != wstring::npos) 143 | { 144 | pos += 14; 145 | size_t pos2 = result.find(L"\n", pos); 146 | configpath = result.substr(pos, pos2 - pos); 147 | return true; 148 | } 149 | } 150 | return false; 151 | } 152 | 153 | // get the SteamVR log path from the vrpathreg utility 154 | bool SteamVRLogPath(wstring &logpath) 155 | { 156 | wstring location; 157 | if (SteamVRPathReg(location)) 158 | { 159 | wstring result = exec(location); 160 | size_t pos = result.find(L"Log path = ", 0); 161 | if (pos != wstring::npos) 162 | { 163 | pos += 11; 164 | size_t pos2 = result.find(L"\n", pos); 165 | logpath = result.substr(pos, pos2 - pos); 166 | return true; 167 | } 168 | return true; 169 | } 170 | return false; 171 | } 172 | 173 | // get full path to steamvr.vrconfig file 174 | bool SteamVRVRSettingsFile(wstring &configfile) 175 | { 176 | if (SteamVRConfigPath(configfile)) 177 | { 178 | configfile.append(L"\\steamvr.vrsettings"); 179 | return true; 180 | } 181 | return false; 182 | } 183 | 184 | #include 185 | 186 | void MergeObject(rapidjson::Value& target, rapidjson::Value& source, rapidjson::Value::AllocatorType& allocator, rapidjson::Value& backup) { 187 | if (target.GetType() == source.GetType() || 188 | target.GetType() == rapidjson::kTrueType && source.GetType() == rapidjson::kFalseType || 189 | target.GetType() == rapidjson::kFalseType && source.GetType() == rapidjson::kTrueType) 190 | { 191 | if (target.GetType() == rapidjson::kObjectType || target.GetType() == rapidjson::kArrayType) 192 | { 193 | std::vector toremove; 194 | 195 | // if the backup copy of the original document has members that are NOT in the source document 196 | // we delete them from the backup copy 197 | for (rapidjson::Value::MemberIterator itr = backup.MemberBegin(); itr != backup.MemberEnd(); ++itr) 198 | if (source.FindMember((*itr).name) == source.MemberEnd()) 199 | toremove.push_back(itr); 200 | 201 | // erase in reverse order to keep the iterators sane 202 | for (int i = toremove.size()-1; i >= 0; i--) 203 | backup.RemoveMember(toremove[i]); 204 | 205 | // add or merge members found in the source document into the target document 206 | for (rapidjson::Value::MemberIterator itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr) 207 | { 208 | rapidjson::Value::MemberIterator dest; 209 | if ((dest = target.FindMember((*itr).name)) == target.MemberEnd()) 210 | target.AddMember(itr->name, itr->value, allocator); 211 | else 212 | MergeObject((*dest).value, (*itr).value, allocator, (*backup.FindMember((*itr).name)).value); 213 | } 214 | } 215 | // string, number, boolean and null types simply get replaced by the source 216 | else if (target.GetType() == rapidjson::kStringType || 217 | target.GetType() == rapidjson::kNumberType || 218 | target.GetType() == rapidjson::kFalseType || 219 | target.GetType() == rapidjson::kTrueType || 220 | target.GetType() == rapidjson::kNullType) 221 | target = source; 222 | } 223 | } 224 | 225 | bool SubtractObject(rapidjson::Value& target, rapidjson::Value& source) 226 | { 227 | if (target.GetType() == source.GetType() || 228 | target.GetType() == rapidjson::kTrueType && source.GetType() == rapidjson::kFalseType || 229 | target.GetType() == rapidjson::kFalseType && source.GetType() == rapidjson::kTrueType) 230 | { 231 | if (target.GetType() == rapidjson::kObjectType || target.GetType() == rapidjson::kArrayType) 232 | { 233 | std::vector toremove; 234 | 235 | // subtract members found in the source document from the target document 236 | for (rapidjson::Value::MemberIterator itr = source.MemberBegin(); itr != source.MemberEnd(); ++itr) 237 | { 238 | rapidjson::Value::MemberIterator dest; 239 | if ((dest = target.FindMember((*itr).name)) != target.MemberEnd()) 240 | if (SubtractObject((*dest).value, (*itr).value)) 241 | toremove.push_back(dest); 242 | } 243 | 244 | // erase in reverse order to keep the iterators sane 245 | for (int i = toremove.size() - 1; i >= 0; i--) 246 | target.RemoveMember(toremove[i]); 247 | 248 | // empty parent members should be deleted 249 | if (target.MemberCount() == 0) return true; 250 | } 251 | // string, number, boolean and null types simply get removed 252 | else if (target.GetType() == rapidjson::kStringType || 253 | target.GetType() == rapidjson::kNumberType || 254 | target.GetType() == rapidjson::kFalseType || 255 | target.GetType() == rapidjson::kTrueType || 256 | target.GetType() == rapidjson::kNullType) 257 | return true; 258 | } 259 | return false; 260 | } 261 | 262 | bool MergeJSON(const wstring &targetfile, const wstring &tobemergedin, const wstring &backupfile) 263 | { 264 | char buffer[65536]; 265 | 266 | FILE * pFile = _wfopen(targetfile.c_str(), L"rt"); 267 | if (pFile == NULL) 268 | { 269 | // create an empty JSON config file if we did not find an existing file. 270 | pFile = _wfopen(targetfile.c_str(), L"wt"); 271 | if (pFile != NULL) 272 | { 273 | fprintf(pFile, "{\n}\n"); 274 | fclose(pFile); 275 | } 276 | 277 | pFile = _wfopen(targetfile.c_str(), L"rt"); 278 | } 279 | rapidjson::Document document; 280 | { 281 | rapidjson::FileReadStream is(pFile, buffer, sizeof(buffer)); 282 | document.ParseStream<0>(is); 283 | } 284 | fclose(pFile); 285 | 286 | FILE * pFile2 = _wfopen(tobemergedin.c_str(), L"rt"); 287 | rapidjson::Document document2; 288 | { 289 | rapidjson::FileReadStream is2(pFile2, buffer, sizeof(buffer)); 290 | document2.ParseStream<0>(is2); 291 | } 292 | fclose(pFile2); 293 | 294 | rapidjson::Document backup; 295 | backup.CopyFrom(document, backup.GetAllocator()); 296 | 297 | MergeObject(document, document2, document.GetAllocator(), backup); 298 | 299 | FILE * pFile3 = _wfopen(targetfile.c_str(), L"wt"); 300 | { 301 | rapidjson::FileWriteStream os(pFile3, buffer, sizeof(buffer)); 302 | rapidjson::PrettyWriter writer(os); 303 | document.Accept(writer); 304 | } 305 | fclose(pFile3); 306 | 307 | FILE * pFile4 = _wfopen(backupfile.c_str(), L"wt"); 308 | { 309 | rapidjson::FileWriteStream os(pFile4, buffer, sizeof(buffer)); 310 | rapidjson::PrettyWriter writer(os); 311 | backup.Accept(writer); 312 | } 313 | fclose(pFile4); 314 | 315 | return true; 316 | } 317 | 318 | bool UnmergeJSON(const wstring &targetfile, const wstring &tobeunmerged, const wstring &backupfile) 319 | { 320 | char buffer[65536]; 321 | 322 | FILE * pFile = _wfopen(targetfile.c_str(), L"rt"); 323 | rapidjson::Document document; 324 | { 325 | rapidjson::FileReadStream is(pFile, buffer, sizeof(buffer)); 326 | document.ParseStream<0>(is); 327 | } 328 | fclose(pFile); 329 | 330 | FILE * pFile2 = _wfopen(tobeunmerged.c_str(), L"rt"); 331 | rapidjson::Document document2; 332 | { 333 | rapidjson::FileReadStream is2(pFile2, buffer, sizeof(buffer)); 334 | document2.ParseStream<0>(is2); 335 | } 336 | fclose(pFile2); 337 | 338 | FILE * pFile3 = _wfopen(backupfile.c_str(), L"rt"); 339 | rapidjson::Document document3; 340 | { 341 | rapidjson::FileReadStream is3(pFile3, buffer, sizeof(buffer)); 342 | document3.ParseStream<0>(is3); 343 | } 344 | fclose(pFile3); 345 | 346 | SubtractObject(document, document2); 347 | rapidjson::Document backup; 348 | backup.CopyFrom(document, backup.GetAllocator()); 349 | MergeObject(document, document3, document.GetAllocator(), backup); 350 | 351 | FILE * pFile4 = _wfopen(targetfile.c_str(), L"wt"); 352 | { 353 | rapidjson::FileWriteStream os(pFile4, buffer, sizeof(buffer)); 354 | rapidjson::PrettyWriter writer(os); 355 | document.Accept(writer); 356 | } 357 | fclose(pFile4); 358 | 359 | return true; 360 | } 361 | 362 | // main program 363 | int wmain(int argc, wchar_t* argv[]) 364 | { 365 | if (argc == 4) 366 | { 367 | // default to current working directory as the driver path 368 | wchar_t wcwd[512]; 369 | wchar_t *cwd = _wgetcwd(wcwd, sizeof(wcwd) / sizeof(wchar_t)); 370 | 371 | // if the path to the source JSON file contains a backslash, 372 | // prefer this path over the current working directory for 373 | // registering the driver path. 374 | wchar_t *tmp; 375 | if ((tmp = wcsrchr(argv[2], L'\\')) != NULL) 376 | { 377 | cwd = wcsncpy(wcwd, argv[2], tmp - argv[2]); 378 | wcwd[tmp - argv[2]] = L'\0'; 379 | } 380 | 381 | if (cwd != NULL) 382 | { 383 | wstring configfile; 384 | if (SteamVRVRSettingsFile(configfile)) 385 | { 386 | if (!wcsicmp(argv[1], L"install")) 387 | { 388 | MergeJSON(configfile, argv[2], argv[3]); 389 | 390 | wstring location; 391 | if (SteamVRPathReg(location)) 392 | { 393 | location.append(L" adddriver \""); 394 | location.append(cwd); 395 | location.append(L"\\leap"); 396 | location.append(L"\""); 397 | wstring result = exec(location); 398 | wcout << result << endl; 399 | } 400 | } 401 | else if (!wcsicmp(argv[1], L"uninstall")) 402 | { 403 | UnmergeJSON(configfile, argv[2], argv[3]); 404 | 405 | wstring location; 406 | if (SteamVRPathReg(location)) 407 | { 408 | location.append(L" removedriver \""); 409 | location.append(cwd); 410 | location.append(L"\\leap"); 411 | location.append(L"\""); 412 | wstring result = exec(location); 413 | wcout << result << endl; 414 | } 415 | } 416 | } 417 | else 418 | wcout << L"Unable to determine location of steamvr.vrsettings file!" << endl; 419 | } 420 | } 421 | else 422 | { 423 | wcout << L"Usage: config_tool.exe [un]install jsonfile backupfile" << endl; 424 | } 425 | return 0; 426 | } 427 | -------------------------------------------------------------------------------- /drivers/driver_leap/driver_leap.cpp: -------------------------------------------------------------------------------- 1 | //========= Copyright Valve Corporation ============// 2 | // 3 | // driver_leap.cpp : Defines the client and server interfaces used by the SteamVR runtime. 4 | // 5 | 6 | #include "pch.h" 7 | #include "driver_leap.h" 8 | 9 | #include 10 | 11 | #ifdef _WIN32 12 | #include // for timeBeginPeriod() 13 | #pragma comment(lib, "winmm.lib") 14 | #endif 15 | 16 | #include 17 | 18 | #define HMD_DLL_EXPORT extern "C" __declspec( dllexport ) 19 | 20 | CServerDriver_Leap g_ServerTrackedDeviceProvider; 21 | CClientDriver_Leap g_ClientTrackedDeviceProvider; 22 | 23 | // strangely, swapping the order here doesn't fix the swapped controllers in Audioshield 24 | #define LEFT_CONTROLLER 0 25 | #define RIGHT_CONTROLLER 1 26 | 27 | HMD_DLL_EXPORT 28 | void *HmdDriverFactory( const char *pInterfaceName, int *pReturnCode ) 29 | { 30 | if ( 0 == strcmp( vr::IServerTrackedDeviceProvider_Version, pInterfaceName ) ) 31 | { 32 | return &g_ServerTrackedDeviceProvider; 33 | } 34 | if ( 0 == strcmp( vr::IClientTrackedDeviceProvider_Version, pInterfaceName ) ) 35 | { 36 | return &g_ClientTrackedDeviceProvider; 37 | } 38 | 39 | if ( pReturnCode ) 40 | *pReturnCode = vr::VRInitError_Init_InterfaceNotFound; 41 | 42 | return NULL; 43 | } 44 | 45 | //================================================================================================== 46 | // Logging helpers 47 | //================================================================================================== 48 | 49 | static vr::IDriverLog * s_pLogFile = NULL; 50 | 51 | static bool InitDriverLog( vr::IDriverLog *pDriverLog ) 52 | { 53 | if ( s_pLogFile ) 54 | return false; 55 | s_pLogFile = pDriverLog; 56 | return s_pLogFile != NULL; 57 | } 58 | 59 | static void CleanupDriverLog() 60 | { 61 | s_pLogFile = NULL; 62 | } 63 | 64 | static void DriverLogVarArgs( const char *pMsgFormat, va_list args ) 65 | { 66 | char buf[1024]; 67 | #if defined( WIN32 ) 68 | vsprintf_s( buf, pMsgFormat, args ); 69 | #else 70 | vsnprintf( buf, sizeof( buf ), pMsgFormat, args ); 71 | #endif 72 | 73 | if ( s_pLogFile ) 74 | s_pLogFile->Log( buf ); 75 | } 76 | 77 | /** Provides printf-style debug logging via the vr::IDriverLog interface provided by SteamVR 78 | * during initialization. Client logging ends up in vrclient_appname.txt and server logging 79 | * ends up in vrserver.txt. 80 | */ 81 | static void DriverLog( const char *pMsgFormat, ... ) 82 | { 83 | va_list args; 84 | va_start( args, pMsgFormat ); 85 | 86 | DriverLogVarArgs( pMsgFormat, args ); 87 | 88 | va_end( args ); 89 | } 90 | 91 | //================================================================================================== 92 | // Listener interface in Server Provider 93 | //================================================================================================== 94 | 95 | /** 96 | * Called once, when this Listener object is newly added to a Controller. 97 | * 98 | * \include Listener_onInit.txt 99 | * 100 | * @param controller The Controller object invoking this callback function. 101 | * @since 1.0 102 | */ 103 | void CServerDriver_Leap::onInit(const Controller&controller) 104 | { 105 | DriverLog("CServerDriver_Leap::onInit()\n"); 106 | } 107 | 108 | /** 109 | * Called when the Controller object connects to the Leap Motion software and 110 | * the Leap Motion hardware device is plugged in, 111 | * or when this Listener object is added to a Controller that is already connected. 112 | * 113 | * When this callback is invoked, Controller::isServiceConnected is true, 114 | * Controller::devices() is not empty, and, for at least one of the Device objects in the list, 115 | * Device::isStreaming() is true. 116 | * 117 | * \include Listener_onConnect.txt 118 | * 119 | * @param controller The Controller object invoking this callback function. 120 | * @since 1.0 121 | */ 122 | void CServerDriver_Leap::onConnect(const Controller&controller) 123 | { 124 | DriverLog("CServerDriver_Leap::onConnect()\n"); 125 | } 126 | 127 | /** 128 | * Called when the Controller object disconnects from the Leap Motion software or 129 | * the Leap Motion hardware is unplugged. 130 | * The controller can disconnect when the Leap Motion device is unplugged, the 131 | * user shuts the Leap Motion software down, or the Leap Motion software encounters an 132 | * unrecoverable error. 133 | * 134 | * \include Listener_onDisconnect.txt 135 | * 136 | * Note: When you launch a Leap-enabled application in a debugger, the 137 | * Leap Motion library does not disconnect from the application. This is to allow 138 | * you to step through code without losing the connection because of time outs. 139 | * 140 | * @param controller The Controller object invoking this callback function. 141 | * @since 1.0 142 | */ 143 | void CServerDriver_Leap::onDisconnect(const Controller&controller) 144 | { 145 | DriverLog("CServerDriver_Leap::onDisconnect()\n"); 146 | } 147 | 148 | /** 149 | * Called when this Listener object is removed from the Controller 150 | * or the Controller instance is destroyed. 151 | * 152 | * \include Listener_onExit.txt 153 | * 154 | * @param controller The Controller object invoking this callback function. 155 | * @since 1.0 156 | */ 157 | void CServerDriver_Leap::onExit(const Controller&controller) 158 | { 159 | DriverLog("CServerDriver_Leap::onExit()\n"); 160 | } 161 | 162 | /** 163 | * Called when a new frame of hand and finger tracking data is available. 164 | * Access the new frame data using the Controller::frame() function. 165 | * 166 | * \include Listener_onFrame.txt 167 | * 168 | * Note, the Controller skips any pending onFrame events while your 169 | * onFrame handler executes. If your implementation takes too long to return, 170 | * one or more frames can be skipped. The Controller still inserts the skipped 171 | * frames into the frame history. You can access recent frames by setting 172 | * the history parameter when calling the Controller::frame() function. 173 | * You can determine if any pending onFrame events were skipped by comparing 174 | * the ID of the most recent frame with the ID of the last received frame. 175 | * 176 | * @param controller The Controller object invoking this callback function. 177 | * @since 1.0 178 | */ 179 | void CServerDriver_Leap::onFrame(const Controller&controller) 180 | { 181 | } 182 | 183 | /** 184 | * Called when this application becomes the foreground application. 185 | * 186 | * Only the foreground application receives tracking data from the Leap 187 | * Motion Controller. This function is only called when the controller 188 | * object is in a connected state. 189 | * 190 | * \include Listener_onFocusGained.txt 191 | * 192 | * @param controller The Controller object invoking this callback function. 193 | * @since 1.0 194 | */ 195 | void CServerDriver_Leap::onFocusGained(const Controller&controller) 196 | { 197 | // DriverLog("CServerDriver_Leap::onFocusGained()\n"); 198 | } 199 | 200 | /** 201 | * Called when this application loses the foreground focus. 202 | * 203 | * Only the foreground application receives tracking data from the Leap 204 | * Motion Controller. This function is only called when the controller 205 | * object is in a connected state. 206 | * 207 | * \include Listener_onFocusLost.txt 208 | * 209 | * @param controller The Controller object invoking this callback function. 210 | * @since 1.0 211 | */ 212 | void CServerDriver_Leap::onFocusLost(const Controller&controller) 213 | { 214 | // DriverLog("CServerDriver_Leap::onFocusLost()\n"); 215 | } 216 | 217 | // onServiceConnect/onServiceDisconnect are for connection established/lost. 218 | // in normal course of events onServiceConnect will get called once after onInit 219 | // and onServiceDisconnect will not get called. disconnect notification only happens 220 | // if service stops running or something else bad happens to disconnect controller from service. 221 | /** 222 | * Called when the Leap Motion daemon/service connects to your application Controller. 223 | * 224 | * \include Listener_onServiceConnect.txt 225 | * 226 | * @param controller The Controller object invoking this callback function. 227 | * @since 1.2 228 | */ 229 | void CServerDriver_Leap::onServiceConnect(const Controller&controller) 230 | { 231 | DriverLog("CServerDriver_Leap::onServiceConnect()\n"); 232 | } 233 | 234 | /** 235 | * Called if the Leap Motion daemon/service disconnects from your application Controller. 236 | * 237 | * Normally, this callback is not invoked. It is only called if some external event 238 | * or problem shuts down the service or otherwise interrupts the connection. 239 | * 240 | * \include Listener_onServiceDisconnect.txt 241 | * 242 | * @param controller The Controller object invoking this callback function. 243 | * @since 1.2 244 | */ 245 | void CServerDriver_Leap::onServiceDisconnect(const Controller&controller) 246 | { 247 | DriverLog("CServerDriver_Leap::onServiceDisconnect()\n"); 248 | } 249 | 250 | /** 251 | * Called when a Leap Motion controller is plugged in, unplugged, or the device changes state. 252 | * 253 | * State changes include entering or leaving robust mode and low resource mode. 254 | * Note that there is no direct way to query whether the device is in these modes, 255 | * although you can use Controller::isLightingBad() to check if there are environmental 256 | * IR lighting problems. 257 | * 258 | * \include Listener_onDeviceChange.txt 259 | * 260 | * @param controller The Controller object invoking this callback function. 261 | * @since 1.2 262 | */ 263 | void CServerDriver_Leap::onDeviceChange(const Controller&controller) 264 | { 265 | DriverLog("CServerDriver_Leap::onDeviceChange()\n"); 266 | 267 | if (controller.isConnected()) 268 | { 269 | bool backgroundModeAllowed = controller.config().getInt32("background_app_mode") == 2; 270 | if (!backgroundModeAllowed) { 271 | // TODO: Show dialog to request permission to allow background mode apps 272 | bool userPermission = true; 273 | if (userPermission) { 274 | controller.config().setInt32("background_app_mode", 2); 275 | controller.config().save(); 276 | } 277 | } 278 | 279 | controller.setPolicy(Leap::Controller::POLICY_OPTIMIZE_HMD); 280 | controller.setPolicy(Leap::Controller::POLICY_BACKGROUND_FRAMES); 281 | 282 | // make sure we always get background frames even when we lose the focus to another 283 | // Leap-enabled application 284 | controller.setPolicy((Leap::Controller::PolicyFlag)(15)); 285 | 286 | // allow other background applications to receive frames even when SteamVR has the focus. 287 | controller.setPolicy((Leap::Controller::PolicyFlag)(23)); 288 | 289 | ScanForNewControllers(true); 290 | } 291 | else 292 | { 293 | for (auto it = m_vecControllers.begin(); it != m_vecControllers.end(); ++it) 294 | delete (*it); 295 | m_vecControllers.clear(); 296 | } 297 | } 298 | 299 | /** 300 | * Called when new images are available. 301 | * Access the new frame data using the Controller::images() function. 302 | * 303 | * \include Listener_onImages.txt 304 | * 305 | * @param controller The Controller object invoking this callback function. 306 | * @since 2.2.1 307 | */ 308 | void CServerDriver_Leap::onImages(const Controller&controller) 309 | { 310 | } 311 | 312 | /** 313 | * Called when the Leap Motion service is paused or resumed or when a 314 | * controller policy is changed. 315 | * 316 | * The service can change states because the computer user changes settings 317 | * in the Leap Motion Control Panel application or because an application 318 | * connected to the service triggers a change. Any application can pause or 319 | * unpause the service, but only runtime policy changes you make apply to your 320 | * own application. 321 | * 322 | * \include Listener_onServiceChange.txt 323 | * 324 | * You can query the pause state of the controller with Controller::isPaused(). 325 | * You can check the state of those policies you are interested in with 326 | * Controller::isPolicySet(). 327 | * 328 | * @param controller The Controller object invoking this callback function. 329 | * @since 3.0 330 | */ 331 | void CServerDriver_Leap::onServiceChange(const Controller&controller) 332 | { 333 | DriverLog("CServerDriver_Leap::onServiceChange()\n"); 334 | } 335 | 336 | /** 337 | * Called when a Leap Motion controller device is plugged into the client 338 | * computer, but fails to operate properly. 339 | * 340 | * Get the list containing all failed devices using Controller::failedDevices(). 341 | * The members of this list provide the device pnpID and reason for failure. 342 | * 343 | * \include Listener_onDeviceFailure.txt 344 | * 345 | * @param controller The Controller object invoking this callback function. 346 | * @since 3.0 347 | */ 348 | void CServerDriver_Leap::onDeviceFailure(const Controller&controller) 349 | { 350 | DriverLog("CServerDriver_Leap::onDeviceFailure()\n"); 351 | } 352 | 353 | /** 354 | * Called when the service emits a log message to report an error, warning, or 355 | * status change. 356 | * 357 | * Log message text is provided as ASCII-encoded english. 358 | * 359 | * @param controller The Controller object invoking this callback function. 360 | * @param severity The severity of the error, if known. 361 | * @param timestamp The timestamp of the error in microseconds. 362 | * (Use Controller::now() - timestamp to compute the age of the message.) 363 | * @param msg The log message. 364 | * @since 3.0 365 | */ 366 | void CServerDriver_Leap::onLogMessage(const Controller&controller, MessageSeverity severity, int64_t timestamp, const char* msg) 367 | { 368 | DriverLog("CServerDriver_Leap::onLogMessage(%d): %s\n", (int)severity, msg); 369 | } 370 | 371 | 372 | //================================================================================================== 373 | // Server Provider 374 | //================================================================================================== 375 | 376 | CServerDriver_Leap::CServerDriver_Leap() 377 | : m_bLaunchedLeapMonitor( false ) 378 | { 379 | // DriverLog not yet initialized at this point. 380 | // DriverLog("CServerDriver_Leap::CServerDriver_Leap()\n"); 381 | } 382 | 383 | CServerDriver_Leap::~CServerDriver_Leap() 384 | { 385 | DriverLog("CServerDriver_Leap::~CServerDriver_Leap()\n"); 386 | Cleanup(); 387 | } 388 | 389 | vr::EVRInitError CServerDriver_Leap::Init( vr::IDriverLog * pDriverLog, vr::IServerDriverHost * pDriverHost, const char * pchUserDriverConfigDir, const char * pchDriverInstallDir ) 390 | { 391 | InitDriverLog( pDriverLog ); 392 | DriverLog("CServerDriver_Leap::Init()\n"); 393 | 394 | m_pDriverHost = pDriverHost; 395 | m_strDriverInstallDir = pchDriverInstallDir; 396 | 397 | m_Controller = new Controller; 398 | 399 | Controller &controller = *m_Controller; 400 | 401 | m_Controller->addListener(*this); 402 | 403 | return vr::VRInitError_None; 404 | } 405 | 406 | void CServerDriver_Leap::Cleanup() 407 | { 408 | DriverLog("CServerDriver_Leap::Cleanup()\n"); 409 | 410 | // send a termination message to the leap monitor companion application 411 | if (m_bLaunchedLeapMonitor) 412 | { 413 | // Ask leap_monitor to shut down. 414 | PostThreadMessage(m_pInfoStartedProcess.dwThreadId, WM_QUIT, 0, 0); 415 | m_bLaunchedLeapMonitor = false; 416 | } 417 | 418 | // clean up our Leap::Controller object 419 | if (m_Controller) 420 | { 421 | m_Controller->removeListener(*this); 422 | delete m_Controller; 423 | m_Controller = NULL; 424 | } 425 | 426 | // clean up any controller objects we've created 427 | for (auto it = m_vecControllers.begin(); it != m_vecControllers.end(); ++it) 428 | delete (*it); 429 | m_vecControllers.clear(); 430 | } 431 | 432 | uint32_t CServerDriver_Leap::GetTrackedDeviceCount() 433 | { 434 | return m_vecControllers.size(); 435 | } 436 | 437 | vr::ITrackedDeviceServerDriver * CServerDriver_Leap::GetTrackedDeviceDriver( uint32_t unWhich ) 438 | { 439 | if ( unWhich < m_vecControllers.size() ) 440 | return m_vecControllers[unWhich]; 441 | 442 | return nullptr; 443 | } 444 | 445 | vr::ITrackedDeviceServerDriver * CServerDriver_Leap::FindTrackedDeviceDriver( const char * pchId ) 446 | { 447 | for ( auto it = m_vecControllers.begin(); it != m_vecControllers.end(); ++it ) 448 | { 449 | if ( 0 == strcmp( ( *it )->GetSerialNumber(), pchId ) ) 450 | { 451 | return *it; 452 | } 453 | } 454 | return nullptr; 455 | } 456 | 457 | void CServerDriver_Leap::RunFrame() 458 | { 459 | if (m_vecControllers.size() == 2) 460 | { 461 | m_vecControllers[0]->RealignCoordinates(m_vecControllers[0], m_vecControllers[1]); 462 | } 463 | 464 | if (m_Controller) 465 | { 466 | if (m_Controller->isConnected()) 467 | { 468 | Frame frame = m_Controller->frame(); 469 | 470 | // update the controllers 471 | for (auto it = m_vecControllers.begin(); it != m_vecControllers.end(); ++it) 472 | { 473 | CLeapHmdLatest *pLeap = *it; 474 | if (pLeap->IsActivated()) 475 | { 476 | // Returns true if this is new data (so we can sleep for long interval) 477 | if (!pLeap->Update(frame)) 478 | { 479 | // not updated? 480 | } 481 | } 482 | } 483 | } 484 | } 485 | } 486 | 487 | bool CServerDriver_Leap::ShouldBlockStandbyMode() 488 | { 489 | return false; 490 | } 491 | 492 | void CServerDriver_Leap::EnterStandby() 493 | { 494 | DriverLog("CServerDriver_Leap::EnterStandby()\n"); 495 | } 496 | 497 | void CServerDriver_Leap::LeaveStandby() 498 | { 499 | DriverLog("CServerDriver_Leap::LeaveStandby()\n"); 500 | } 501 | 502 | static void GenerateSerialNumber( char *p, int psize, int base, int controller ) 503 | { 504 | char tmp[32]; 505 | _snprintf(tmp, 32, "controller%d", controller); 506 | _snprintf( p, psize, "leap%d_%s", base, (controller == LEFT_CONTROLLER) ? "lefthand" : (controller == RIGHT_CONTROLLER) ? "righthand" : tmp ); 507 | } 508 | 509 | void CServerDriver_Leap::ScanForNewControllers( bool bNotifyServer ) 510 | { 511 | while (m_vecControllers.size() < 2) 512 | { 513 | char buf[256]; 514 | int base = 0; 515 | int i = m_vecControllers.size(); 516 | GenerateSerialNumber( buf, sizeof( buf ), base, i ); 517 | if ( !FindTrackedDeviceDriver( buf ) ) 518 | { 519 | DriverLog( "added new device %s\n", buf ); 520 | m_vecControllers.push_back( new CLeapHmdLatest( m_pDriverHost, base, i ) ); 521 | if ( bNotifyServer && m_pDriverHost ) 522 | { 523 | m_pDriverHost->TrackedDeviceAdded( m_vecControllers.back()->GetSerialNumber() ); 524 | } 525 | } 526 | } 527 | } 528 | 529 | // The leap_monitor is a companion program which will tell us the pose of the HMD. 530 | void CServerDriver_Leap::LaunchLeapMonitor( const char * pchDriverInstallDir ) 531 | { 532 | if ( m_bLaunchedLeapMonitor ) 533 | return; 534 | 535 | DriverLog("CServerDriver_Leap::LaunchLeapMonitor()\n"); 536 | 537 | m_bLaunchedLeapMonitor = true; 538 | 539 | std::ostringstream ss; 540 | 541 | ss << pchDriverInstallDir << "\\bin\\"; 542 | #if defined( _WIN64 ) 543 | ss << "win64"; 544 | #elif defined( _WIN32 ) 545 | ss << "win32"; 546 | #else 547 | #error Do not know how to launch leap_monitor 548 | #endif 549 | DriverLog( "leap_monitor path: %s\n", ss.str().c_str() ); 550 | 551 | #if defined( _WIN32 ) 552 | STARTUPINFOA sInfoProcess = { 0 }; 553 | sInfoProcess.cb = sizeof(STARTUPINFOW); 554 | // sInfoProcess.dwFlags = STARTF_USESHOWWINDOW; 555 | // sInfoProcess.wShowWindow = SW_SHOWDEFAULT; 556 | BOOL okay = CreateProcessA( (ss.str() + "\\leap_monitor.exe").c_str(), NULL, NULL, NULL, FALSE, 0, NULL, ss.str().c_str(), &sInfoProcess, &m_pInfoStartedProcess ); 557 | DriverLog( "start leap_monitor okay: %d %08x\n", okay, GetLastError() ); 558 | #else 559 | #error Do not know how to launch leap_monitor 560 | #endif 561 | } 562 | 563 | /** Launch leap_monitor if needed (requested by devices as they activate) */ 564 | void CServerDriver_Leap::LaunchLeapMonitor() 565 | { 566 | LaunchLeapMonitor( m_strDriverInstallDir.c_str() ); 567 | } 568 | 569 | //================================================================================================== 570 | // Client Provider 571 | //================================================================================================== 572 | 573 | CClientDriver_Leap::CClientDriver_Leap() 574 | { 575 | } 576 | 577 | CClientDriver_Leap::~CClientDriver_Leap() 578 | { 579 | } 580 | 581 | vr::EVRInitError CClientDriver_Leap::Init( vr::IDriverLog * pDriverLog, vr::IClientDriverHost * pDriverHost, const char * pchUserDriverConfigDir, const char * pchDriverInstallDir ) 582 | { 583 | InitDriverLog( pDriverLog ); 584 | DriverLog("CClientDriver_Leap::Init()\n"); 585 | m_pDriverHost = pDriverHost; 586 | return vr::VRInitError_None; 587 | } 588 | 589 | void CClientDriver_Leap::Cleanup() 590 | { 591 | DriverLog("CClientDriver_Leap::Cleanup()\n"); 592 | } 593 | 594 | bool CClientDriver_Leap::BIsHmdPresent( const char * pchUserConfigDir ) 595 | { 596 | return false; 597 | } 598 | 599 | vr::EVRInitError CClientDriver_Leap::SetDisplayId( const char * pchDisplayId ) 600 | { 601 | return vr::VRInitError_None; 602 | //return vr::VRInitError_Driver_HmdUnknown; 603 | } 604 | 605 | vr::HiddenAreaMesh_t CClientDriver_Leap::GetHiddenAreaMesh( vr::EVREye eEye ) 606 | { 607 | return vr::HiddenAreaMesh_t(); 608 | } 609 | 610 | uint32_t CClientDriver_Leap::GetMCImage( uint32_t * pImgWidth, uint32_t * pImgHeight, uint32_t * pChannels, void * pDataBuffer, uint32_t unBufferLen ) 611 | { 612 | return uint32_t(); 613 | } 614 | 615 | //================================================================================================== 616 | // Device Driver 617 | //================================================================================================== 618 | 619 | const std::chrono::milliseconds CLeapHmdLatest::k_TrackingLatency( -30 ); 620 | 621 | CLeapHmdLatest::CLeapHmdLatest( vr::IServerDriverHost * pDriverHost, int base, int n ) 622 | : m_pDriverHost( pDriverHost ) 623 | , m_nBase( base ) 624 | , m_nId( n ) 625 | , m_bCalibrated( true ) 626 | , m_pAlignmentPartner( NULL ) 627 | , m_unSteamVRTrackedDeviceId( vr::k_unTrackedDeviceIndexInvalid ) 628 | { 629 | DriverLog("CLeapHmdLatest::CLeapHmdLatest(base=%d, n=%d)\n", base, n); 630 | 631 | memset(m_hmdPos, 0, sizeof(m_hmdPos)); 632 | 633 | char buf[256]; 634 | GenerateSerialNumber( buf, sizeof( buf ), base, n ); 635 | m_strSerialNumber = buf; 636 | 637 | memset( &m_ControllerState, 0, sizeof( m_ControllerState ) ); 638 | memset( &m_Pose, 0, sizeof( m_Pose ) ); 639 | m_Pose.result = vr::TrackingResult_Uninitialized; 640 | 641 | m_firmware_revision = 0x0001; 642 | m_hardware_revision = 0x0001; 643 | 644 | // Load config from steamvr.vrsettings 645 | vr::IVRSettings *settings_; 646 | settings_ = m_pDriverHost->GetSettings(vr::IVRSettings_Version); 647 | 648 | // Load rendermodel 649 | char tmp_[256]; 650 | settings_->GetString("leap", (m_nId == LEFT_CONTROLLER) ? "renderModel_lefthand" : (m_nId == RIGHT_CONTROLLER) ? "renderModel_righthand" : "renderModel", tmp_, sizeof(tmp_), "vr_controller_vive_1_5"); 651 | m_strRenderModel = tmp_; 652 | 653 | // set the 654 | m_gripAngleOffset = settings_->GetFloat("leap", (m_nId == LEFT_CONTROLLER) ? "gripAngleOffset_lefthand" : (m_nId == RIGHT_CONTROLLER) ? "gripAngleOffset_righthand" : "gripAngleOffset", 0.0); 655 | } 656 | 657 | CLeapHmdLatest::~CLeapHmdLatest() 658 | { 659 | DriverLog("CLeapHmdLatest::~CLeapHmdLatest(base=%d, n=%d)\n", m_nBase, m_nId); 660 | } 661 | 662 | void *CLeapHmdLatest::GetComponent( const char *pchComponentNameAndVersion ) 663 | { 664 | if ( !stricmp( pchComponentNameAndVersion, vr::IVRControllerComponent_Version ) ) 665 | { 666 | return ( vr::IVRControllerComponent* )this; 667 | } 668 | 669 | return NULL; 670 | } 671 | 672 | vr::EVRInitError CLeapHmdLatest::Activate( uint32_t unObjectId ) 673 | { 674 | DriverLog( "CLeapHmdLatest::Activate: %s is object id %d\n", GetSerialNumber(), unObjectId ); 675 | m_unSteamVRTrackedDeviceId = unObjectId; 676 | 677 | g_ServerTrackedDeviceProvider.LaunchLeapMonitor(); 678 | 679 | return vr::VRInitError_None; 680 | } 681 | 682 | void CLeapHmdLatest::Deactivate() 683 | { 684 | DriverLog( "CLeapHmdLatest::Deactivate: %s was object id %d\n", GetSerialNumber(), m_unSteamVRTrackedDeviceId ); 685 | m_unSteamVRTrackedDeviceId = vr::k_unTrackedDeviceIndexInvalid; 686 | } 687 | 688 | void CLeapHmdLatest::PowerOff() 689 | { 690 | DriverLog("CLeapHmdLatest::PowerOff()\n"); 691 | // FIXME Implement 692 | } 693 | 694 | void CLeapHmdLatest::DebugRequest( const char * pchRequest, char * pchResponseBuffer, uint32_t unResponseBufferSize ) 695 | { 696 | std::istringstream ss( pchRequest ); 697 | std::string strCmd; 698 | 699 | ss >> strCmd; 700 | if ( strCmd == "leap:realign_coordinates" ) 701 | { 702 | // leap_monitor is calling us back with HMD tracking information so we can 703 | // finish realigning our coordinate system to the HMD's 704 | float m[3][3], v[3]; 705 | for ( int i = 0; i < 3; ++i ) 706 | { 707 | for ( int j = 0; j < 3; ++j ) 708 | { 709 | ss >> m[j][i]; 710 | } 711 | ss >> v[i]; 712 | } 713 | FinishRealignCoordinates(m, v); 714 | } 715 | } 716 | 717 | const char * CLeapHmdLatest::GetSerialNumber() 718 | { 719 | return m_strSerialNumber.c_str(); 720 | } 721 | 722 | vr::DriverPose_t CLeapHmdLatest::GetPose() 723 | { 724 | // This is only called at startup to synchronize with the driver. 725 | // Future updates are driven by our thread calling TrackedDevicePoseUpdated() 726 | return m_Pose; 727 | } 728 | 729 | bool CLeapHmdLatest::GetBoolTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) 730 | { 731 | *pError = vr::TrackedProp_ValueNotProvidedByDevice; 732 | return false; 733 | } 734 | 735 | float CLeapHmdLatest::GetFloatTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) 736 | { 737 | *pError = vr::TrackedProp_ValueNotProvidedByDevice; 738 | return 0.0f; 739 | } 740 | 741 | int32_t CLeapHmdLatest::GetInt32TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) 742 | { 743 | int32_t nRetVal = 0; 744 | vr::ETrackedPropertyError error = vr::TrackedProp_UnknownProperty; 745 | switch ( prop ) 746 | { 747 | case vr::Prop_DeviceClass_Int32: 748 | nRetVal = vr::TrackedDeviceClass_Controller; 749 | error = vr::TrackedProp_Success; 750 | break; 751 | 752 | case vr::Prop_Axis0Type_Int32: 753 | nRetVal = vr::k_eControllerAxis_Joystick; 754 | error = vr::TrackedProp_Success; 755 | break; 756 | 757 | case vr::Prop_Axis1Type_Int32: 758 | nRetVal = vr::k_eControllerAxis_Trigger; 759 | error = vr::TrackedProp_Success; 760 | break; 761 | 762 | case vr::Prop_Axis2Type_Int32: 763 | case vr::Prop_Axis3Type_Int32: 764 | case vr::Prop_Axis4Type_Int32: 765 | error = vr::TrackedProp_ValueNotProvidedByDevice; 766 | break; 767 | } 768 | 769 | *pError = error; 770 | return nRetVal; 771 | } 772 | 773 | uint64_t CLeapHmdLatest::GetUint64TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) 774 | { 775 | uint64_t ulRetVal = 0; 776 | vr::ETrackedPropertyError error = vr::TrackedProp_ValueNotProvidedByDevice; 777 | 778 | switch ( prop ) 779 | { 780 | case vr::Prop_CurrentUniverseId_Uint64: 781 | case vr::Prop_PreviousUniverseId_Uint64: 782 | error = vr::TrackedProp_ValueNotProvidedByDevice; 783 | break; 784 | 785 | case vr::Prop_SupportedButtons_Uint64: 786 | ulRetVal = 787 | vr::ButtonMaskFromId( vr::k_EButton_ApplicationMenu) | 788 | vr::ButtonMaskFromId( vr::k_EButton_System ) | 789 | vr::ButtonMaskFromId( vr::k_EButton_SteamVR_Touchpad ) | 790 | vr::ButtonMaskFromId( vr::k_EButton_SteamVR_Trigger) | 791 | vr::ButtonMaskFromId( vr::k_EButton_Grip ); 792 | error = vr::TrackedProp_Success; 793 | break; 794 | 795 | case vr::Prop_HardwareRevision_Uint64: 796 | ulRetVal = m_hardware_revision; 797 | error = vr::TrackedProp_Success; 798 | break; 799 | 800 | case vr::Prop_FirmwareVersion_Uint64: 801 | ulRetVal = m_firmware_revision; 802 | error = vr::TrackedProp_Success; 803 | break; 804 | 805 | } 806 | 807 | *pError = error; 808 | return ulRetVal; 809 | } 810 | 811 | vr::HmdMatrix34_t CLeapHmdLatest::GetMatrix34TrackedDeviceProperty( vr::ETrackedDeviceProperty prop, vr::ETrackedPropertyError * pError ) 812 | { 813 | return vr::HmdMatrix34_t(); 814 | } 815 | 816 | uint32_t CLeapHmdLatest::GetStringTrackedDeviceProperty( vr::ETrackedDeviceProperty prop, char * pchValue, uint32_t unBufferSize, vr::ETrackedPropertyError * pError ) 817 | { 818 | std::ostringstream ssRetVal; 819 | 820 | switch ( prop ) 821 | { 822 | case vr::Prop_SerialNumber_String: 823 | ssRetVal << m_strSerialNumber; 824 | break; 825 | 826 | case vr::Prop_RenderModelName_String: 827 | // We return the user configured rendermodel here. Defaults to "vr_controller_vive_1_5". 828 | ssRetVal << m_strRenderModel.c_str(); 829 | break; 830 | 831 | case vr::Prop_ManufacturerName_String: 832 | ssRetVal << "LeapMotion"; 833 | break; 834 | 835 | case vr::Prop_ModelNumber_String: 836 | ssRetVal << "Controller"; 837 | break; 838 | 839 | case vr::Prop_TrackingFirmwareVersion_String: 840 | ssRetVal << "cd.firmware_revision=" << m_firmware_revision; 841 | break; 842 | 843 | case vr::Prop_HardwareRevision_String: 844 | ssRetVal << "cd.hardware_revision=" << m_hardware_revision; 845 | break; 846 | } 847 | 848 | std::string sRetVal = ssRetVal.str(); 849 | if ( sRetVal.empty() ) 850 | { 851 | *pError = vr::TrackedProp_ValueNotProvidedByDevice; 852 | return 0; 853 | } 854 | else if ( sRetVal.size() + 1 > unBufferSize ) 855 | { 856 | *pError = vr::TrackedProp_BufferTooSmall; 857 | return sRetVal.size() + 1; // caller needs to know how to size buffer 858 | } 859 | else 860 | { 861 | _snprintf( pchValue, unBufferSize, sRetVal.c_str() ); 862 | *pError = vr::TrackedProp_Success; 863 | return sRetVal.size() + 1; 864 | } 865 | } 866 | 867 | vr::VRControllerState_t CLeapHmdLatest::GetControllerState() 868 | { 869 | // This is only called at startup to synchronize with the driver. 870 | // Future updates are driven by our thread calling TrackedDeviceButton*() and TrackedDeviceAxis*() 871 | return vr::VRControllerState_t(); 872 | } 873 | 874 | bool CLeapHmdLatest::TriggerHapticPulse( uint32_t unAxisId, uint16_t usPulseDurationMicroseconds ) 875 | { 876 | return true; // handled -- returning false will cause errors to come out of vrserver 877 | } 878 | 879 | void CLeapHmdLatest::SendButtonUpdates( ButtonUpdate ButtonEvent, uint64_t ulMask ) 880 | { 881 | if ( !ulMask ) 882 | return; 883 | 884 | for ( int i = 0; i< vr::k_EButton_Max; i++ ) 885 | { 886 | vr::EVRButtonId button = ( vr::EVRButtonId )i; 887 | 888 | uint64_t bit = ButtonMaskFromId( button ); 889 | 890 | if ( bit & ulMask ) 891 | { 892 | ( m_pDriverHost->*ButtonEvent )( m_unSteamVRTrackedDeviceId, button, 0.0 ); 893 | } 894 | } 895 | } 896 | 897 | void CLeapHmdLatest::UpdateControllerState(Frame &frame) 898 | { 899 | vr::VRControllerState_t NewState = { 0 }; 900 | 901 | bool handFound = false; 902 | GestureMatcher::WhichHand which = (m_nId == LEFT_CONTROLLER ) ? GestureMatcher::LeftHand : 903 | (m_nId == RIGHT_CONTROLLER) ? GestureMatcher::RightHand : 904 | GestureMatcher::AnyHand; 905 | 906 | float scores[GestureMatcher::NUM_GESTURES]; 907 | handFound = matcher.MatchGestures(frame, which, scores); 908 | 909 | if (handFound) 910 | { 911 | // Changing unPacketNum tells anyone polling state that something might have 912 | // changed. We don't try to be precise about that here. 913 | NewState.unPacketNum = m_ControllerState.unPacketNum + 1; 914 | 915 | // system menu mapping (timeout gesture) 916 | if (scores[GestureMatcher::Timeout] >= 0.5f) 917 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_System); 918 | if (scores[GestureMatcher::Timeout] >= 0.5f) 919 | NewState.ulButtonPressed |= vr::ButtonMaskFromId(vr::k_EButton_System); 920 | 921 | // application menu mapping (Flat hand towards your face gesture) 922 | if (scores[GestureMatcher::FlatHandPalmTowards] >= 0.8f) 923 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_ApplicationMenu); 924 | if (scores[GestureMatcher::FlatHandPalmTowards] >= 0.8f) 925 | NewState.ulButtonPressed |= vr::ButtonMaskFromId(vr::k_EButton_ApplicationMenu); 926 | 927 | // digital trigger mapping (fist clenching gesture) 928 | if (scores[GestureMatcher::TriggerFinger] > 0.5f) 929 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger); 930 | if (scores[GestureMatcher::TriggerFinger] > 0.5f) 931 | NewState.ulButtonPressed |= vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger); 932 | 933 | // grip mapping (clench fist with middle, index, pinky fingers) 934 | if (scores[GestureMatcher::LowerFist] >= 0.5f) 935 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_Grip); 936 | if (scores[GestureMatcher::LowerFist] >= 0.5f) 937 | NewState.ulButtonPressed |= vr::ButtonMaskFromId(vr::k_EButton_Grip); 938 | 939 | // touchpad button press mapping (Thumbpress gesture) 940 | if (scores[GestureMatcher::Thumbpress] >= 0.2f) 941 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Touchpad); 942 | if (scores[GestureMatcher::Thumbpress] >= 1.0f) 943 | NewState.ulButtonPressed |= vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Touchpad); 944 | 945 | #if 0 946 | // sixense driver seems to have good deadzone, but add a small one here 947 | if (fabsf(cd.joystick_x) > 0.03f || fabsf(cd.joystick_y) > 0.03f) 948 | NewState.ulButtonTouched |= vr::ButtonMaskFromId(vr::k_EButton_StreamVR_Touchpad); 949 | #endif 950 | 951 | // All pressed buttons are touched 952 | NewState.ulButtonTouched |= NewState.ulButtonPressed; 953 | 954 | uint64_t ulChangedTouched = NewState.ulButtonTouched ^ m_ControllerState.ulButtonTouched; 955 | uint64_t ulChangedPressed = NewState.ulButtonPressed ^ m_ControllerState.ulButtonPressed; 956 | 957 | SendButtonUpdates(&vr::IServerDriverHost::TrackedDeviceButtonTouched, ulChangedTouched & NewState.ulButtonTouched); 958 | SendButtonUpdates(&vr::IServerDriverHost::TrackedDeviceButtonPressed, ulChangedPressed & NewState.ulButtonPressed); 959 | SendButtonUpdates(&vr::IServerDriverHost::TrackedDeviceButtonUnpressed, ulChangedPressed & ~NewState.ulButtonPressed); 960 | SendButtonUpdates(&vr::IServerDriverHost::TrackedDeviceButtonUntouched, ulChangedTouched & ~NewState.ulButtonTouched); 961 | 962 | NewState.rAxis[0].x = scores[GestureMatcher::TouchpadAxisX]; 963 | NewState.rAxis[0].y = scores[GestureMatcher::TouchpadAxisY]; 964 | 965 | NewState.rAxis[1].x = scores[GestureMatcher::TriggerFinger]; 966 | NewState.rAxis[1].y = 0.0f; 967 | 968 | // the touchpad maps to Axis 0 X/Y 969 | if (NewState.rAxis[0].x != m_ControllerState.rAxis[0].x || NewState.rAxis[0].y != m_ControllerState.rAxis[0].y) 970 | m_pDriverHost->TrackedDeviceAxisUpdated(m_unSteamVRTrackedDeviceId, 0, NewState.rAxis[0]); 971 | 972 | // trigger maps to Axis 1 X 973 | if (NewState.rAxis[1].x != m_ControllerState.rAxis[1].x) 974 | m_pDriverHost->TrackedDeviceAxisUpdated(m_unSteamVRTrackedDeviceId, 1, NewState.rAxis[1]); 975 | 976 | m_ControllerState = NewState; 977 | } 978 | } 979 | 980 | // multiplication of quaternions 981 | vr::HmdQuaternion_t operator*(const vr::HmdQuaternion_t& a, const vr::HmdQuaternion_t& b) 982 | { 983 | vr::HmdQuaternion_t tmp; 984 | 985 | tmp.w = (b.w * a.w) - (b.x * a.x) - (b.y * a.y) - (b.z * a.z); 986 | tmp.x = (b.w * a.x) + (b.x * a.w) + (b.y * a.z) - (b.z * a.y); 987 | tmp.y = (b.w * a.y) + (b.y * a.w) + (b.z * a.x) - (b.x * a.z); 988 | tmp.z = (b.w * a.z) + (b.z * a.w) + (b.x * a.y) - (b.y * a.x); 989 | 990 | return tmp; 991 | } 992 | 993 | // generate a rotation quaternion around an arbitrary axis 994 | vr::HmdQuaternion_t rotate_around_axis(const Vector &v, const float &a) 995 | { 996 | // Here we calculate the sin( a / 2) once for optimization 997 | float factor = sinf(a* 0.01745329 / 2.0); 998 | 999 | // Calculate the x, y and z of the quaternion 1000 | float x = v.x * factor; 1001 | float y = v.y * factor; 1002 | float z = v.z * factor; 1003 | 1004 | // Calcualte the w value by cos( a / 2 ) 1005 | float w = cosf(a* 0.01745329 / 2.0); 1006 | 1007 | float mag = sqrtf(w*w + x*x + y*y + z*z); 1008 | 1009 | vr::HmdQuaternion_t result = { w / mag, x / mag, y / mag, z / mag }; 1010 | return result; 1011 | } 1012 | 1013 | // convert a 3x3 rotation matrix into a rotation quaternion 1014 | static vr::HmdQuaternion_t CalculateRotation(float(*a)[3]) { 1015 | 1016 | vr::HmdQuaternion_t q; 1017 | 1018 | float trace = a[0][0] + a[1][1] + a[2][2]; 1019 | if (trace > 0) { 1020 | float s = 0.5f / sqrtf(trace + 1.0f); 1021 | q.w = 0.25f / s; 1022 | q.x = (a[2][1] - a[1][2]) * s; 1023 | q.y = (a[0][2] - a[2][0]) * s; 1024 | q.z = (a[1][0] - a[0][1]) * s; 1025 | } 1026 | else { 1027 | if (a[0][0] > a[1][1] && a[0][0] > a[2][2]) { 1028 | float s = 2.0f * sqrtf(1.0f + a[0][0] - a[1][1] - a[2][2]); 1029 | q.w = (a[2][1] - a[1][2]) / s; 1030 | q.x = 0.25f * s; 1031 | q.y = (a[0][1] + a[1][0]) / s; 1032 | q.z = (a[0][2] + a[2][0]) / s; 1033 | } 1034 | else if (a[1][1] > a[2][2]) { 1035 | float s = 2.0f * sqrtf(1.0f + a[1][1] - a[0][0] - a[2][2]); 1036 | q.w = (a[0][2] - a[2][0]) / s; 1037 | q.x = (a[0][1] + a[1][0]) / s; 1038 | q.y = 0.25f * s; 1039 | q.z = (a[1][2] + a[2][1]) / s; 1040 | } 1041 | else { 1042 | float s = 2.0f * sqrtf(1.0f + a[2][2] - a[0][0] - a[1][1]); 1043 | q.w = (a[1][0] - a[0][1]) / s; 1044 | q.x = (a[0][2] + a[2][0]) / s; 1045 | q.y = (a[1][2] + a[2][1]) / s; 1046 | q.z = 0.25f * s; 1047 | } 1048 | } 1049 | q.x = -q.x; 1050 | q.y = -q.y; 1051 | q.z = -q.z; 1052 | return q; 1053 | } 1054 | 1055 | void CLeapHmdLatest::UpdateTrackingState(Frame &frame) 1056 | { 1057 | HandList &hands = frame.hands(); 1058 | 1059 | bool handFound = false; 1060 | for (int h = 0; h < hands.count(); h++) 1061 | { 1062 | Hand &hand = hands[h]; 1063 | 1064 | // controller #0 is supposed to be the left hand, controller #1 the right one. 1065 | if (hand.isValid() && (m_nId == LEFT_CONTROLLER && hand.isLeft() || 1066 | m_nId == RIGHT_CONTROLLER && hand.isRight())) 1067 | { 1068 | handFound = true; 1069 | 1070 | // The "driver" coordinate system is the one that vecPosition is in. This is whatever 1071 | // coordinates the driver naturally produces for position and orientation. The "world" 1072 | // coordinate system is the one that is presented to vrserver. This should include 1073 | // fixing any tilt to the world (caused by a tilted camera, for example) and can include 1074 | // any other useful transformation for the driver (e.g. the driver is tracking from a 1075 | // secondary camera, but uses this transform to move this object into the primary camera 1076 | // coordinate system to be consistent with other objects). 1077 | // 1078 | // This transform is multiplied on the left of the predicted "driver" pose. That becomes 1079 | // the vr::TrackingUniverseRawAndUncalibrated origin, which is then further offset for 1080 | // floor height and tracking space center by the chaperone system to produce both the 1081 | // vr::TrackingUniverseSeated and vr::TrackingUniverseStanding spaces. 1082 | // 1083 | // In the leap driver, we use it to unify our coordinate system with the HMD. 1084 | m_Pose.qWorldFromDriverRotation = m_hmdRot; 1085 | m_Pose.vecWorldFromDriverTranslation[0] = m_hmdPos[0]; 1086 | m_Pose.vecWorldFromDriverTranslation[1] = m_hmdPos[1]; 1087 | m_Pose.vecWorldFromDriverTranslation[2] = m_hmdPos[2]; 1088 | 1089 | // The "head" coordinate system defines a natural point for the object. While the "driver" 1090 | // space may be chosen for mechanical, eletrical, or mathematical convenience (e.g. being 1091 | // the location of the IMU), the "head" should be a point meaningful to the user. For HMDs, 1092 | // it's the point directly between the user's eyes. The origin of this coordinate system 1093 | // is the origin used for the rendermodel. 1094 | // 1095 | // This transform is multiplied on the right side of the "driver" pose. 1096 | // 1097 | // This transform was inadvertently left at identity for the GDC 2015 controllers, creating 1098 | // a defacto standard "head" position for controllers at the location of the IMU for that 1099 | // particular controller. We will remedy that later by adding other, explicitly named and 1100 | // chosen spaces. For now, mimicking that point in this driver lets us run content authored 1101 | // for the HTC Vive Developer Edition controller. This was done by loading an existing 1102 | // controller rendermodel along side the Leap model and rotating the Leap model to roughly 1103 | // align the main features like the handle and trigger. 1104 | m_Pose.qDriverFromHeadRotation.w = 1; 1105 | m_Pose.qDriverFromHeadRotation.x = 0; // -m_hmdRot.x; this would cancel out the HMD's rotation 1106 | m_Pose.qDriverFromHeadRotation.y = 0; // -m_hmdRot.y; but instead we rely on the Leap Motion to 1107 | m_Pose.qDriverFromHeadRotation.z = 0; // -m_hmdRot.z; update the hand rotation as the head rotates 1108 | m_Pose.vecDriverFromHeadTranslation[0] = 0; 1109 | m_Pose.vecDriverFromHeadTranslation[1] = 0; 1110 | m_Pose.vecDriverFromHeadTranslation[2] = 0; 1111 | 1112 | Vector position = hand.palmPosition(); 1113 | 1114 | m_Pose.vecPosition[0] = -0.001*position.x; 1115 | m_Pose.vecPosition[1] = -0.001*position.z; 1116 | m_Pose.vecPosition[2] = -0.001*position.y - 0.15; // assume 15 cm offset from midpoint between eys 1117 | 1118 | Vector velocity = hand.palmVelocity(); 1119 | 1120 | m_Pose.vecVelocity[0] = -0.001*velocity.x; 1121 | m_Pose.vecVelocity[1] = -0.001*velocity.z; 1122 | m_Pose.vecVelocity[2] = -0.001*velocity.y; 1123 | 1124 | // Unmeasured. XXX we currently leave the acceleration at zero 1125 | m_Pose.vecAcceleration[0] = 0.0; 1126 | m_Pose.vecAcceleration[1] = 0.0; 1127 | m_Pose.vecAcceleration[2] = 0.0; 1128 | 1129 | // get two vectors describing the hand's orientation in space. We need to find a rotation 1130 | // matrix that turns the default coordinate system into the hand's coordinate system 1131 | Vector direction = hand.direction(); direction /= direction.magnitude(); 1132 | Vector normal = hand.palmNormal(); normal /= normal.magnitude(); 1133 | Vector side = direction.cross(normal); 1134 | 1135 | #if 0 1136 | // This code assumes palms are facing downwards. 1137 | 1138 | // NOTE: y and z are swapped with respect to the Leap Motion's coordinate system and I list 1139 | // the vectors in the order in which I expect them to be in the tracking camera's 1140 | // coordinates system: X = sideways, 1141 | // Y = up/down i.e. palm's normal vector 1142 | // Z = front/back i.e. hand's pointing direction 1143 | m_Pose.qRotation = CalculateRotation(R); 1144 | 1145 | float R[3][3] = 1146 | { { side.x, side.z, side.y }, 1147 | { normal.x, normal.z, normal.y }, 1148 | { direction.x, direction.z, direction.y } }; 1149 | 1150 | #else 1151 | // This code assumes palms are facing inwards as if you were holding controllers. 1152 | // This is why the left hand and the 1153 | // right hands have to use different matrices to compute their rotations. 1154 | 1155 | float L[3][3] = 1156 | { {-normal.x, -normal.z, -normal.y }, 1157 | { side.x, side.z, side.y }, 1158 | { direction.x, direction.z, direction.y } }; 1159 | 1160 | float R[3][3] = 1161 | { { normal.x, normal.z, normal.y }, 1162 | {-side.x, -side.z, -side.y }, 1163 | { direction.x, direction.z, direction.y } }; 1164 | 1165 | // now turn this into a Quaternion and we're done. 1166 | if (m_nId == LEFT_CONTROLLER) 1167 | m_Pose.qRotation = CalculateRotation(L); 1168 | else if (m_nId == RIGHT_CONTROLLER) 1169 | m_Pose.qRotation = CalculateRotation(R); 1170 | 1171 | #endif 1172 | // rotate by the specified grip angle (may be useful when using the Vive as a gun grip) 1173 | if (m_gripAngleOffset != 0) 1174 | m_Pose.qRotation = rotate_around_axis(Vector(1.0, 0.0, 0.0), m_gripAngleOffset) * m_Pose.qRotation; 1175 | 1176 | // Unmeasured. XXX with no angular velocity, throwing might not work in some games 1177 | m_Pose.vecAngularVelocity[0] = 0.0; 1178 | m_Pose.vecAngularVelocity[1] = 0.0; 1179 | m_Pose.vecAngularVelocity[2] = 0.0; 1180 | 1181 | // The same argument applies here as to vecAcceleration, and a driver is even 1182 | // less likely to have a valid value for it (since gyros measure angular velocity) 1183 | m_Pose.vecAngularAcceleration[0] = 0.0; 1184 | m_Pose.vecAngularAcceleration[1] = 0.0; 1185 | m_Pose.vecAngularAcceleration[2] = 0.0; 1186 | 1187 | // this results in the controllers being shown on screen 1188 | m_Pose.result = vr::TrackingResult_Running_OK; 1189 | 1190 | // the pose validity also depends on HMD tracking data sent to us by the leap_monitor.exe 1191 | m_Pose.poseIsValid = m_bCalibrated; 1192 | } 1193 | } 1194 | 1195 | if (!handFound) 1196 | { 1197 | m_Pose.result = vr::TrackingResult_Running_OutOfRange; 1198 | m_Pose.poseIsValid = false; 1199 | } 1200 | 1201 | // This is very hard to know with this driver, but CServerDriver_Leap::ThreadFunc 1202 | // tries to reduce latency as much as possible. There is processing in the Leap Motion SDK, 1203 | // though, which causes additional unknown latency. This time is used to know how much 1204 | // extrapolation (via velocity and angular velocity) should be done when predicting poses. 1205 | m_Pose.poseTimeOffset = -0.016f; 1206 | 1207 | // when we get here, the Leap Motion is connected 1208 | m_Pose.deviceIsConnected = true; 1209 | 1210 | // These should always be false from any modern driver. These are for Oculus DK1-like 1211 | // rotation-only tracking. Support for that has likely rotted in vrserver. 1212 | m_Pose.willDriftInYaw = false; 1213 | m_Pose.shouldApplyHeadModel = false; 1214 | 1215 | // This call posts this pose to shared memory, where all clients will have access to it the next 1216 | // moment they want to predict a pose. 1217 | m_pDriverHost->TrackedDevicePoseUpdated(m_unSteamVRTrackedDeviceId, m_Pose); 1218 | } 1219 | 1220 | bool CLeapHmdLatest::IsActivated() const 1221 | { 1222 | return m_unSteamVRTrackedDeviceId != vr::k_unTrackedDeviceIndexInvalid; 1223 | } 1224 | 1225 | bool CLeapHmdLatest::HasControllerId( int nBase, int nId ) 1226 | { 1227 | return nBase == m_nBase && nId == m_nId; 1228 | } 1229 | 1230 | /** Process sixenseControllerData. Return true if it's new to help caller manage sleep durations */ 1231 | bool CLeapHmdLatest::Update(Frame &frame) 1232 | { 1233 | UpdateTrackingState(frame); 1234 | UpdateControllerState(frame); 1235 | 1236 | return true; 1237 | } 1238 | 1239 | // Alignment of the coordinate system of driver_leap with the HMD: 1240 | void CLeapHmdLatest::RealignCoordinates( CLeapHmdLatest * pLeapA, CLeapHmdLatest * pLeapB ) 1241 | { 1242 | if ( pLeapA->m_unSteamVRTrackedDeviceId == vr::k_unTrackedDeviceIndexInvalid ) 1243 | return; 1244 | 1245 | pLeapA->m_pAlignmentPartner = pLeapB; 1246 | pLeapB->m_pAlignmentPartner = pLeapA; 1247 | 1248 | // Ask leap_monitor to tell us HMD pose 1249 | static vr::VREvent_Data_t nodata = { 0 }; 1250 | pLeapA->m_pDriverHost->VendorSpecificEvent( pLeapA->m_unSteamVRTrackedDeviceId, 1251 | (vr::EVREventType) (vr::VREvent_VendorSpecific_Reserved_Start + 0), nodata, 1252 | -std::chrono::duration_cast( k_TrackingLatency ).count() ); 1253 | } 1254 | 1255 | // leap_monitor called us back with the HMD information 1256 | void CLeapHmdLatest::FinishRealignCoordinates(float(*m)[3], float *v ) 1257 | { 1258 | CLeapHmdLatest * pLeapA = this; 1259 | CLeapHmdLatest * pLeapB = m_pAlignmentPartner; 1260 | 1261 | if ( !pLeapA || !pLeapB ) 1262 | return; 1263 | 1264 | vr::HmdQuaternion_t q = CalculateRotation(m); 1265 | pLeapA->UpdateHmdPose(v, q); 1266 | pLeapB->UpdateHmdPose(v, q); 1267 | } 1268 | 1269 | void CLeapHmdLatest::UpdateHmdPose(float *v, vr::HmdQuaternion_t q) 1270 | { 1271 | memcpy(m_hmdPos, &v[0], sizeof(m_hmdPos)); 1272 | m_hmdRot = q; 1273 | m_bCalibrated = true; 1274 | } 1275 | --------------------------------------------------------------------------------