├── Common ├── VectorCompare.cs ├── VectorCompare.cs.meta ├── VectorTrans.cs └── VectorTrans.cs.meta ├── GesturesDeal ├── BaseDataFilter.meta └── BaseDataFilter │ ├── Data.meta │ ├── Data │ ├── BaseData.cs │ └── BaseData.cs.meta │ ├── HandAndFingersPoint.cs │ ├── HandAndFingersPoint.cs.meta │ ├── LeapDriver.cs │ └── LeapDriver.cs.meta ├── GustersPatch ├── Motion.meta ├── Motion │ ├── FingerClick.cs │ ├── FingerClick.cs.meta │ ├── FingerPinch.cs │ ├── FingerPinch.cs.meta │ ├── FingerUnPinch.cs │ ├── FingerUnPinch.cs.meta │ ├── HandFaceToF.cs │ ├── HandFaceToF.cs.meta │ ├── HandOpenToFist.cs │ ├── HandOpenToFist.cs.meta │ ├── SingleHandOpenTrack.cs │ └── SingleHandOpenTrack.cs.meta ├── State.meta └── State │ ├── FingerMatch.cs │ ├── FingerMatch.cs.meta │ ├── HandBend.cs │ ├── HandBend.cs.meta │ ├── HandFingerPoint.cs │ ├── HandFingerPoint.cs.meta │ ├── HandOpen.cs │ ├── HandOpen.cs.meta │ ├── HandPinchGroup.cs │ └── HandPinchGroup.cs.meta └── README.md /Common/VectorCompare.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | public class VectorCompare 5 | { 6 | static readonly float VerToXAxisDirThreshold = Mathf.PI * 7 / 18;//垂直x轴的方向阈值[70,180-70] 7 | static readonly float VerToYAxisDirThreshold = Mathf.PI * 7 / 18;//垂直y轴方向的阈值[70,180-70] 8 | static readonly float VerToZAxisDirThreshold = Mathf.PI * 7 / 18;//垂直y轴方向的阈值[70,180-70] 9 | 10 | 11 | /// 12 | /// 一个方向是否垂直+x轴 13 | /// 14 | /// 15 | /// 阈值 16 | /// 垂直true 17 | public static bool VerDirToXAxis(Vector dir,float thresholdAdjust = 0f) 18 | { 19 | bool isVer=false; 20 | float radian = dir.AngleTo(Vector.XAxis); 21 | 22 | if( radian > VerToXAxisDirThreshold+thresholdAdjust && 23 | radian < (Mathf.PI-(VerToXAxisDirThreshold+thresholdAdjust))) 24 | { 25 | isVer = true; 26 | } 27 | return isVer; 28 | } 29 | 30 | 31 | public static bool VerDirToYAxis(Vector dir, float thresholdAdjust = 0f) 32 | { 33 | bool isVer = false; 34 | 35 | float radian = dir.AngleTo(Vector.YAxis); 36 | 37 | if( radian > VerToYAxisDirThreshold+thresholdAdjust && 38 | radian < ( Mathf.PI -(VerToYAxisDirThreshold+ thresholdAdjust) )) 39 | { 40 | isVer = true; 41 | } 42 | return isVer; 43 | } 44 | public static bool VerDirToZAxis(Vector dir, float thresholdAdjust = 0f) 45 | { 46 | bool isVer = false; 47 | 48 | float radian = dir.AngleTo(Vector.YAxis); 49 | 50 | if( radian > VerToZAxisDirThreshold+thresholdAdjust && 51 | radian < ( Mathf.PI -(VerToYAxisDirThreshold+ thresholdAdjust) )) 52 | { 53 | isVer = true; 54 | } 55 | return isVer; 56 | } 57 | 58 | //public static float RadianTo 59 | } 60 | -------------------------------------------------------------------------------- /Common/VectorCompare.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0d3e1de4f299464abafcfe434ca61fe 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Common/VectorTrans.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | public class VectorTrans 5 | { 6 | public static Vector3 ToUnityVector3(Vector leapVector) 7 | { 8 | Vector3 unityVector3 = new Vector3( leapVector.x,leapVector.y,-leapVector.z); 9 | return unityVector3; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Common/VectorTrans.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cb140438d66dc440aac77cbbd578da6 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 656d772805deee448b0eef55c6ca380d 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/Data.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 270bbca07a7dbd146a8d17d3293ade67 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/Data/BaseData.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | 5 | //一个手指的数据包含一个指尖点数据和手指根骨的位置数据 6 | public struct FingerData 7 | { 8 | public PointData m_Point;//指尖的位置和指向 9 | public Vector m_Position;//手指根骨的位置,对于拇指来说是Proximal phalanges近端指骨的位置 10 | 11 | public FingerData(PointData pointData, Vector pos) 12 | { 13 | m_Point = pointData; 14 | m_Position = pos; 15 | } 16 | 17 | public FingerData(Vector pointPos, Vector pointDir, Vector pos) 18 | { 19 | m_Point.m_Position = pointPos; 20 | m_Point.m_Direction = pointDir; 21 | m_Position = pos; 22 | } 23 | 24 | public void Set(FingerData fd) 25 | { 26 | m_Point = fd.m_Point; 27 | m_Position = fd.m_Position; 28 | } 29 | } 30 | //一个点的数据,包括方向和位置 31 | public struct PointData 32 | { 33 | public Vector m_Position;//位置 34 | public Vector m_Direction;//方向 35 | 36 | public PointData(Vector pos,Vector dir) 37 | { 38 | m_Position = pos; 39 | m_Direction = dir; 40 | } 41 | 42 | public void Set(PointData pd) 43 | { 44 | m_Position = pd.m_Position; 45 | m_Direction = pd.m_Direction; 46 | } 47 | 48 | public void Set(Vector pos,Vector dir) 49 | { 50 | m_Position = pos; 51 | m_Direction = dir; 52 | } 53 | } 54 | 55 | //先被看到的手 56 | public enum E_HandInAboveView 57 | { 58 | None, 59 | Left, 60 | Right 61 | } -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/Data/BaseData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 003a47f586dabc24ab47a29c554c644b 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/HandAndFingersPoint.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System.Collections.Generic; 5 | using System; 6 | /// 7 | /// 此类为数据的模型,收集Leap中需要的数据,并转换成自己需要的格式 8 | /// 整理手掌和指尖的数据信息 9 | /// [可能的数据结构的优化]:这里用零向量表示无效数据,这个操作在ClearNotExistHandData方法中设定, 10 | /// 但是,在FingerData和使用PointData中增加额外的字段IsVaild来表示会更直接一些。 11 | /// 12 | public class HandAndFingersPoint : MonoBehaviour 13 | { 14 | const int BUFFER_MAX=5; 15 | Controller m_LeapCtrl; 16 | 17 | public E_HandInAboveView m_AboveView = E_HandInAboveView.None; 18 | 19 | //手指-数据 ,[0]表示左手,[1]表示右手 20 | private Dictionary[] m_FingerDatas = new Dictionary[2]; 21 | //buffer,[0]表示左手,[1]表示右手,[,n](n属于0,3,表示第n次缓存) 22 | private Dictionary[,] m_FingerDatasBuffer=new Dictionary[2,BUFFER_MAX]; 23 | private int m_CurBufIndex=0; 24 | //palm 0:左手 和1:右手 25 | private PointData[] m_PalmDatas = new PointData[2]; 26 | 27 | private readonly PointData m_DefaultPointData = new PointData(Vector.Zero, Vector.Zero); 28 | private readonly FingerData m_DefaultFingerData = new FingerData(Vector.Zero,Vector.Zero,Vector.Zero); 29 | 30 | //中间量:不再使用。不能作为一个中间存储变量,它会被改变。 31 | //Dictionary m_FingerDataMiddle = new Dictionary (); 32 | 33 | public Dictionary[] FingerDatas 34 | { 35 | get 36 | { 37 | return m_FingerDatas; 38 | } 39 | } 40 | 41 | public PointData[] PalmDatas 42 | { 43 | get 44 | { 45 | return m_PalmDatas; 46 | } 47 | } 48 | 49 | void AddDefaultPalmsData() 50 | { 51 | m_PalmDatas [0]=m_DefaultPointData; 52 | m_PalmDatas [1]=m_DefaultPointData; 53 | } 54 | 55 | /// 56 | /// 设定手指的 57 | /// 58 | void AddDefaultFingersData() 59 | { 60 | DicAddDefaultData(m_FingerDatas [0]) ; 61 | DicAddDefaultData(m_FingerDatas [1]) ; 62 | for(int i=0;i<2;i++) 63 | { 64 | for(int j=0;j 72 | /// 初始化Dictionary的值。 73 | /// 74 | /// Dic. 75 | void DicAddDefaultData(Dictionary dic) 76 | { 77 | dic.Add (Finger.FingerType.TYPE_INDEX,m_DefaultFingerData); 78 | dic.Add (Finger.FingerType.TYPE_MIDDLE,m_DefaultFingerData); 79 | dic.Add (Finger.FingerType.TYPE_PINKY,m_DefaultFingerData); 80 | dic.Add (Finger.FingerType.TYPE_RING,m_DefaultFingerData); 81 | dic.Add (Finger.FingerType.TYPE_THUMB,m_DefaultFingerData); 82 | } 83 | 84 | /// 85 | /// 设定Dictionary的值为默认值。 86 | /// 87 | /// Dic. 88 | void DicUseDefaultData(Dictionary dic) 89 | { 90 | dic[Finger.FingerType.TYPE_INDEX]=m_DefaultFingerData; 91 | dic[Finger.FingerType.TYPE_MIDDLE]=m_DefaultFingerData; 92 | dic[Finger.FingerType.TYPE_PINKY]=m_DefaultFingerData; 93 | dic[Finger.FingerType.TYPE_RING]=m_DefaultFingerData; 94 | dic[Finger.FingerType.TYPE_THUMB]=m_DefaultFingerData; 95 | } 96 | 97 | void Awake() 98 | { 99 | ////分配空间 100 | m_FingerDatas[0] = new Dictionary(); 101 | m_FingerDatas[1] = new Dictionary(); 102 | 103 | for (int i = 0; i < 2; i++) 104 | { 105 | for (int j = 0; j < BUFFER_MAX; j++) 106 | { 107 | m_FingerDatasBuffer[i, j] = new Dictionary(); 108 | } 109 | } 110 | 111 | //制作默认数据 112 | //DicAddDefaultData (m_FingerDataMiddle); 113 | 114 | } 115 | 116 | void Start() 117 | { 118 | m_LeapCtrl = LeapDriver.GetLeapCtrl (); 119 | } 120 | 121 | void Update() 122 | { 123 | //清除(确切说是填充)不存在手掌信息数据。因为手没有被检测到,但这时数据需要更新。所以有这样一个填充判断和控制。 124 | ClearNotExistHandData (); 125 | 126 | //掌心信息 127 | SavePalmsFrame (); 128 | 129 | //手指信息 130 | SaveFingersFrame(); 131 | 132 | 133 | //记录先进入视野的手,记录到m_AboveView中 134 | SaveAboveViewHand(); 135 | } 136 | 137 | void FixedUpdate() 138 | { 139 | ShowBaxis (); 140 | //显示 141 | ShowAllFingersData (); 142 | // ShowIndexFinger(); 143 | //测试数据的可用性 144 | //TestFingerData (); 145 | } 146 | 147 | /// 148 | /// 记录第一次进入检测的手是左手还是右手. 149 | /// 1.枚举为None时 150 | /// 1.1检测到一只手,指定为该手 151 | /// 1.2检测到两只手,指定[0]下标 152 | /// 1.3检测到0只手,指定None 153 | /// 2.枚举为Right/Legt时 154 | /// 2.1检测到1只手,指定为该手 155 | /// 2.2检测到2之手,不变 156 | /// 2.3检测到0只收,设定为None 157 | /// 158 | void SaveAboveViewHand() 159 | { 160 | Frame frame = m_LeapCtrl.Frame(); 161 | HandList hands = frame.Hands; 162 | int count = hands.Count; 163 | 164 | if(m_AboveView== E_HandInAboveView.None) 165 | { 166 | if(count ==1||count==2) 167 | { 168 | bool isRight = hands[0].IsRight; 169 | if(isRight) 170 | { 171 | m_AboveView = E_HandInAboveView.Right; 172 | } 173 | else 174 | { 175 | m_AboveView = E_HandInAboveView.Left; 176 | } 177 | } 178 | } 179 | else 180 | { 181 | if(count==1) 182 | { 183 | bool isRight = hands[0].IsRight; 184 | if(isRight) 185 | { 186 | m_AboveView = E_HandInAboveView.Right; 187 | } 188 | else 189 | { 190 | m_AboveView = E_HandInAboveView.Left; 191 | } 192 | } 193 | else if(count==0) 194 | { 195 | m_AboveView = E_HandInAboveView.None; 196 | } 197 | } 198 | } 199 | /// 200 | /// 保存五个手指的信息。 201 | /// 202 | void SaveFingersFrame() 203 | { 204 | Frame frame = m_LeapCtrl.Frame (); 205 | 206 | HandList hands = frame.Hands; 207 | 208 | foreach ( var hand in hands ) 209 | { 210 | SaveFingerDataWithHandIndex( hand ); 211 | } 212 | 213 | m_CurBufIndex=(m_CurBufIndex+1)%(BUFFER_MAX-1); 214 | } 215 | 216 | /// 217 | /// 保存手的信息到指定的缓存中,也保存在当前数据中 218 | /// 219 | /// Hand index. 220 | /// Hand. 221 | /// Current buffer index. 222 | void SaveFingerDataWithHandIndex(Hand hand) 223 | { 224 | //做空判断好恶心,既然要求传入一个Hand,为什么你要传入一个null呢。 225 | //这里不做null的判断也不会有问题 226 | //if (hand != null) 227 | //{ 228 | bool isLeft = hand.IsLeft; 229 | int handIndex = isLeft ? 0 : 1; 230 | 231 | foreach (Finger finger in hand.Fingers) 232 | { 233 | Finger.FingerType fingerType = finger.Type(); 234 | 235 | Vector fingerDir = finger.Direction; 236 | 237 | // Bone bone = finger.Bone(Bone.BoneType.TYPE_DISTAL); 238 | // Vector distalPos = bone.Center; 239 | Vector distalPos = finger.TipPosition; 240 | //记录根骨位置 241 | Vector metacarpalPos = finger.Bone(Bone.BoneType.TYPE_METACARPAL).Center; 242 | 243 | //如果是拇指,用近端骨指的位置代替 244 | if (finger.Type()==Finger.FingerType.TYPE_THUMB) 245 | { 246 | metacarpalPos = finger.Bone(Bone.BoneType.TYPE_PROXIMAL).Center; 247 | } 248 | 249 | //将数据保存到m_FingerDatas中,以及buffer中。 250 | FingerData fingerData = new FingerData(distalPos,fingerDir, metacarpalPos); 251 | 252 | //Vector3 fingerPos = VectorTrans.ToUnityVector3(fingerData.m_Position); 253 | //s Vector3 fingerDistalPos = VectorTrans.ToUnityVector3(fingerData.m_Point.m_Position); 254 | SaveFingerData(handIndex, fingerType, fingerData); 255 | } 256 | //} 257 | } 258 | 259 | /// 260 | /// 保存指定的手指到指定的位置中 261 | /// 262 | /// 索引号 0表示左手 263 | /// 264 | /// 手指信息 265 | void SaveFingerData(int handIndex, 266 | Finger.FingerType fingerType, 267 | FingerData fingerData) 268 | { 269 | //将data保存或者覆盖到m_FingerDatas中 270 | if(m_FingerDatas[handIndex].ContainsKey(fingerType)) 271 | { 272 | m_FingerDatas[handIndex][fingerType] = fingerData; 273 | } 274 | else 275 | { 276 | m_FingerDatas[handIndex].Add(fingerType, fingerData); 277 | } 278 | 279 | //保存或者覆盖到buffer中 280 | if(m_FingerDatasBuffer[handIndex,m_CurBufIndex].ContainsKey(fingerType)) 281 | { 282 | m_FingerDatasBuffer[handIndex, m_CurBufIndex][fingerType] = fingerData; 283 | } 284 | else 285 | { 286 | m_FingerDatasBuffer[handIndex, m_CurBufIndex].Add(fingerType, fingerData); 287 | } 288 | } 289 | 290 | /// 291 | /// 获取掌心的位置和法向量,记录到m_PalmDatas中 292 | /// 293 | void SavePalmsFrame() 294 | { 295 | Frame frame = m_LeapCtrl.Frame (); 296 | 297 | //将左手的信息记录到0中,将右手的信息记录到1中 298 | foreach (var hand in frame.Hands) 299 | { 300 | if (hand.IsLeft) 301 | { 302 | m_PalmDatas[0].m_Position = hand.PalmPosition; 303 | m_PalmDatas[0].m_Direction = hand.PalmNormal; 304 | } 305 | else if(hand.IsRight) 306 | { 307 | m_PalmDatas[1].m_Position = hand.PalmPosition; 308 | m_PalmDatas[1].m_Direction = hand.PalmNormal; 309 | } 310 | } 311 | } 312 | 313 | /// 314 | /// 让不存在手对应的记录,设置为DefaultData 315 | /// 会在Update中调用,这个方法只是功能性单纯的封装。并没有自己的层次结构 316 | /// 317 | void ClearNotExistHandData() 318 | { 319 | Frame frame = m_LeapCtrl.Frame (); 320 | HandList hands = frame.Hands; 321 | int count = hands.Count; 322 | 323 | //筛选出不存在的手掌,并将这个手掌的信息设定为空 324 | //一个手掌,或零个手掌 325 | if (count == 0) 326 | { 327 | //将[0],[1]索引的数据都清空 328 | for(int i=0;i<2;i++) 329 | { 330 | ClearTheHandData(i); 331 | } 332 | } 333 | //1个手掌 334 | else if(count==1) 335 | { 336 | //查找哪个手掌是没有被检测到 337 | int emptyHandIndex = hands[0].IsLeft?//存在的是左手 338 | 1://空的是右手,返回1 339 | 0;//空的是左手,返回0 340 | ClearTheHandData(emptyHandIndex); 341 | } 342 | } 343 | /// 344 | /// 清除一个手掌的信息 345 | /// 包括:1.手掌信息 346 | /// 2.当前五指信息 347 | /// 3.缓存五指信息 348 | /// 349 | /// 参数:0表示左手,1表示右手 350 | /// 351 | void ClearTheHandData(int handIndex) 352 | { 353 | //清除手掌 354 | m_PalmDatas [handIndex].Set (m_DefaultPointData); 355 | 356 | //清除 当前的五指信息。也可遍历所有含有的键值对 357 | //没有找到很好改变值的方式,如果每次判断isContainsKey,然后在改变值为Default,则需要遍历5次虽然节省了内存,但是增加的Cpu的处理时间. 358 | //清空后,在需要的时候产生一个新对象。 359 | m_FingerDatas[handIndex].Clear(); 360 | 361 | //清除 当前缓存的信息 362 | m_FingerDatasBuffer [handIndex, m_CurBufIndex].Clear(); 363 | 364 | } 365 | 366 | /// 367 | /// 获取手指的缓存数据,可能为空。本来想在运动的时候检测,但考虑到抖动因素,所以决定由动作本身保存缓存数据 368 | /// 369 | /// 370 | public Dictionary GetPreviousBufFingerData(int handIndex) 371 | { 372 | //Dictionary a; 373 | int preBufIndex = (m_CurBufIndex-1) < 0?BUFFER_MAX:m_CurBufIndex-1;//3->2,1->0,0->Max-1 374 | return m_FingerDatasBuffer[handIndex,preBufIndex]; 375 | } 376 | 377 | //___________________Test_______________ 378 | /// 379 | /// test Hand.Direction, 380 | /// 该方法只有在手掌平摊伸直时有意义。 381 | /// 382 | void HandDirection() 383 | { 384 | Frame frame = m_LeapCtrl.Frame (); 385 | Hand hand = frame.Hands [0]; 386 | Vector3 v1 = VectorTrans.ToUnityVector3 (hand.PalmPosition); 387 | Vector3 v2 = VectorTrans.ToUnityVector3 (hand.Direction); 388 | Debug.DrawRay (v1, 389 | v2*100, 390 | Color.red); 391 | print (v1+","+v2); 392 | } 393 | 394 | /// 395 | /// 显示Vector的方向和大小 396 | /// 397 | /// Position. 398 | /// Dir. 399 | void ShowTheDatasPosAndDir(Vector pos,Vector dir) 400 | { 401 | UnityEngine.Vector3 unityDir = VectorTrans.ToUnityVector3 (dir); 402 | UnityEngine.Vector3 unityPos = VectorTrans.ToUnityVector3 (pos); 403 | 404 | Debug.DrawRay (unityPos, unityDir*10, Color.blue); 405 | print (unityPos+","+unityDir); 406 | } 407 | 408 | 409 | void ShowBaxis() 410 | { 411 | Debug.DrawRay (Vector3.zero, Vector3.forward*100, Color.blue); 412 | Debug.DrawRay (Vector3.zero, Vector3.right*100, Color.red); 413 | Debug.DrawRay (Vector3.zero, Vector3.up*100, Color.green); 414 | } 415 | 416 | //显示所有手指缓存数据 417 | void ShowAllFingersBufData() 418 | { 419 | 420 | } 421 | 422 | //显示所有手指数据 423 | void ShowAllFingersData() 424 | { 425 | //左右手 426 | for (int i=0; i<2; i++) 427 | { 428 | //遍历所有包含的手指 429 | if(m_FingerDatas[i]!=null) 430 | { 431 | var fingerDataValues = m_FingerDatas[i].Values; 432 | 433 | foreach (FingerData data in fingerDataValues) 434 | { 435 | ShowFingerData(i,m_PalmDatas[i], data); 436 | } 437 | } 438 | } 439 | 440 | } 441 | 442 | //显示单个手指的数据 443 | //1.掌心到手指的连线-红色 444 | //2.手指的方向-蓝色 445 | void ShowFingerData (int handIndex,PointData palmData,FingerData fingerData) 446 | { 447 | Vector3 handPos = VectorTrans.ToUnityVector3 (palmData.m_Position); 448 | Vector3 handDir = VectorTrans.ToUnityVector3 (palmData.m_Direction); 449 | 450 | Vector3 fingerPos = VectorTrans.ToUnityVector3 (fingerData.m_Position); 451 | Vector3 fingerDistalPos = VectorTrans.ToUnityVector3 (fingerData.m_Point.m_Position); 452 | Vector3 fingerDir = VectorTrans.ToUnityVector3 (fingerData.m_Point.m_Direction); 453 | 454 | Debug.DrawLine (handPos,fingerPos,Color.red); 455 | Debug.DrawLine(fingerPos, fingerDistalPos, Color.red); 456 | //手心到指尖的连线 457 | Debug.DrawLine(handPos, fingerDistalPos, Color.yellow); 458 | 459 | float vecLength = (fingerPos - fingerDistalPos).magnitude; 460 | Debug.DrawRay(fingerDistalPos, fingerDir * vecLength * 0.1f, Color.blue); 461 | Debug.DrawRay(handPos, handDir * vecLength * 0.1f, Color.blue); 462 | } 463 | 464 | //显示食指的骨根位置,近端位置,和指尖的位置及其连线 465 | void ShowIndexFinger() 466 | { 467 | Vector3 pos0=Vector3.zero; 468 | Vector3 pos1=Vector3.zero; 469 | Vector3 pos2=Vector3.zero; 470 | HandList hs= m_LeapCtrl.Frame().Hands; 471 | foreach(var f in hs.Leftmost.Fingers) 472 | { 473 | if(f.Type()==Finger.FingerType.TYPE_INDEX) 474 | { 475 | pos0 = VectorTrans.ToUnityVector3( f.Bone(Bone.BoneType.TYPE_METACARPAL).Center); 476 | pos1 = VectorTrans.ToUnityVector3(f.Bone(Bone.BoneType.TYPE_PROXIMAL).Center); 477 | pos2 = VectorTrans.ToUnityVector3(f.Bone(Bone.BoneType.TYPE_DISTAL).Center); 478 | } 479 | } 480 | Debug.DrawLine(pos0, pos1, Color.red); 481 | Debug.DrawLine(pos0, pos2, Color.yellow); 482 | Debug.DrawLine(pos1, pos2, Color.green); 483 | } 484 | 485 | ///适配性检测 486 | /// 观察手指可用的行为 487 | void TestVaildData() 488 | { 489 | 490 | } 491 | 492 | /// 493 | /// 检测指定手指的可用性 494 | /// 495 | void TestFingerData() 496 | { 497 | //左右手 498 | for (int i=0; i<2; i++) 499 | { 500 | //遍历所有包含的手指 501 | if(m_FingerDatas[i]!=null) 502 | { 503 | //获取index手指的数据 504 | //PointData? data = GetDicValue(m_FingerDatas[i],Finger.FingerType.TYPE_INDEX); 505 | //if(data.HasValue) 506 | //{ 507 | // PointData trueData=(PointData)data; 508 | 509 | //} 510 | 511 | } 512 | } 513 | } 514 | 515 | ///获取dic中的数据 516 | PointData? GetDicValue(Dictionary dic,Finger.FingerType fingerType) 517 | { 518 | if (dic.ContainsKey (fingerType)) 519 | return dic [fingerType]; 520 | else 521 | return null; 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/HandAndFingersPoint.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aaa8605d276774f478236ddeba009797 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/LeapDriver.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | 5 | /// 6 | /// 关联LeapMotion驱动,分配内存空间,创建接口 7 | /// 8 | public sealed class LeapDriver 9 | { 10 | private static Controller leapController=null; 11 | public static Controller GetLeapCtrl() 12 | { 13 | if (leapController == null) { 14 | leapController = new Controller (); 15 | } 16 | return leapController; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GesturesDeal/BaseDataFilter/LeapDriver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95b7e8ec80834774991164d82724ce44 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a6d032c5dff334d458adbc466a818c33 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerClick.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | using Leap; 5 | 6 | /// 7 | /// 1.当判定手指为FingerPoint姿势并且手指放置在相应的碰撞层上(按钮上)时,进入初始状态。 8 | /// 2.每隔0.2s判定手指-z轴的偏移量。 9 | /// 3.当达到前进偏移阈值(深度阈值)时,进入等待反弹状态。 10 | /// 4.反弹状态的检测根据z轴的偏移量增加而决定 11 | /// 5.检测到反弹后,继续检测反弹阈值,如果超过反弹阈值,则触发点击完成事件m_OnClicked. 12 | /// 13 | public class FingerClick : MonoBehaviour 14 | { 15 | [SerializeField] HandFingerPoint m_FingerPoint; 16 | [SerializeField] HandAndFingersPoint m_HandData; 17 | int m_CollisionLayer;//碰撞的层 18 | Action m_OnClicked;//完成点击事件 19 | 20 | Action m_OnEnterClick;//进入点击模式 21 | readonly float EnterDelayTime=0.2f;//进入点击模式后延迟多长多长事件触发 22 | 23 | //Motion 24 | readonly float CheckTimeStep = 0.1f;//运动状态检测的时间差。 25 | Vector m_PreviousPos = Vector.Zero;//上一次检测的位置 26 | Vector m_Off = Vector.Zero; 27 | //threshold 28 | readonly float DeepThreshold=25f;//向前偏移的深度阈值 29 | readonly float BackThreshold=16f;//返回时的反弹阈值 30 | readonly float ForwordPointThreshold=Mathf.PI *5 /18;//向前指向的阈值 31 | 32 | //点击的持续时间,点击的速度 33 | [SerializeField] float m_DeepClickingTime;//进入点击状态后,手指向前移动持续的时间 34 | [SerializeField] 35 | float m_BackClickingTime;//进入反弹状态后,手指返回移动持续的时间 36 | [SerializeField] 37 | float m_ClickSpeed;//点击时候的速度 38 | 39 | //手势识别中的几个状态节点 40 | [SerializeField] 41 | bool m_IsEnteredClick;//进入了识别状态 42 | [SerializeField] 43 | bool m_IsEnteredBackClick;//进入了反弹状态 44 | Action m_OnBackFunc; 45 | 46 | Vector m_EnterPos;//初始状态指尖的位置 47 | Vector m_EnterBackPos;//反弹状态的初始位置 48 | 49 | //点击状态点 50 | readonly float DeepClickRadianThreshold = Mathf.PI*30/180;//点击时每帧的方向偏离-z轴的夹角 51 | 52 | public bool IsEnteredClick 53 | { 54 | get 55 | { 56 | return m_IsEnteredClick; 57 | } 58 | } 59 | public bool IsEnteredBackClick 60 | { 61 | get 62 | { 63 | return m_IsEnteredBackClick; 64 | } 65 | } 66 | 67 | public Vector Off 68 | { 69 | get 70 | { 71 | return m_Off; 72 | } 73 | } 74 | //FinishClickAction Msg 75 | //手掌打开状态,事件注册接口 76 | public void RegisterClickedMsg(Action onClicked) 77 | { 78 | if (onClicked != null) 79 | { 80 | m_OnClicked += onClicked; 81 | } 82 | 83 | } 84 | public void CancelClickedMsg(Action onClicked) 85 | { 86 | if(onClicked!=null) 87 | { 88 | m_OnClicked -= onClicked; 89 | } 90 | } 91 | 92 | /// 93 | /// 进入点击状态事件的注册和注销操作 94 | /// 95 | /// 96 | public void RegisterEnterClickMsg(Action onEnterClick) 97 | { 98 | if (onEnterClick != null) 99 | { 100 | m_OnClicked += onEnterClick; 101 | } 102 | } 103 | public void CancelEnterClickMsg(Action onEnterClick) 104 | { 105 | if (onEnterClick != null) 106 | { 107 | m_OnClicked -= onEnterClick; 108 | } 109 | } 110 | 111 | public void RegisterBackClickMsg(Action onBack) 112 | { 113 | if (onBack != null) 114 | { 115 | m_OnBackFunc += onBack; 116 | } 117 | } 118 | public void CancelBackClickMsg(Action onBack) 119 | { 120 | if (onBack != null) 121 | { 122 | m_OnBackFunc -= onBack; 123 | } 124 | } 125 | 126 | // Update is called once per frame 127 | void Update () 128 | { 129 | int clickIndex; 130 | //这里的判定本该分成预备状态、运动状态、终结状态。但是对于点击来讲,这三个状态的手势判定条件都相同(手势相同、阈值相同) 131 | //所以现在都用同一个控制器来表示 132 | 133 | if (EnterClickState(out clickIndex)) 134 | { 135 | //初始状态 136 | if(!m_IsEnteredClick) 137 | { 138 | //标记进入点击状态,记录初始位置 139 | m_IsEnteredClick = true; 140 | m_EnterPos = m_HandData.FingerDatas[clickIndex][Finger.FingerType.TYPE_INDEX].m_Point.m_Position; 141 | m_PreviousPos = m_EnterPos; 142 | 143 | //延迟触发初始事件 144 | StartCoroutine(EnterClickDelay()); 145 | } 146 | //非初始状态 147 | else 148 | //不是反弹状态 149 | if(!m_IsEnteredBackClick) 150 | { 151 | m_DeepClickingTime+=Time.deltaTime; 152 | //是否进入一次位置判定 153 | if (m_DeepClickingTime > CheckTimeStep) 154 | { 155 | m_DeepClickingTime = 0f; 156 | //收集当前手指索引的食指指尖位置数据 157 | Vector indexFingerTipPos = m_HandData.FingerDatas[clickIndex][Finger.FingerType.TYPE_INDEX].m_Point.m_Position; 158 | m_Off = indexFingerTipPos - m_PreviousPos; 159 | 160 | //是向前点击状态,偏移向量与-z轴(Leap坐标系)的夹角小于某个阈值 161 | if(m_Off.AngleTo(-Vector.ZAxis) DeepThreshold ) 167 | { 168 | //标记进入了反弹状态,记录反弹状态的位置 169 | m_IsEnteredBackClick = true; 170 | m_EnterBackPos = indexFingerTipPos; 171 | if(m_OnBackFunc != null) 172 | { 173 | // print("aaa"); 174 | m_OnBackFunc(); 175 | } 176 | } 177 | } 178 | //不是前进状态 179 | else 180 | { 181 | //手势匹配失败,重置变量 182 | ResetClickState(); 183 | } 184 | //更新m_PreviousPos的位置 185 | m_PreviousPos = indexFingerTipPos; 186 | } 187 | } 188 | else //进入了返回状态 189 | { 190 | m_BackClickingTime+=Time.deltaTime; 191 | //进入位置判定 192 | if ( m_BackClickingTime > CheckTimeStep ) 193 | { 194 | m_BackClickingTime = 0f; 195 | Vector indexFingerTipPos = m_HandData.FingerDatas[clickIndex][Finger.FingerType.TYPE_INDEX].m_Point.m_Position; 196 | //是后退状态 197 | if (m_PreviousPos.z < indexFingerTipPos.z) 198 | { 199 | float backDeep = indexFingerTipPos.z - m_EnterBackPos.z; 200 | //满足了反弹阈值 201 | if( backDeep > BackThreshold ) 202 | { 203 | print("Clicked"); 204 | //触发事件,重置状态 205 | if (m_OnClicked!=null) 206 | { 207 | print("Clicked()"); 208 | m_OnClicked(); 209 | } 210 | ResetClickState(); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | //不是手指点击状态 217 | else 218 | { 219 | ResetClickState(); 220 | m_IsEnteredClick = false; 221 | } 222 | } 223 | 224 | /// 225 | /// 进入点击状态后延迟触发一个进入事件 226 | /// 227 | /// 228 | IEnumerator EnterClickDelay() 229 | { 230 | float curDelayTime=0f; 231 | while(curDelayTime 248 | /// 在判定过程中,因为匹配失败或者完成匹配。都会重置点击状态 249 | /// 250 | void ResetClickState() 251 | { 252 | m_DeepClickingTime = 0f; 253 | m_ClickSpeed = 0f; 254 | m_BackClickingTime = 0f; 255 | 256 | m_IsEnteredBackClick = false; 257 | } 258 | 259 | /// 260 | /// 进入预备/运动/终结状态 261 | /// 检测到大致前向方向 262 | /// 263 | /// 选择哪个手进入的预备状态,只有返回true才有效 264 | /// 265 | bool EnterClickState(out int index) 266 | { 267 | bool isEnter = false; 268 | index = -1; 269 | 270 | for (int i = 0; i < 2; i++) 271 | { 272 | //是指向状态 273 | if (m_FingerPoint.IsEnterPointed[i]) 274 | { 275 | Vector dir = m_FingerPoint.Dir[i]; 276 | //是点击手势 277 | if (IsPointAsClickForwardDir(dir, ForwordPointThreshold)) 278 | { 279 | isEnter = true; 280 | index = i; 281 | } 282 | } 283 | } 284 | return isEnter; 285 | } 286 | 287 | /// 288 | /// 判定一个手指指向是否是点击前方 289 | /// 这里阈值的判定与其说是+x轴,倒不如说是距离yz面的夹角程度更直接,但实现方式上选择了+x轴。 290 | /// 291 | /// 292 | /// 表示距离+x的夹角,标准是90度 293 | bool IsPointAsClickForwardDir(Vector dir,float threshold) 294 | { 295 | bool IsPointAsClick = false; 296 | float radian = dir.AngleTo(Vector.Right); 297 | 298 | if (radian > threshold && radian < Mathf.PI - threshold) 299 | { 300 | IsPointAsClick = true; 301 | } 302 | return IsPointAsClick; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerClick.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac6a127ec2a6dd140b2e4dcd884105ec 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerPinch.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | 5 | ///Log1:手势废弃,Leap API不能正确识别手指的前方,尤其是整根手指从骨根弯曲,不明白Finger.Direction的定义。 6 | /// 7 | /// 匹配手指捏住的动作 8 | /// 两种方案: 9 | /// 1.当匹配捏的初始状态后,等待一段时间后,若此刻是捏的终止状态,则说明完成了捏动作 X:决定第二种 10 | /// 2.当匹配初始后,每帧会追踪两手指尖的距离,当距离一直在减小,并最终处于捏的终止状态时,说明完成了捏动作 11 | /// 比较:方式2优点:可以记录每帧的变化时差,以此作为捏的快慢程度量。 12 | /// 方式2缺点:复杂,需要考虑手指的细微抖动所造成的位置比较————解决方案:每隔0.2秒做一次判定,不用协同程序,因为时间太短。 13 | /// 匹配只会涉及一只手,即如果检测到一只手处于捏动作中,那么不会再检测其他手,除非这只手退出了捏状态。 14 | /// 手势匹配细节: 15 | /// 1.捏动作的初始状态: 16 | /// 食指伸直,食指指尖与拇指指尖相距一定距离(初始距离阈值) 17 | /// 涉及变量:Action m_OnEnterPinch;bool m_IsEnteredPinch;const float EnterDisThreshold; 18 | /// 2.捏动作的运动过程 19 | /// 每隔0.2s检测食指和拇指指尖的距离,并记录。以前后两次距离的差值来表示速度 20 | /// 涉及变量:speed:float;m_PreviousDistance:float;const CheckTimeStep:float 21 | /// 3.捏动作结束状态: 22 | /// 食指伸直,食指指尖与拇指指尖相距一定距离(终结距离阈值) 23 | /// 涉及变量:Action m_OnPinched;const float PinchedDisThreshold; 24 | /// 25 | public class FingerPinch : MonoBehaviour 26 | { 27 | //[SerializeField] HandAndFingersPoint m_HandData; 28 | [SerializeField] HandPinchGroup m_HandPinch; 29 | //Enter Pinch 30 | Action m_OnEnterPinch; 31 | readonly float EnterDelayTime = 0.3f; 32 | [SerializeField] bool m_IsEnteredPinch;//是否进入了捏动作的匹配状态,即是否检测到捏的初始状态,并且到现在为止动作也是正常匹配中。 33 | readonly float EnterDisThreshold=80f;//>80mm判定进入Pinch状态 34 | 35 | //On Pinching 36 | [SerializeField] float m_Speed;//当前的两指尖距离-m_PreviousDistance,代表速度。 37 | [SerializeField] float m_PreviousDistance; 38 | float m_CurCheckTime; 39 | readonly float CheckTimeStep = 0.2f;//状态判定的时间间隔————当匹配捏的初始状态后每隔N秒做一次数据处理 40 | 41 | //Pinched 42 | readonly float PinchedDisThreshold=10f;//Leap单位,10mm 43 | Action m_OnPinched;//完成捏动作匹配后,触发事件链。 44 | 45 | 46 | [SerializeField] int m_HandIndex;//当前捏状态所识别的手索引 47 | 48 | // Update is called once per frame 49 | void Update () 50 | { 51 | //没有进入初始状态时判定是否进入了初始状态 52 | if(!m_IsEnteredPinch) 53 | { 54 | //符合 55 | if(EnterPinchState(out m_HandIndex)) 56 | { 57 | m_IsEnteredPinch = true; 58 | m_PreviousDistance = m_HandPinch.Distance[m_HandIndex]; 59 | StartCoroutine(EnterPinchDelay()); 60 | } 61 | } 62 | 63 | //当前处于捏状态中 64 | if ( m_IsEnteredPinch ) 65 | { 66 | //时间判定 67 | m_CurCheckTime+=Time.deltaTime; 68 | if(m_CurCheckTime>CheckTimeStep) 69 | { 70 | m_CurCheckTime = 0f; 71 | 72 | float curDis = m_HandPinch.Distance[m_HandIndex]; 73 | //依然处于捏动作过程中 74 | if( curDis < m_PreviousDistance ) 75 | { 76 | //处于结束状态 77 | if (curDis < PinchedDisThreshold) 78 | { 79 | //触发相应操作 80 | ResetState(); 81 | if (m_OnPinched != null) 82 | { 83 | m_OnPinched(); 84 | } 85 | } 86 | //不是结束状态 87 | else 88 | { 89 | //记录数据 90 | m_Speed = m_PreviousDistance - curDis; 91 | m_PreviousDistance = curDis; 92 | } 93 | } 94 | //不匹配捏动作 95 | else 96 | { 97 | //退出捏动作 98 | ResetState(); 99 | } 100 | } 101 | } 102 | } 103 | 104 | void ResetState() 105 | { 106 | m_CurCheckTime = 0f; 107 | m_Speed = 0f; 108 | m_PreviousDistance = 0f; 109 | m_IsEnteredPinch = false; 110 | m_HandIndex = -1; 111 | } 112 | 113 | /// 114 | /// 延迟发送缩放广播 115 | /// 116 | /// 117 | IEnumerator EnterPinchDelay() 118 | { 119 | float curDelayTime = 0f; 120 | while (curDelayTime < EnterDelayTime) 121 | { 122 | curDelayTime += Time.deltaTime; 123 | yield return null; 124 | } 125 | //进入了点击状态 126 | if (m_IsEnteredPinch) 127 | { 128 | //发送广播 129 | if (m_OnEnterPinch != null) 130 | { 131 | m_OnEnterPinch(); 132 | } 133 | } 134 | } 135 | 136 | /// 137 | /// 初始动作的匹配判定 138 | /// 139 | bool EnterPinchState(out int handIndex) 140 | { 141 | bool isEnterPinch = false; 142 | handIndex = -1; 143 | for (int i = 0; i < 2; i++) 144 | { 145 | if (m_HandPinch.IsHandPinch[i]) 146 | { 147 | //距离大于阈值 148 | if( m_HandPinch.Distance[i] > EnterDisThreshold ) 149 | { 150 | isEnterPinch = true; 151 | handIndex = i; 152 | } 153 | } 154 | } 155 | return isEnterPinch; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerPinch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c78dedfc79dc6d41acb7bde9c35bfda 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerUnPinch.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | 5 | ///Log1:手势废弃,Leap API不能正确识别手指的前方,尤其是整根手指从骨根弯曲,不明白Finger.Direction的定义。 6 | /// 7 | /// 匹配手指捏住的动作 8 | /// 两种方案: 9 | /// 1.当匹配捏的初始状态后,等待一段时间后,若此刻是捏的终止状态,则说明完成了捏动作 X:决定第二种 10 | /// 2.当匹配初始后,每帧会追踪两手指尖的距离,当距离一直在减小,并最终处于捏的终止状态时,说明完成了捏动作 11 | /// 比较:方式2优点:可以记录每帧的变化时差,以此作为捏的快慢程度量。 12 | /// 方式2缺点:复杂,需要考虑手指的细微抖动所造成的位置比较————解决方案:每隔0.2秒做一次判定,不用协同程序,因为时间太短。 13 | /// 匹配只会涉及一只手,即如果检测到一只手处于捏动作中,那么不会再检测其他手,除非这只手退出了捏状态。 14 | /// 手势匹配细节: 15 | /// 1.捏动作的初始状态: 16 | /// 食指伸直,食指指尖与拇指指尖相距一定距离(初始距离阈值) 17 | /// 涉及变量:Action m_OnEnterPinch;bool m_IsEnteredPinch;const float EnterDisThreshold; 18 | /// 2.捏动作的运动过程 19 | /// 每隔0.2s检测食指和拇指指尖的距离,并记录。以前后两次距离的差值来表示速度 20 | /// 涉及变量:speed:float;m_PreviousDistance:float;const CheckTimeStep:float 21 | /// 3.捏动作结束状态: 22 | /// 食指伸直,食指指尖与拇指指尖相距一定距离(终结距离阈值) 23 | /// 涉及变量:Action m_OnPinched;const float PinchedDisThreshold; 24 | /// 25 | public class FingerUnPinch : MonoBehaviour 26 | { 27 | //[SerializeField] HandAndFingersPoint m_HandData; 28 | [SerializeField] 29 | HandPinchGroup m_HandPinch; 30 | //Enter Pinch 31 | Action m_OnEnterUnPinch; 32 | readonly float EnterDelayTime = 0.3f; 33 | [SerializeField] bool m_IsEnteredUnPinch;//是否进入了捏动作的匹配状态,即是否检测到捏的初始状态,并且到现在为止动作也是正常匹配中。 34 | readonly float EnterDisThreshold = 10f;//小于10mm才算进入UnPinch状态 35 | 36 | //On Pinching 37 | [SerializeField] float m_Speed;//当前的两指尖距离-m_PreviousDistance,代表速度。 38 | [SerializeField] float m_PreviousDistance; 39 | float m_CurCheckTime; 40 | readonly float CheckTimeStep = 0.2f;//状态判定的时间间隔————当匹配捏的初始状态后每隔N秒做一次数据处理 41 | 42 | //Pinched 43 | readonly float UnPinchedDisThreshold = 100f;//Leap单位,100mm 44 | Action m_OnUnPinched;//完成捏动作匹配后,触发事件链。 45 | 46 | 47 | [SerializeField] int m_HandIndex;//当前捏状态所识别的手索引 48 | 49 | // Update is called once per frame 50 | void Update() 51 | { 52 | //没有进入初始状态时判定是否进入了初始状态 53 | if (!m_IsEnteredUnPinch) 54 | { 55 | //符合 56 | if (EnterUnPinchState(out m_HandIndex)) 57 | { 58 | m_IsEnteredUnPinch = true; 59 | m_PreviousDistance = m_HandPinch.Distance[m_HandIndex]; 60 | StartCoroutine(EnterUnPinchDelay()); 61 | } 62 | } 63 | 64 | //当前处于捏状态中 65 | if (m_IsEnteredUnPinch) 66 | { 67 | //时间判定 68 | m_CurCheckTime += Time.deltaTime; 69 | if (m_CurCheckTime > CheckTimeStep) 70 | { 71 | m_CurCheckTime = 0f; 72 | 73 | float curDis = m_HandPinch.Distance[m_HandIndex]; 74 | //依然处于捏动作过程中 75 | if (curDis > m_PreviousDistance) 76 | { 77 | //处于结束状态 78 | if (curDis > UnPinchedDisThreshold) 79 | { 80 | //触发相应操作 81 | ResetState(); 82 | if (m_OnUnPinched != null) 83 | { 84 | m_OnUnPinched(); 85 | } 86 | } 87 | //不是结束状态 88 | else 89 | { 90 | //记录数据 91 | m_Speed = curDis - m_PreviousDistance; 92 | m_PreviousDistance = curDis; 93 | } 94 | } 95 | //不匹配捏动作 96 | else 97 | { 98 | //退出捏动作 99 | ResetState(); 100 | } 101 | } 102 | } 103 | } 104 | 105 | void ResetState() 106 | { 107 | m_CurCheckTime = 0f; 108 | m_Speed = 0f; 109 | m_PreviousDistance = 0f; 110 | m_IsEnteredUnPinch = false; 111 | m_HandIndex = -1; 112 | } 113 | 114 | /// 115 | /// 延迟发送缩放广播 116 | /// 117 | /// 118 | IEnumerator EnterUnPinchDelay() 119 | { 120 | float curDelayTime = 0f; 121 | while (curDelayTime < EnterDelayTime) 122 | { 123 | curDelayTime += Time.deltaTime; 124 | yield return null; 125 | } 126 | //进入了点击状态 127 | if (m_IsEnteredUnPinch) 128 | { 129 | //发送广播 130 | if (m_OnEnterUnPinch != null) 131 | { 132 | m_OnEnterUnPinch(); 133 | } 134 | } 135 | } 136 | 137 | /// 138 | /// 初始动作的匹配判定 139 | /// 140 | bool EnterUnPinchState(out int handIndex) 141 | { 142 | bool isEnterUnPinch = false; 143 | handIndex = -1; 144 | for (int i = 0; i < 2; i++) 145 | { 146 | if (m_HandPinch.IsHandPinch[i]) 147 | { 148 | //距离小于阈值 149 | if (m_HandPinch.Distance[i] < EnterDisThreshold) 150 | { 151 | isEnterUnPinch = true; 152 | handIndex = i; 153 | } 154 | } 155 | } 156 | return isEnterUnPinch; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /GustersPatch/Motion/FingerUnPinch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b92c2a508a535f4d9a1cb9946c8c5a7 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion/HandFaceToF.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System; 5 | 6 | /// 7 | /// 判定两只手处于垂直向前状态 8 | /// 记录两只手之间的距离,速度。 9 | /// 10 | public class HandFaceToF : MonoBehaviour 11 | { 12 | [SerializeField] HandOpen m_HandOpen; 13 | 14 | //Update参数 15 | readonly float CheckStepTime = 0.1f; 16 | float m_CurCheckStepTime; 17 | 18 | //初始状态 19 | [SerializeField] bool m_IsEnteredFTF; 20 | Action m_OnEnterFunc; 21 | //运动状态 22 | float m_PreviousDis; 23 | [SerializeField] float m_Speed;//cur-pre.大于0表示放大 24 | //终结状态 25 | Action m_OnEndFunc; 26 | //延迟验证 27 | readonly float EnterDelayTime = 0.2f; 28 | float m_CurEnterDelayTime; 29 | 30 | //手势匹配 31 | readonly float VerToXAxisDirAdjust = -Mathf.PI/18;//垂直x轴的方向调节阈值[60,180-60] 32 | readonly float VerToYAxisDirAdjust = -Mathf.PI/18;//垂直y轴方向的调节阈值[60,180-60] 33 | 34 | public bool IsEntered 35 | { 36 | get 37 | { 38 | return m_IsEnteredFTF; 39 | } 40 | } 41 | public float Speed 42 | { 43 | get 44 | { 45 | return m_Speed; 46 | } 47 | } 48 | public void RegisterFunc(Action m_OnEnter, Action m_OnEnd) 49 | { 50 | if (m_OnEnter != null) 51 | { 52 | m_OnEndFunc += m_OnEnter; 53 | } 54 | if (m_OnEnd != null) 55 | { 56 | m_OnEndFunc += m_OnEnd; 57 | } 58 | } 59 | 60 | public void CancelFunc(Action m_OnEnter, Action m_OnEnd) 61 | { 62 | if (m_OnEnter != null) 63 | { 64 | m_OnEndFunc -= m_OnEnter; 65 | } 66 | if (m_OnEnd != null) 67 | { 68 | m_OnEndFunc -= m_OnEnd; 69 | } 70 | } 71 | 72 | // Update is called once per frame 73 | void Update () 74 | { 75 | m_CurCheckStepTime+=Time.deltaTime; 76 | m_CurEnterDelayTime += Time.deltaTime; 77 | if(m_CurCheckStepTime>CheckStepTime) 78 | { 79 | m_CurCheckStepTime = 0f; 80 | //满足FaceToFace 81 | if(FTFState()) 82 | { 83 | //当前不是进入状态 84 | if(!m_IsEnteredFTF) 85 | { 86 | if(m_CurEnterDelayTime > EnterDelayTime) 87 | { 88 | m_CurEnterDelayTime=0f; 89 | 90 | m_IsEnteredFTF=true; 91 | m_PreviousDis = m_HandOpen.PalmPos[0].DistanceTo(m_HandOpen.PalmPos[1]); 92 | if(m_OnEnterFunc!=null) 93 | { 94 | m_OnEnterFunc(); 95 | } 96 | } 97 | } 98 | //已经进入FTF状态 99 | else 100 | { 101 | //记录数据 102 | float curDis = m_HandOpen.PalmPos[0].DistanceTo(m_HandOpen.PalmPos[1]); 103 | m_Speed = curDis-m_PreviousDis; 104 | m_PreviousDis = curDis; 105 | } 106 | } 107 | //不是FTF 108 | else 109 | { 110 | //这是终结状态 111 | if (m_IsEnteredFTF) 112 | { 113 | if(m_OnEndFunc!=null) 114 | { 115 | m_OnEndFunc(); 116 | } 117 | } 118 | Reset(); 119 | } 120 | } 121 | } 122 | 123 | void Reset() 124 | { 125 | m_IsEnteredFTF=false; 126 | 127 | //运动状态 128 | m_PreviousDis=0f; 129 | m_Speed=0f;//cur-pre.大于0表示放大 130 | m_CurCheckStepTime = 0f; 131 | m_CurEnterDelayTime=0f; 132 | } 133 | 134 | bool FTFState() 135 | { 136 | bool isFTF=false; 137 | if( m_HandOpen.IsEnterOpened[0] && m_HandOpen.IsEnterOpened[1] ) 138 | { 139 | if(VectorCompare.VerDirToXAxis(m_HandOpen.Dir[0],VerToXAxisDirAdjust) && 140 | VectorCompare.VerDirToYAxis(m_HandOpen.PalmDir[0], VerToYAxisDirAdjust) && 141 | VectorCompare.VerDirToXAxis(m_HandOpen.Dir[1],VerToXAxisDirAdjust) && 142 | VectorCompare.VerDirToYAxis(m_HandOpen.PalmDir[1], VerToYAxisDirAdjust)) 143 | { 144 | isFTF = true; 145 | } 146 | } 147 | return isFTF; 148 | } 149 | 150 | /// 151 | /// 一个方向是否垂直+x轴 152 | /// 153 | /// 154 | /// 阈值 155 | /// 垂直true 156 | bool VerDirToXAxis(Vector dir,float threshold) 157 | { 158 | bool isVer=false; 159 | float radian = dir.AngleTo(Vector.XAxis); 160 | 161 | if( radian > threshold && radian < (Mathf.PI-threshold)) 162 | { 163 | isVer = true; 164 | } 165 | return isVer; 166 | } 167 | 168 | bool VerDirToYAxis(Vector dir, float threshold) 169 | { 170 | bool isVer = false; 171 | 172 | float radian = dir.AngleTo(Vector.YAxis); 173 | 174 | if(radian>threshold && radian<(Mathf.PI - threshold)) 175 | { 176 | isVer = true; 177 | } 178 | return isVer; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /GustersPatch/Motion/HandFaceToF.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5384d49a8cb1c764bbe14ec732636422 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion/HandOpenToFist.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | using Leap; 5 | /// 6 | /// 定位:水平前向 7 | /// 从伸手到握拳姿势 8 | /// 匹配:进入初始状态后,经过一个固定时间,判定手处于弯曲状态。表示成立。 9 | /// 之所以不用手指的弯曲程度计算,是因为不准 10 | /// 11 | public class HandOpenToFist : MonoBehaviour 12 | { 13 | [SerializeField] HandBend m_HandBend; 14 | [SerializeField] HandOpen m_HandOpen; 15 | //Update 16 | readonly float CheckStepTime = 0.2f; 17 | float m_CurCheckStepTime; 18 | 19 | //enter 20 | float m_CurEnterDelayTime; 21 | readonly float EnterDelayTimeThrehold=0.3f; 22 | Action m_OnEnterFunc; 23 | [SerializeField] bool m_IsEnteredOpen; 24 | 25 | //中间过程 26 | readonly float WaitBendTimeThreshold=2f;//进入初始状态1s后,若为弯曲状态,则说明动作成立。 27 | 28 | //end 29 | Action m_OnEndFunc; 30 | 31 | //record 32 | float m_PreviousRadian; 33 | 34 | //threshold 35 | readonly float ForwardThreshold=Mathf.PI /9;//指尖方向到-z轴的阈值 36 | readonly float DownThreshold=Mathf.PI /9;//指尖方向到-z轴的阈值 37 | 38 | public void RegisterFunc(Action onEnter,Action onEnd) 39 | { 40 | if(onEnd!=null) 41 | { 42 | m_OnEndFunc+=onEnd; 43 | } 44 | if(onEnter!=null) 45 | { 46 | m_OnEnterFunc+=onEnter; 47 | } 48 | } 49 | 50 | public void CancelFunc(Action onEnter,Action onEnd) 51 | { 52 | if(onEnd!=null) 53 | { 54 | m_OnEndFunc-=onEnd; 55 | } 56 | if(onEnter!=null) 57 | { 58 | m_OnEnterFunc-=onEnter; 59 | } 60 | } 61 | 62 | 63 | // Update is called once per frame 64 | void Update () { 65 | m_CurCheckStepTime += Time.deltaTime; 66 | m_CurEnterDelayTime += Time.deltaTime; 67 | 68 | //每隔一定时间做一次检测 69 | if(m_CurCheckStepTime>CheckStepTime) 70 | { 71 | m_CurCheckStepTime=0f; 72 | //没有进入判定状态 73 | if(!m_IsEnteredOpen) 74 | { 75 | int handIndex; 76 | //是初始状态 77 | if(EnterOpenState(out handIndex)) 78 | { 79 | //满足了延迟时间 80 | if(m_CurEnterDelayTime>EnterDelayTimeThrehold) 81 | { 82 | m_CurEnterDelayTime=0f; 83 | 84 | //设定初始状态 85 | m_IsEnteredOpen=true; 86 | if(m_OnEnterFunc!=null) 87 | { 88 | m_OnEnterFunc(); 89 | } 90 | //等待一个弯曲判定 91 | StartCoroutine(WaitForBendState(WaitBendTimeThreshold,handIndex)); 92 | } 93 | } 94 | //不是初始状态 95 | else 96 | { 97 | m_CurEnterDelayTime=0f; 98 | } 99 | } 100 | } 101 | } 102 | 103 | /// 104 | /// 判断是否进入伸掌状态 105 | /// 没有考虑多只手的检测,影响是,右手有最终判定权。 106 | /// 107 | /// 选定的手的索引,返回true时有效 108 | /// true, if open state was entered, false otherwise. 109 | bool EnterOpenState(out int handIndex) 110 | { 111 | bool isEnter = false; 112 | handIndex = -1; 113 | for(int i=0;i<2;i++) 114 | { 115 | if(m_HandOpen.IsEnterOpened [i]) 116 | { 117 | Vector palmDir = m_HandOpen.PalmDir[i]; 118 | Vector fingerDir = m_HandOpen.Dir[i]; 119 | 120 | //是水平向前的方向 121 | if(IsHorAndForwardOpenHand(palmDir,fingerDir)) 122 | { 123 | isEnter =true; 124 | handIndex = i; 125 | } 126 | } 127 | } 128 | 129 | return isEnter; 130 | } 131 | 132 | /// 133 | /// 经过一段时间后,若指定的手处于弯曲状态,则触发握拳的完成事件,初始化成员变量 134 | /// 135 | /// The for bend state. 136 | /// 经过多长时间后触发 137 | /// 对哪只手进行弯曲状态的匹配 138 | IEnumerator WaitForBendState(float delayTime,int handIndex) 139 | { 140 | float curTime = 0f; 141 | 142 | while (curTime 167 | /// 判定一个摊开手掌的状态是否是水平向前 168 | /// 169 | /// true, if and forward open hand was hored, false otherwise. 170 | bool IsHorAndForwardOpenHand(Vector palmDir,Vector fingerDir ) 171 | { 172 | bool isHorAndForward = false; 173 | if( fingerDir.AngleTo(-Vector.ZAxis) < ForwardThreshold && 174 | palmDir.AngleTo(-Vector.YAxis) < DownThreshold ) 175 | { 176 | isHorAndForward=true; 177 | } 178 | return isHorAndForward; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /GustersPatch/Motion/HandOpenToFist.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c44dad6a3d0300541bc88e37bdc44cbd 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/Motion/SingleHandOpenTrack.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System; 5 | /// 6 | /// 当检测到一直手后,进入追踪状态,记录单手运动的速度,掌心方向。 7 | /// 检测到HandOpen之后,延迟0.5s进入追踪状态。 8 | /// 关于速度:慢速[0,10],快速[25,40],建议以25为上限 9 | /// 10 | public class SingleHandOpenTrack : MonoBehaviour { 11 | [SerializeField] HandOpen m_HandOpen; 12 | Controller leap; 13 | //Update设定 14 | readonly float TrackStepTime = 0.1f; 15 | float m_CurTrackTime; 16 | 17 | //初始状态 18 | [SerializeField] 19 | bool m_IsSingleHandOpened; 20 | readonly float EnterDelayTime = 0.15f;//进入0.5s后触发 21 | float m_CurEnterDelayTime; 22 | Action m_OnEnterFunc; 23 | 24 | //运动状态 25 | [SerializeField] float m_Speed;//手掌运动速度,前后两个位置做差 26 | Vector m_PreviousPos;//前次的位置 27 | Vector m_Dir=Vector.Zero;//手掌运动的方向,CurPos-PrePos 28 | 29 | //结束状态 30 | Action m_OnEndFunc; 31 | public bool IsSingleHandOpened 32 | { 33 | get 34 | { 35 | return m_IsSingleHandOpened; 36 | } 37 | } 38 | public Vector Dir 39 | { 40 | get 41 | { 42 | return m_Dir; 43 | } 44 | } 45 | 46 | public float Speed 47 | { 48 | get 49 | { 50 | return m_Speed; 51 | } 52 | } 53 | 54 | public void RegisterFunc(Action m_OnEnter,Action m_OnEnd) 55 | { 56 | if(m_OnEnter!=null) 57 | { 58 | m_OnEndFunc += m_OnEnter; 59 | } 60 | if(m_OnEnd!=null) 61 | { 62 | m_OnEndFunc += m_OnEnd; 63 | } 64 | } 65 | 66 | public void CancelFunc(Action m_OnEnter, Action m_OnEnd) 67 | { 68 | if (m_OnEnter != null) 69 | { 70 | m_OnEndFunc -= m_OnEnter; 71 | } 72 | if (m_OnEnd != null) 73 | { 74 | m_OnEndFunc -= m_OnEnd; 75 | } 76 | } 77 | void Awake() 78 | { 79 | leap = LeapDriver.GetLeapCtrl(); 80 | } 81 | // Update is called once per frame 82 | void Update () 83 | { 84 | m_CurTrackTime+=Time.deltaTime; 85 | m_CurEnterDelayTime += Time.deltaTime; 86 | 87 | if(m_CurTrackTime>TrackStepTime) 88 | { 89 | m_CurTrackTime = 0f; 90 | //print(leap.Frame().Hands.Count); 91 | if (leap.Frame().Hands.Count == 1) 92 | { 93 | int index; 94 | int number = m_HandOpen.OpenNumber(out index); 95 | //只有一只手被检测到 96 | 97 | //如果只有一只手处于摊开状态 98 | if (number == 1) 99 | { 100 | //第一次伸掌 101 | if (!m_IsSingleHandOpened) 102 | { 103 | //满足延迟时间 104 | if (m_CurEnterDelayTime > EnterDelayTime) 105 | { 106 | m_CurEnterDelayTime = 0f; 107 | 108 | m_IsSingleHandOpened = true; 109 | m_PreviousPos = m_HandOpen.PalmPos[index]; 110 | if (m_OnEndFunc != null) 111 | { 112 | m_OnEndFunc(); 113 | } 114 | } 115 | } 116 | //已经处于伸掌状态 117 | else 118 | { 119 | Vector curDir = m_HandOpen.PalmPos[index]; 120 | m_Dir = curDir - m_PreviousPos; 121 | m_Speed = curDir.DistanceTo(m_PreviousPos); 122 | m_PreviousPos = curDir; 123 | } 124 | } 125 | //不是伸掌状态 126 | else 127 | { 128 | //上一次是伸掌状态,说明这是一次手势的结束,触发结束事件 129 | if (m_IsSingleHandOpened) 130 | { 131 | if (m_OnEndFunc != null) 132 | { 133 | m_OnEndFunc(); 134 | } 135 | } 136 | //初始化数据 137 | Reset(); 138 | } 139 | } 140 | //不是伸掌状态 141 | else 142 | { 143 | //上一次是伸掌状态,说明这是一次手势的结束,触发结束事件 144 | if (m_IsSingleHandOpened) 145 | { 146 | if (m_OnEndFunc != null) 147 | { 148 | m_OnEndFunc(); 149 | } 150 | } 151 | //初始化数据 152 | Reset(); 153 | } 154 | } 155 | 156 | } 157 | /// 158 | /// 重置数据,如果判定失败时调用 159 | /// 160 | void Reset() 161 | { 162 | m_Speed = 0f; 163 | m_PreviousPos = Vector.Zero; 164 | m_Dir = Vector.Zero; 165 | m_IsSingleHandOpened = false; 166 | // m_CurTrackTime = 0f; 167 | m_CurEnterDelayTime = 0f; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /GustersPatch/Motion/SingleHandOpenTrack.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d77c06da365bb8149b6143623075ef83 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/State.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c3f353e0a4acb24f9381216850007db 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /GustersPatch/State/FingerMatch.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | /// 5 | /// 该方法提供对于单个手指匹配的算法,如伸直,弯曲 6 | /// 以后可能的改变:对于不同的场景可能要求有所不同,这里的阈值也许会随之改变 7 | /// 8 | public class FingerMatch 9 | { 10 | //弯曲状态的角度阈值 11 | static readonly float FingerBendState_Radian = Mathf.PI*4f / 18 ;//40度 12 | //伸直状态的角度阈值 13 | static readonly float FingerStrightState_Radian = Mathf.PI/12;//15度 14 | 15 | /// 16 | /// 手指伸直的状态,当根骨-指尖的方向和指向的偏差小于阀值时,判定手指为伸直状态。 17 | /// 注意无效的方向为零向量,先判定是零向量 18 | /// 19 | /// 对阈值做的微调 20 | /// 21 | public static bool StrightState(FingerData fingerData, float adjustBorder=0f) 22 | { 23 | bool isStright =false; 24 | Vector disalDir = fingerData.m_Point.m_Direction; 25 | //如果指尖方向为0向量,表示无效的数据 26 | if (!disalDir.Equals(Vector.Zero)) 27 | { 28 | Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,由指根指向指尖的向量 29 | float radian = fingerDir.AngleTo(disalDir); 30 | 31 | if (radian < FingerStrightState_Radian + adjustBorder) 32 | { 33 | isStright = true; 34 | } 35 | } 36 | return isStright; 37 | } 38 | 39 | /// 40 | /// 判断一根手指是否处于弯曲状态 41 | /// 42 | /// 需要判定的手指数据 43 | /// 弯曲的阈值 44 | /// 45 | public static bool BendState(FingerData fingerData, float adjustBorder=0f)//,out float eulerAugle) 46 | { 47 | bool isBend = false; 48 | 49 | //eulerAugle = -1f; 50 | Vector disalDir = fingerData.m_Point.m_Direction; 51 | if( !disalDir.Equals(Vector.Zero) ) 52 | { 53 | Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;//指尖位置减去指根位置,指跟到指尖的向量 54 | 55 | float radian = fingerDir.AngleTo(disalDir); 56 | //eulerAugle = radian*180/Mathf.PI; 57 | //夹角超过定义的阈值时,认定为弯曲状态 58 | if (radian > FingerBendState_Radian + adjustBorder) 59 | { 60 | isBend = true; 61 | } 62 | } 63 | 64 | return isBend; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /GustersPatch/State/FingerMatch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a702dc512a165544c8a5dba83d7c3a13 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/State/HandBend.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System.Collections.Generic; 5 | using System; 6 | /// 7 | /// 手势:除拇指以外的四指弯曲 8 | /// 匹配:2-3根手指(越接近垂直越难分辨),提供接口,默认识别3个手指,但是如果垂直方向的位置,指定2个手指即可 9 | /// 冲突:该状态与单指模式冲突,解决方案:同一场景下设定优先级 10 | /// 11 | public class HandBend : MonoBehaviour 12 | { 13 | [SerializeField] HandAndFingersPoint m_HandData; 14 | 15 | //开始弯曲和结束弯曲的触发事件,应该有时间淡入淡出设定,并不完善 16 | Action[] m_EnterBendFunc = new Action[2]; 17 | Action[] m_ExitBendFunc = new Action[2]; 18 | 19 | float[] m_BendingTime = new float[2];//此次弯曲的持续时间 20 | [SerializeField] bool[] m_IsEnterBended = new bool[2];//这次伸掌状态打开过 21 | 22 | int m_MatchNumber = 2;//手掌弯曲的状态需要几根手指匹配 23 | //const float FingerBendState_Radian = Mathf.PI*5.5f / 18 ;//55度 24 | 25 | Vector[] m_Dir = new Vector[2];//方向为掌心的指向,非弯曲状态设定为Zero 26 | float[] m_BendRadian = new float[2];//弯曲程度,用中指的弯曲度代替 27 | 28 | public bool[] IsEnterBended 29 | { 30 | get 31 | { 32 | return m_IsEnterBended; 33 | } 34 | } 35 | 36 | public float[] BendingTime 37 | { 38 | get 39 | { 40 | return m_BendingTime; 41 | } 42 | } 43 | 44 | public Vector[] Dir 45 | { 46 | get 47 | { 48 | return m_Dir; 49 | } 50 | } 51 | //手掌打开状态,事件注册接口 52 | public void AddOpenEnterMsg(Action leftOpen, Action rightOpen) 53 | { 54 | if (leftOpen != null) 55 | { 56 | m_EnterBendFunc[0] += leftOpen; 57 | } 58 | 59 | if (rightOpen != null) 60 | { 61 | m_EnterBendFunc[1] += rightOpen; 62 | } 63 | } 64 | 65 | //手掌打开结束状态,事件注册接口 66 | public void AddOpenExitMsg(Action leftOpen, Action rightOpen) 67 | { 68 | if (leftOpen != null) 69 | { 70 | m_ExitBendFunc[0] += leftOpen; 71 | } 72 | 73 | if (rightOpen != null) 74 | { 75 | m_ExitBendFunc[1] += rightOpen; 76 | } 77 | } 78 | 79 | /// 80 | /// set设定的时候限制在2,3两种取值 81 | /// 82 | public int MatchNumber 83 | { 84 | get 85 | { 86 | return m_MatchNumber; 87 | } 88 | set 89 | { 90 | if (value < 2) 91 | m_MatchNumber = 2; 92 | else if (value > 3) 93 | m_MatchNumber = 3; 94 | } 95 | } 96 | // Use this for initialization 97 | void Start () { 98 | 99 | } 100 | 101 | /// 102 | /// 每帧做手掌弯曲的判断 103 | /// 104 | void Update () 105 | { 106 | BendCtrl(m_HandData.FingerDatas,m_HandData.PalmDatas); 107 | } 108 | 109 | void BendCtrl(Dictionary[] dic,PointData[] palmDatas) 110 | { 111 | Vector[] bendDir = new Vector[2]; 112 | bool[] isBend = new bool[2]; 113 | isBend[0] = BendState(dic[0], palmDatas[0], m_MatchNumber, out bendDir[0]); 114 | isBend[1] = BendState(dic[1], palmDatas[1], m_MatchNumber, out bendDir[1]); 115 | 116 | //有几组数据就遍历几次-限1或2次 117 | int count= dic.Length; 118 | for (int handIndex = 0; handIndex < count; handIndex++) 119 | { 120 | //这个if - else 结构依据m_IsEnterBended-isLeftOpen表示的二维坐标系,而出现的四个象限。 121 | //此次左手伸掌状态的最开始,触发 122 | //当不处于m_IsEnterBended状态,并且现在左手是伸掌状态才会触发 123 | if (!m_IsEnterBended[handIndex] && isBend[handIndex]) 124 | { 125 | if (m_EnterBendFunc[handIndex] != null) 126 | { 127 | m_EnterBendFunc[handIndex](); 128 | } 129 | m_BendingTime[handIndex] = 0f; 130 | m_IsEnterBended[handIndex] = true; 131 | 132 | m_Dir[handIndex] = bendDir[handIndex]; 133 | } 134 | 135 | //持续进行左手的伸掌状态 136 | //左手已经进入到伸掌状态,而且现在也是伸掌状态 137 | else if (m_IsEnterBended[handIndex] && isBend[handIndex]) 138 | { 139 | m_BendingTime[handIndex] += Time.deltaTime;//该函数会在Update中调用。 140 | m_Dir[handIndex] = bendDir[handIndex]; 141 | } 142 | 143 | //退出左手伸掌状态,这是此次持续伸掌的最后一瞬间伸掌 144 | //左手已经进入伸掌状态,并且现在不是伸掌状态 145 | else if (m_IsEnterBended[handIndex] && !isBend[handIndex]) 146 | { 147 | if (m_ExitBendFunc[handIndex] != null) 148 | { 149 | m_ExitBendFunc[handIndex](); 150 | } 151 | m_IsEnterBended[handIndex] = false; 152 | m_BendingTime[handIndex] = 0f; 153 | 154 | m_Dir[handIndex] = bendDir[handIndex]; 155 | } 156 | 157 | //本身不是处于伸掌状态,而且此次判断也不是伸掌状态 158 | else 159 | { 160 | m_IsEnterBended[handIndex] = false; 161 | m_BendingTime[handIndex] = 0f; 162 | m_Dir[handIndex] = Vector.Zero; 163 | } 164 | } 165 | } 166 | /// 167 | /// 判定一个手掌是否处于弯曲状态。 168 | /// 手掌中有matchNumber个手指满足即可,实际设定为2-3个 169 | /// 170 | /// 171 | /// 172 | /// 【返回值】:如果匹配成功方向为掌心的方向 173 | /// 是否为弯曲状态 174 | bool BendState( Dictionary dic_FingersData,PointData palmData, int matchNumber,out Vector dir ) 175 | { 176 | bool isBend = false; 177 | dir = Vector.Zero; 178 | int count = 0; 179 | Dictionary fingersOutThumb = new Dictionary(dic_FingersData); 180 | fingersOutThumb.Remove(Finger.FingerType.TYPE_THUMB); 181 | 182 | var values = fingersOutThumb.Values; 183 | 184 | //float eulerAngule = 0f; 185 | //遍历四指,匹配个数满足设定个数认定手掌为弯曲,并且设定弯曲的方向为掌心方向。 186 | foreach (FingerData fingerData in values) 187 | { 188 | if (FingerMatch.BendState(fingerData))//,out eulerAngule)) 189 | { 190 | count++; 191 | } 192 | } 193 | //print ("Bend Count:"+count+" BendEuler:"+eulerAngule); 194 | if (count >= matchNumber) 195 | { 196 | isBend = true; 197 | dir = palmData.m_Direction; 198 | } 199 | 200 | return isBend; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /GustersPatch/State/HandBend.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2c811986a39c3a4eac5c94187ccfbbd 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/State/HandFingerPoint.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// 手势:点击模式,拇指不考虑。食指处于伸直状态,其他三指处于弯曲状态 9 | /// 匹配方案:食指伸直,其他三个手指中有2-3个处于弯曲状态() 10 | /// 11 | public class HandFingerPoint : MonoBehaviour 12 | { 13 | [SerializeField] HandAndFingersPoint m_HandData; 14 | 15 | //开始弯曲和结束弯曲的触发事件,应该有时间淡入淡出设定,并不完善 16 | Action[] m_EnterPointFunc = new Action[2]; 17 | Action[] m_ExitPointFunc = new Action[2]; 18 | 19 | float[] m_PointingTime = new float[2];//此次指向手势的持续时间 20 | [SerializeField] bool[] m_IsEnterPointed = new bool[2];//这次伸掌状态打开过 21 | 22 | //匹配阈值设定 23 | int m_MatchNumber = 2;//手掌弯曲的状态需要几根手指匹配,在观察器 24 | //const float FingerBendState_Radian = Mathf.PI * 7 / 18;//70度 25 | //const float FingerStrightState_Radian = Mathf.PI / 12;//15度 26 | Vector[] m_Dir = new Vector[2];//方向为食指的指向,非弯曲状态设定为Zero 27 | Vector[] m_Pos = new Vector[2];//食指指尖的位置数据 28 | public bool[] IsEnterPointed 29 | { 30 | get 31 | { 32 | return m_IsEnterPointed; 33 | } 34 | } 35 | 36 | public float[] PointingTime 37 | { 38 | get 39 | { 40 | return m_PointingTime; 41 | } 42 | } 43 | 44 | public Vector[] Dir 45 | { 46 | get 47 | { 48 | return m_Dir; 49 | } 50 | } 51 | //手掌打开状态,事件注册接口 52 | public void AddOpenEnterMsg(Action leftOpen, Action rightOpen) 53 | { 54 | if (leftOpen != null) 55 | { 56 | m_EnterPointFunc[0] += leftOpen; 57 | } 58 | 59 | if (rightOpen != null) 60 | { 61 | m_EnterPointFunc[1] += rightOpen; 62 | } 63 | } 64 | 65 | //手掌打开结束状态,事件注册接口 66 | public void AddOpenExitMsg(Action leftOpen, Action rightOpen) 67 | { 68 | if (leftOpen != null) 69 | { 70 | m_ExitPointFunc[0] += leftOpen; 71 | } 72 | 73 | if (rightOpen != null) 74 | { 75 | m_ExitPointFunc[1] += rightOpen; 76 | } 77 | } 78 | 79 | /// 80 | /// set设定的时候限制在2,3两种取值 81 | /// 82 | public int MatchNumber 83 | { 84 | get 85 | { 86 | return m_MatchNumber; 87 | } 88 | set 89 | { 90 | if (value < 2) 91 | m_MatchNumber = 2; 92 | else if (value > 3) 93 | m_MatchNumber = 3; 94 | } 95 | } 96 | 97 | /// 98 | /// 每帧做手掌弯曲的判断 99 | /// 100 | void Update() 101 | { 102 | IndexPointCtrl(m_HandData.FingerDatas); 103 | } 104 | 105 | /// 106 | /// 多个手掌的手指数据是否处于食指指向状态,并对类的属性做出修改 107 | /// 108 | /// 109 | void IndexPointCtrl(Dictionary[] dic) 110 | { 111 | Vector[] indexPointDir = new Vector[2]; 112 | bool[] isIndexPoint = new bool[2]; 113 | isIndexPoint[0] = IndexPointState(dic[0], out indexPointDir[0]); 114 | isIndexPoint[1] = IndexPointState(dic[1], out indexPointDir[1]); 115 | 116 | //有几组数据就遍历几次-限1或2次 117 | int count = dic.Length; 118 | for (int handIndex = 0; handIndex < count; handIndex++) 119 | { 120 | //这个if - else 结构依据m_IsEnterBended-isLeftOpen表示的二维坐标系,而出现的四个象限。 121 | //此次左手伸掌状态的最开始,触发 122 | //当不处于m_IsEnterBended状态,并且现在左手是伸掌状态才会触发 123 | if (!m_IsEnterPointed[handIndex] && isIndexPoint[handIndex]) 124 | { 125 | if (m_EnterPointFunc[handIndex] != null) 126 | { 127 | m_EnterPointFunc[handIndex](); 128 | } 129 | m_PointingTime[handIndex] = 0f; 130 | m_IsEnterPointed[handIndex] = true; 131 | 132 | m_Dir[handIndex] = indexPointDir[handIndex]; 133 | } 134 | 135 | //持续进行左手的伸掌状态 136 | //左手已经进入到伸掌状态,而且现在也是伸掌状态 137 | else if (m_IsEnterPointed[handIndex] && isIndexPoint[handIndex]) 138 | { 139 | m_PointingTime[handIndex] += Time.deltaTime;//该函数会在Update中调用。 140 | m_Dir[handIndex] = indexPointDir[handIndex]; 141 | } 142 | 143 | //退出左手伸掌状态,这是此次持续伸掌的最后一瞬间伸掌 144 | //左手已经进入伸掌状态,并且现在不是伸掌状态 145 | else if (m_IsEnterPointed[handIndex] && !isIndexPoint[handIndex]) 146 | { 147 | if (m_ExitPointFunc[handIndex] != null) 148 | { 149 | m_ExitPointFunc[handIndex](); 150 | } 151 | m_IsEnterPointed[handIndex] = false; 152 | m_PointingTime[handIndex] = 0f; 153 | 154 | m_Dir[handIndex] = indexPointDir[handIndex]; 155 | } 156 | 157 | //本身不是处于伸掌状态,而且此次判断也不是伸掌状态 158 | else 159 | { 160 | m_IsEnterPointed[handIndex] = false; 161 | m_PointingTime[handIndex] = 0f; 162 | m_Dir[handIndex] = Vector.Zero; 163 | } 164 | } 165 | } 166 | 167 | /// 168 | /// 判定一个手掌是否处于食指指向状态。 169 | /// 手掌中有matchNumber个手指满足即可,实际设定为2个。 170 | /// 171 | /// 172 | /// 173 | /// 【返回值】:如果匹配成功方向为掌心的方向 174 | /// 是否为食指指向状态。 175 | bool IndexPointState(Dictionary dic_FingersData, out Vector dir) 176 | { 177 | bool isBend = false; 178 | dir = Vector.Zero; 179 | int count = 0; 180 | Dictionary fingersOutThumbAndIndex = new Dictionary(dic_FingersData); 181 | fingersOutThumbAndIndex.Remove(Finger.FingerType.TYPE_THUMB); 182 | 183 | //如果不存在食指的信息就不需要继续判断了 184 | if (fingersOutThumbAndIndex.ContainsKey(Finger.FingerType.TYPE_INDEX)) 185 | { 186 | FingerData indexFinger = fingersOutThumbAndIndex[Finger.FingerType.TYPE_INDEX]; 187 | //食指处于伸直状态才继续进行判断 188 | if (FingerMatch.StrightState(indexFinger)) 189 | { 190 | fingersOutThumbAndIndex.Remove(Finger.FingerType.TYPE_INDEX); 191 | 192 | var values = fingersOutThumbAndIndex.Values; 193 | //遍历四指,匹配个数满足设定个数认定手掌为弯曲,并且设定弯曲的方向为掌心方向。 194 | foreach (FingerData fingerData in values) 195 | { 196 | if (FingerMatch.BendState(fingerData)) 197 | { 198 | count++; 199 | } 200 | } 201 | 202 | //log 203 | //print("FingerPoint bend count:"+count); 204 | 205 | //判定弯曲手指的个数是否符合要求 206 | //判定食指是否是伸直状态 207 | if (count >= m_MatchNumber) 208 | { 209 | isBend = true; 210 | dir = indexFinger.m_Point.m_Direction;//食指的方向 211 | } 212 | } 213 | } 214 | return isBend; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /GustersPatch/State/HandFingerPoint.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aefe2c53e78dbca429d8566b0d57678c 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/State/HandOpen.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// 匹配是摊开手的状态,通知相应的操作。在动作匹配中也用到 9 | /// 细分为水平摊开,垂直摊开,一般摊开 10 | /// 数据匹配方式:除大拇指以外的四个手指中有3个手指是伸直状态,并且中指必须是伸直状态 11 | /// 12 | public class HandOpen : MonoBehaviour 13 | { 14 | [SerializeField] HandAndFingersPoint m_HandData; 15 | 16 | Action[] m_EnterOpenFunc=new Action[2]; 17 | Action[] m_ExitOpenFunc=new Action[2]; 18 | 19 | float[] m_OpeningTime =new float[2]; 20 | [SerializeField] bool[] m_IsEnterOpened = new bool[2];//这次伸掌状态时候打开过 21 | Vector[] m_Dir=new Vector[2];//伸掌的手掌方向用中指指尖方向表示,zero表示非伸掌状态 22 | Vector[] m_PalmPos = new Vector[2]; 23 | Vector[] m_PalmDir = new Vector[2]; 24 | //设定匹配时手指需要满足的个数 25 | int m_MatchNumber = 3;//2或3 26 | 27 | //中间变量 28 | Vector[] m_OpenDir = new Vector[2]; 29 | 30 | //borderSettings 31 | // const float FingerStrightState_Radian = Mathf.PI/12;//15度 32 | 33 | public Vector[] Dir 34 | { 35 | get { 36 | return m_Dir; 37 | } 38 | } 39 | 40 | public Vector[] PalmPos 41 | { 42 | get 43 | { 44 | return m_PalmPos; 45 | } 46 | 47 | } 48 | 49 | public Vector[] PalmDir 50 | { 51 | get 52 | { 53 | return m_PalmDir; 54 | } 55 | 56 | } 57 | 58 | public bool[] IsEnterOpened 59 | { 60 | get { 61 | return m_IsEnterOpened; 62 | } 63 | } 64 | 65 | public float[] OpeningTime 66 | { 67 | get 68 | { 69 | return m_OpeningTime; 70 | } 71 | } 72 | 73 | /// 74 | /// 只能在下一帧起作用 75 | /// 76 | /// The set match number. 77 | public int SetMatchNumber 78 | { 79 | set 80 | { 81 | m_MatchNumber = value; 82 | } 83 | } 84 | //手掌打开状态,事件注册接口 85 | public void AddOpenEnterMsg(Action leftOpen, Action rightOpen) 86 | { 87 | if (leftOpen != null) 88 | { 89 | m_EnterOpenFunc[0] += leftOpen; 90 | } 91 | 92 | if (rightOpen != null) 93 | { 94 | m_EnterOpenFunc[1] += rightOpen; 95 | } 96 | } 97 | 98 | //手掌打开结束状态,事件注册接口 99 | public void AddOpenExitMsg(Action leftOpen, Action rightOpen) 100 | { 101 | if (leftOpen != null) 102 | { 103 | m_ExitOpenFunc[0] += leftOpen; 104 | } 105 | 106 | if (rightOpen != null) 107 | { 108 | m_ExitOpenFunc[1] += rightOpen; 109 | } 110 | } 111 | 112 | void Awake() 113 | { 114 | if (m_HandData == null) 115 | Debug.LogError("It's no ref"); 116 | } 117 | 118 | void Update() 119 | { 120 | // print ("m_IsEnterOpened0:"+m_IsEnterOpened[0]+" 1:"+m_IsEnterOpened[1]); 121 | 122 | OpenCtrl( m_HandData.FingerDatas ,m_HandData.PalmDatas); 123 | } 124 | 125 | /// 126 | /// 判断左右手是否处于伸掌状态,触发相应事件和消息,记录相应的值 127 | /// 128 | /// 代表左右手的数据源 129 | void OpenCtrl(Dictionary[] dic,PointData[] palmData) 130 | { 131 | 132 | bool[] isOpen = new bool[2]; 133 | 134 | isOpen[0] = OpenState(dic[0],palmData[0], out m_OpenDir[0],out m_PalmPos[0],out m_PalmDir[0]); 135 | isOpen[1] = OpenState(dic[1], palmData[1], out m_OpenDir[1], out m_PalmPos[1], out m_PalmDir[1]); 136 | 137 | //int count = dic.Length; 138 | for (int handIndex = 0; handIndex < 2; handIndex++) 139 | { 140 | //这个if - else 结构依据m_IsEnterOpened-isLeftOpen表示的二维坐标系,而出现的四个象限。 141 | //此次左手伸掌状态的最开始,触发 142 | //当不处于m_IsEnterOpened状态,并且现在左手是伸掌状态才会触发 143 | if (!m_IsEnterOpened[handIndex] && isOpen[handIndex]) 144 | { 145 | if (m_EnterOpenFunc[handIndex] != null) 146 | { 147 | m_EnterOpenFunc[handIndex](); 148 | } 149 | m_OpeningTime[handIndex] = 0f; 150 | m_IsEnterOpened[handIndex] = true; 151 | 152 | m_Dir[handIndex] = m_OpenDir[handIndex]; 153 | // m_PalmPos[handIndex] = m_PalmPos[handIndex]; 154 | 155 | } 156 | 157 | //持续进行左手的伸掌状态 158 | //左手已经进入到伸掌状态,而且现在也是伸掌状态 159 | else if (m_IsEnterOpened[handIndex] && isOpen[handIndex]) 160 | { 161 | m_OpeningTime[handIndex] += Time.deltaTime;//该函数会在Update中调用。 162 | m_Dir[handIndex] = m_OpenDir[handIndex]; 163 | // m_PalmPos[handIndex] = m_PalmPos[handIndex]; 164 | } 165 | 166 | //退出左手伸掌状态,这是此次持续伸掌的最后一瞬间伸掌 167 | //左手已经进入伸掌状态,并且现在不是伸掌状态 168 | else if (m_IsEnterOpened[handIndex] && !isOpen[handIndex]) 169 | { 170 | if (m_ExitOpenFunc[handIndex] != null) 171 | { 172 | m_ExitOpenFunc[handIndex](); 173 | } 174 | m_IsEnterOpened[handIndex] = false; 175 | m_OpeningTime[handIndex] = 0f; 176 | 177 | m_Dir[handIndex] = m_OpenDir[handIndex]; 178 | // m_PalmPos[handIndex] = m_PalmPos[handIndex]; 179 | } 180 | //本身不是处于伸掌状态,而且此次判断也不是伸掌状态 181 | else 182 | { 183 | m_IsEnterOpened[handIndex] = false; 184 | m_OpeningTime[handIndex] = 0f; 185 | m_Dir[handIndex] = Vector.Zero; 186 | m_PalmPos[handIndex] = Vector.Zero; 187 | m_PalmDir[handIndex] = Vector.Zero; 188 | } 189 | } 190 | } 191 | 192 | /// 193 | /// 通过给定手指数据,判断是否处于伸手状态 194 | /// 当三个手指符合不包括拇指,判定为伸掌状态 195 | /// 196 | /// 197 | /// 198 | bool OpenState( Dictionary dic_FingersData,PointData palmData,out Vector dir,out Vector palmPos,out Vector palmDir) 199 | { 200 | bool isOpen = false; 201 | dir = Vector.Zero; 202 | palmPos = Vector.Zero; 203 | palmDir = Vector.Zero; 204 | int count = 0; 205 | Dictionary fingersOutThumb = new Dictionary(dic_FingersData); 206 | fingersOutThumb.Remove(Finger.FingerType.TYPE_THUMB); 207 | 208 | var values = fingersOutThumb.Values; 209 | 210 | foreach( FingerData fingerData in values ) 211 | { 212 | if (FingerMatch.StrightState(fingerData)) 213 | { 214 | count++; 215 | } 216 | } 217 | // print ("FingerStright Count:"+count); 218 | if (count >= m_MatchNumber) 219 | { 220 | 221 | isOpen = true; 222 | } 223 | 224 | //指定输出的伸手方向为中指指向 225 | //如果没有识别中指,判定为false 226 | //如果中指不是伸直状态,判定为false 227 | if (fingersOutThumb.ContainsKey(Finger.FingerType.TYPE_MIDDLE)) 228 | { 229 | FingerData middleFingerData = fingersOutThumb[Finger.FingerType.TYPE_MIDDLE]; 230 | if (FingerMatch.StrightState(middleFingerData)) 231 | { 232 | dir = middleFingerData.m_Point.m_Direction; 233 | palmPos = palmData.m_Position; 234 | palmDir = palmData.m_Direction; 235 | } 236 | else 237 | { 238 | isOpen = false; 239 | } 240 | } 241 | else 242 | { 243 | isOpen = false; 244 | } 245 | 246 | return isOpen; 247 | } 248 | 249 | /// 250 | /// 计算当前手掌伸开的个数 251 | /// 返回个数 252 | /// 253 | /// 记录手掌打开的索引值,返回值大于0时有效 254 | /// 当前处于摊开的手掌个数 255 | public int OpenNumber(out int index) 256 | { 257 | int number = 0; 258 | index =0; 259 | if(m_IsEnterOpened[0]==true) 260 | { 261 | if(m_IsEnterOpened[1]==true) 262 | { 263 | number = 2; 264 | } 265 | else 266 | { 267 | number = 1; 268 | index = 0; 269 | } 270 | } 271 | else if(m_IsEnterOpened[1]==true) 272 | { 273 | number = 1; 274 | index = 1; 275 | } 276 | else 277 | { 278 | number = 0; 279 | } 280 | return number; 281 | } 282 | } 283 | 284 | -------------------------------------------------------------------------------- /GustersPatch/State/HandOpen.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 771fc0f3a6b76694a9651acf7c08e006 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /GustersPatch/State/HandPinchGroup.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using Leap; 4 | 5 | ///Log1:手势废弃,Leap API不能正确识别手指的前方,尤其是整根手指从骨根弯曲,不明白Finger.Direction的定义。 6 | ///在这里,不能正确匹配手指伸直的判定 7 | /// 8 | /// 捏手势判定 9 | /// 可用的信息: 10 | /// 1.食指和拇指指尖之间的距离 11 | /// 2.是否是捏手势 12 | /// 【注意】捏手势的优先级应当设定为很低,因为它匹配成功只需要食指伸直。 13 | /// 14 | public class HandPinchGroup : MonoBehaviour 15 | { 16 | [SerializeField] HandAndFingersPoint m_HandData; 17 | bool[] m_IsHandPinch = new bool[2]; 18 | float[] m_Distance = new float[2]; 19 | readonly float EnterDisThreshold;//要大于这个阈值才认定进入 20 | 21 | public bool[] IsHandPinch 22 | { 23 | get 24 | { 25 | return m_IsHandPinch; 26 | } 27 | } 28 | public float[] Distance 29 | { 30 | get 31 | { 32 | return m_Distance; 33 | } 34 | } 35 | // Use this for initialization 36 | void Start () { 37 | 38 | } 39 | 40 | // Update is called once per frame 41 | void Update () 42 | { 43 | for (int i = 0; i < 2; i++) 44 | { 45 | //含有中指和食指的数据 46 | if (m_HandData.FingerDatas[i].ContainsKey(Finger.FingerType.TYPE_INDEX) && 47 | m_HandData.FingerDatas[i].ContainsKey(Finger.FingerType.TYPE_THUMB)) 48 | { 49 | FingerData indexFingerData = m_HandData.FingerDatas[i][Finger.FingerType.TYPE_INDEX]; 50 | Vector indexPos = indexFingerData.m_Point.m_Position; 51 | Vector thumbPos = m_HandData.FingerDatas[i][Finger.FingerType.TYPE_THUMB].m_Point.m_Position; 52 | 53 | //print("IndexToThumb:" + indexPos.DistanceTo(thumbPos) ); 54 | print((indexFingerData.m_Point.m_Position-indexFingerData.m_Position).AngleTo(indexFingerData.m_Point.m_Direction)*180/Mathf.PI); 55 | if (FingerMatch.StrightState(indexFingerData)) 56 | { 57 | m_IsHandPinch[i] = true; 58 | m_Distance[i] = indexPos.DistanceTo(thumbPos); 59 | 60 | } 61 | else 62 | { 63 | m_IsHandPinch[i] = false; 64 | m_Distance[i] = 0f; 65 | } 66 | } 67 | else 68 | { 69 | m_IsHandPinch[i] = false; 70 | m_Distance[i] = 0f; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /GustersPatch/State/HandPinchGroup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6496059320948834fb98a6b23bd5f444 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leap-Motion-In-Unity3D 2 | ### **Leap Motion体感控制器在Unity3D中完成自定义的手势识别方案** 3 | ### **包含以下几部分:** 4 | + 基本数据结构 5 | + 手指的匹配 6 | + 手势状态的匹配 7 | + 手势运动的匹配 8 | 9 | ### **特点:** 10 | + 手势采用事件响应,所以外部可以定制手势特定时刻触发的事件 11 | + 手势通过阈值调节匹配的精确度 12 | + 动作通过离散的数据监测保证匹配行为的合理 13 | 14 | [ **博文链接:从最开始到编写算法的思路** ](http://blog.csdn.net/u012289636/article/details/46883731) 15 | --------------------------------------------------------------------------------