Leap Motion 之 官方SDK代码分析

来源:互联网 发布:发票二维码扫描软件 编辑:程序博客网 时间:2024/05/01 19:26

【仅用于个人复习使用,顺序较乱】
[Detection SDK]
Detection SDK
关于手势检测的官方SDK主要有这么几个脚本。
Detector是所有检测器的父类,主要保存的属性是IsActive。
DetectorLogicGate继承自Detector,主要提供多个Detector的逻辑操作,比如AND/OR/NAND/NOR。
PinchDetector是用来检测手是否握紧,继承自AbstractHoldDetector。而AbstractHoldDetector又继承自Detector。
FingerDirectionDetector用来检测某个手指是否沿某个指定的方向。
PalmDirectionDetector用来检测手掌是否沿某个指定的方向。
ExtendedFingerDetector用来检测手指是否展开。
ProximityDetector用来进行距离检测。

(LeapMotion脚本代码思路教程:http://blog.csdn.net/admintan/article/details/50319757)
(LeapMotion的名字空间:http://blog.csdn.net/eagle_pre21/article/details/51776830)
这两个链接还是值得一看的。
我们先对官方提供的一些demo和源码进行解析,从而了解LeapMotion提供了哪些主要的接口或者说我们应该怎么编写LeapMotion的代码。

五、SDK的使用 之 PinchDetector
我们这里将以LeapMotion Detection Module中的PinchDrawDemo场景为例。这是PinchDrawDemo的结构:
PinchDrawDemo
LMHeadMountedRig是CoreAsset中的一个预制件,应该对应的是HeadMounted模式。PinchDrawing是执行绘制功能的object。DirectionLight则是场景中的灯光。
我们对这个场景做简单的分析,来了解LM SDK的使用方法。

(1)PinchDraw.cs
这是PinchDrawing对象上的唯一的脚本,具体代码如下:

using UnityEngine;using System.Collections.Generic;namespace Leap.Unity.DetectionExamples {  public class PinchDraw : MonoBehaviour {    //使用Tooltip,当我们在Inspector面板中,把鼠标停留在PinchDetector变量上,就会显示这一句字符串。这里的PinchDetector是一个DetectionUtility文件夹中的自定义类,左右手模型上各有一个。    [Tooltip("Each pinch detector can draw one line at a time.")]    [SerializeField]    //左右手的PinchDetector    private PinchDetector[] _pinchDetectors;    [SerializeField]    //mesh的材质    private Material _material;    [SerializeField]    //mesh的颜色    private Color _drawColor = Color.white;    [SerializeField]    //SmoothedVector3的平滑系数    private float _smoothingDelay = 0.01f;    [SerializeField]    //绘制的Ring的半径    private float _drawRadius = 0.002f;    [SerializeField]    //绘制一个Ring所使用的顶点数    private int _drawResolution = 8;    [SerializeField]    //判断是否需要添加Ring的最短距离    private float _minSegmentLength = 0.005f;    private DrawState[] _drawStates;    //get/set,前面已经介绍了,这里不再多提    public Color DrawColor {      get {        return _drawColor;      }      set {        _drawColor = value;      }    }    public float DrawRadius {      get {        return _drawRadius;      }      set {        _drawRadius = value;      }    }    //当Inspector面板中的值被修改时触发,用于保证值的合法性。类似的函数还有Reset(),当面板中脚本的值被reset时触发。    //具体可参考:http://www.tuicool.com/articles/AfqY32    void OnValidate() {      _drawRadius = Mathf.Max(0, _drawRadius);      _drawResolution = Mathf.Clamp(_drawResolution, 3, 24);      _minSegmentLength = Mathf.Max(0, _minSegmentLength);    }    void Awake() {      //至少要有一个PinchDetector被传入      if (_pinchDetectors.Length == 0) {        Debug.LogWarning("No pinch detectors were specified!  PinchDraw can not draw any lines without PinchDetectors.");      }    }    //初始化DrawState数组,并加入新建DrawState对象。DrawState是这个脚本中定义的内部类,具体参见下方的注释。    void Start() {      _drawStates = new DrawState[_pinchDetectors.Length];      for (int i = 0; i < _pinchDetectors.Length; i++) {        //把this作为参数传入DrawState,保存在parent变量中。        _drawStates[i] = new DrawState(this);      }    }    void Update() {      for (int i = 0; i < _pinchDetectors.Length; i++) {        var detector = _pinchDetectors[i];        var drawState = _drawStates[i];        //以下三个是PinchDetector类的方法,根据名字很容易猜到他们的功能。捏成拳头就开始画线,拳头松开就结束画线。画线调用的则是内部类DrawState封装的函数。        //DidStartHold——手刚捏成拳头        if (detector.DidStartHold) {          drawState.BeginNewLine();        }        //DidRelease——手从拳头松开        if (detector.DidRelease) {          drawState.FinishLine();        }        //IsHolding——手保持拳头的姿势        if (detector.IsHolding) {          drawState.UpdateLine(detector.Position);        }      }    }    //期待已久的DrawState类~    private class DrawState {      private List<Vector3> _vertices = new List<Vector3>();      private List<int> _tris = new List<int>();      private List<Vector2> _uvs = new List<Vector2>();      private List<Color> _colors = new List<Color>();      //外部类在初始化DrawState数组时(Start()函数),会将this(外部类对象的引用)传入新建的DrawState。      private PinchDraw _parent;      private int _rings = 0;      private Vector3 _prevRing0 = Vector3.zero;      private Vector3 _prevRing1 = Vector3.zero;      private Vector3 _prevNormal0 = Vector3.zero;      private Mesh _mesh;      //SmoothedVector3是LM CoreAsset中Algorithm文件夹里的自定义类,大致就是让Vector3的值在改变时更平滑。      private SmoothedVector3 _smoothedPosition;      public DrawState(PinchDraw parent) {        _parent = parent;        //初始化SmoothedVector3对象        _smoothedPosition = new SmoothedVector3();        _smoothedPosition.delay = parent._smoothingDelay;        _smoothedPosition.reset = true;      }      //开始画线,为后面的Update做一些初始化的准备工作      public GameObject BeginNewLine() {        _rings = 0;        _vertices.Clear();        _tris.Clear();        _uvs.Clear();        _colors.Clear();        _smoothedPosition.reset = true;        //每次BeginNewLine会新建一个mesh        _mesh = new Mesh();        _mesh.name = "Line Mesh";        _mesh.MarkDynamic();//标记为动态,这样mesh就会使用动态的buffer,在渲染时的性能更高。        //用脚本动态添加GameObject        GameObject lineObj = new GameObject("Line Object");        //初始化transform        lineObj.transform.position = Vector3.zero;        lineObj.transform.rotation = Quaternion.identity;        lineObj.transform.localScale = Vector3.one;        //添加MeshFilter以及材质        lineObj.AddComponent<MeshFilter>().mesh = _mesh;        lineObj.AddComponent<MeshRenderer>().sharedMaterial = _parent._material;        return lineObj;      }      public void UpdateLine(Vector3 position) {        //更新SmoothedPosition,这里传入的position是PinchDetector.position。        _smoothedPosition.Update(position, Time.deltaTime);        bool shouldAdd = false;        //当vertices为空 或者 position与prevRing0的距离超过minSegmentLength的时候,需要调用addRing()去添加ring。        shouldAdd |= _vertices.Count == 0;        shouldAdd |= Vector3.Distance(_prevRing0, _smoothedPosition.value) >= _parent._minSegmentLength;        //addRing添加ring,updateMesh去更新mesh的数据。        if (shouldAdd) {          addRing(_smoothedPosition.value);          updateMesh();        }      }      public void FinishLine() {        ;        //通过vertices、normals、triangles等可以通过脚本绘制mesh,mesh的data会被标记为modified,在下一次传给GraphicsAPI去渲染。而调用UploadMeshData可以强制将data立刻传给API渲染。        //而markNoLongerReadable参数则表示是否清空mesh data在内存中占用的空间。由于这里是FinishLine,所以设置为true。        _mesh.UploadMeshData(true);      }      //将顶点等数组的数据给刷新到mesh中去,从而更新场景中的mesh。      private void updateMesh() {        _mesh.SetVertices(_vertices);        _mesh.SetColors(_colors);        _mesh.SetUVs(0, _uvs);        //以Triangles的方式设置mesh的sub-mesh        _mesh.SetIndices(_tris.ToArray(), MeshTopology.Triangles, 0);        //重新计算边界和法向量        _mesh.RecalculateBounds();        _mesh.RecalculateNormals();      }      //添加Ring,函数中做了一堆计算,没太看懂,note一下~      private void addRing(Vector3 ringPosition) {        _rings++;//ring的个数        if (_rings == 1) {          //为一个Ring在顶点数组中新建8个顶点,初始化uv和color          addVertexRing();          addVertexRing();          //通过顶点数组去创建三角面片,用顶点数组的三个index来表示构成三角面片的三个顶点          addTriSegment();        }        addVertexRing();        addTriSegment();        //后面就是一些看不懂的计算了,大致就是为Ring去计算法向量,以及通过UpdateRingVerts()更新顶点数组中的顶点坐标之类的。。。花里胡哨        Vector3 ringNormal = Vector3.zero;        if (_rings == 2) {          Vector3 direction = ringPosition - _prevRing0;          float angleToUp = Vector3.Angle(direction, Vector3.up);          if (angleToUp < 10 || angleToUp > 170) {            ringNormal = Vector3.Cross(direction, Vector3.right);          } else {            ringNormal = Vector3.Cross(direction, Vector3.up);          }          ringNormal = ringNormal.normalized;          _prevNormal0 = ringNormal;        } else if (_rings > 2) {          Vector3 prevPerp = Vector3.Cross(_prevRing0 - _prevRing1, _prevNormal0);          ringNormal = Vector3.Cross(prevPerp, ringPosition - _prevRing0).normalized;        }        if (_rings == 2) {          updateRingVerts(0,                          _prevRing0,                          ringPosition - _prevRing1,                          _prevNormal0,                          0);        }        if (_rings >= 2) {          updateRingVerts(_vertices.Count - _parent._drawResolution,                          ringPosition,                          ringPosition - _prevRing0,                          ringNormal,                          0);          updateRingVerts(_vertices.Count - _parent._drawResolution * 2,                          ringPosition,                          ringPosition - _prevRing0,                          ringNormal,                          1);          updateRingVerts(_vertices.Count - _parent._drawResolution * 3,                          _prevRing0,                          ringPosition - _prevRing1,                          _prevNormal0,                          1);        }        _prevRing1 = _prevRing0;        _prevRing0 = ringPosition;        _prevNormal0 = ringNormal;      }      private void addVertexRing() {        for (int i = 0; i < _parent._drawResolution; i++) {          _vertices.Add(Vector3.zero);  //Dummy vertex, is updated later          _uvs.Add(new Vector2(i / (_parent._drawResolution - 1.0f), 0));          _colors.Add(_parent._drawColor);        }      }      //Connects the most recently added vertex ring to the one before it      private void addTriSegment() {        for (int i = 0; i < _parent._drawResolution; i++) {          int i0 = _vertices.Count - 1 - i;          int i1 = _vertices.Count - 1 - ((i + 1) % _parent._drawResolution);          _tris.Add(i0);          _tris.Add(i1 - _parent._drawResolution);          _tris.Add(i0 - _parent._drawResolution);          _tris.Add(i0);          _tris.Add(i1);          _tris.Add(i1 - _parent._drawResolution);        }      }      private void updateRingVerts(int offset, Vector3 ringPosition, Vector3 direction, Vector3 normal, float radiusScale) {        direction = direction.normalized;        normal = normal.normalized;        for (int i = 0; i < _parent._drawResolution; i++) {          float angle = 360.0f * (i / (float)(_parent._drawResolution));          Quaternion rotator = Quaternion.AngleAxis(angle, direction);          Vector3 ringSpoke = rotator * normal * _parent._drawRadius * radiusScale;          _vertices[offset + i] = ringPosition + ringSpoke;        }      }    }  }}

总结这段代码,就是通过LM官方提供的PinchDetector去识别当前手势的Pinch信息,主要就是开始握紧、保持握紧以及结束握紧三个状态。下面是对应的函数:
PinchDetector
通过对三个状态的判断,去执行DrawState相应的函数进行画线。

(2)LMHeadMountedRig对象
多了两个脚本Leap VR Camera Controller.cs和Leap VR Temporal Wraping.cs,猜测这个可能是用于VR场景下。
由于我们暂时不考虑VR模式下使用,所以尝试把这个替换成普通的LeapHandController,也就是桌面模式下的控制器。

当然我们直接把这个场景中的LeapHandController拿出来,而把LMHeadMountedRig给删掉就ok了。这里为了记录一下场景搭建的过程,就从头开始吧。

先新建一个Scene,新建一个叫Hands的空对象,里面拖入LeapHandController预制件作为子对象。再新建一个名为HandModels的空子对象,将模型CapsuleL和Capsule_R拖进来。
在LeapHandController的HandPool组件中,把ModelPool的size置为1,会出来一个空的组。将组命名为gfx,因为这里不需要pfx,所以一个组就够了。再把之前的Capsule L和R这两个模型作为参数传入gfx。
最后再把Camera拖入Hands对象就大功告成~

当然这只是最基本的搭建,由于我们需要Pinch的识别,所以将PinchDetector.cs拖给CapsuleL和CapsuleR。新建一个PinchDrawing,将PinchDraw脚本拖给他,并将两个PinchDetector作为参数传给PinchDraw即可。
给出最后的示意图:
示意图1
示意图1
示意图2
示意图2

六、SDK的使用 之 OtherDetector
我们这里以FingerDetectorDemo为例进行介绍。
在看代码之前,我们首先需要了解以下内容:
1、C#中的IEnumerable、IEnumerate(这个教程看完以后就舒服):http://blog.csdn.net/byondocean/article/details/6871881
2、C#中的yield:http://www.cnblogs.com/kingcat/archive/2012/07/11/2585943.html
3、unity3d中的yield return:http://blog.csdn.net/huang9012/article/details/29595747

这是场景中的Hierarchy:
Hierarchy
LMHeadMountedRig是VR下的预制件,我们可以只保留内部的LeapHandController。把CenterEyeEntered的其他内容给去掉。当然你还得自己加一个Camera,具体搭建请参照前文。。。
识别的代码主要在CapsuleL和CapsuleR上,这个稍后一点讲。而Environment是整个游戏的环境,当然其他的东西都属于花里胡哨,只有BlueBall以及BallSpawner是有用的。我们先看BlueBall上的SpawnBalls.cs脚本。
(1)SpawnBalls.cs

using UnityEngine;using System.Collections;public class SpawnBalls : MonoBehaviour {  public GameObject BallPrefab;//预制件,用于实例化小球  public float delayInterval = .15f; // seconds  public int BallLimit = 100;//最大球数  public Vector3 BallSize = new Vector3(0.1f, 0.1f, 0.1f);//产生的球的半径  //IEnumerator对象,用于协程  private IEnumerator _spawnCoroutine;  void Awake () {    //设置协程,这里并不会去执行协程的代码    _spawnCoroutine = AddBallWithDelay(BallPrefab);  }  public void StartBalls(){    //开启协程    StartCoroutine(_spawnCoroutine);  }  public void StopBalls(){    //关闭协程    StopAllCoroutines();  }  private IEnumerator AddBallWithDelay (GameObject prefab) {    while (true) {      addBall(prefab);//生成一个小球      //每帧在调用LateUpdate()之后,会调用协程的MoveNext,如果满足条件,则从上次yield的位置继续执行,否则就跳过协程。      yield return new WaitForSeconds(delayInterval);    }  }  private void addBall (GameObject prefab) {    if (transform.childCount > BallLimit) removeBalls(BallLimit / 10);//超过最大球数就销毁1/10    GameObject go = GameObject.Instantiate(prefab);    go.transform.parent = transform;//生成的小球作为此脚本所在对象的子对象    go.transform.localPosition = Vector3.zero;//相对位置    go.transform.localScale = BallSize;//相对大小    Rigidbody rb = go.GetComponent<Rigidbody>();    rb.AddForce(Random.value * 3, -Random.value * 13, Random.value * 3, ForceMode.Impulse);//给一个冲量  }  private void removeBalls (int count) {    if (count > transform.childCount) count = transform.childCount;    for (int b = 0; b < count; b++) {      Destroy(transform.GetChild(b).gameObject);//删除1/10的子对象    }  }}

(2)Detector.cs

//这个注释其实源文件已经很详细了,这里就简要概括一下好了。//Detector作为所有检测器的父类,核心就是IsActive这个属性。当派生类例如FingerDirectionDetector继承它后,如果手指方向与指定方向匹配,则调用父类的Avtivate()方法。该方法会将IsActive置为true,方便外部获取信息(比如DetectorLogicGate就是用IsActive来做逻辑计算的)。同时调用OnActivate事件对应的回调函数。using UnityEngine;using UnityEngine.Events;using System.Collections;using Leap;namespace Leap.Unity {  /**   * Base class for detectors.   *    * A Detector is an object that observes some aspect of a scene and reports true   * when the specified conditions are met. Typically these conditions involve hand   * information, but this is not required.   *    * Detector implementations must call Activate() when their conditions are met and   * Deactivate() when those conditions are no longer met. Implementations should   * also call Deactivate() when they, or the object they are a component of become disabled.   * Implementations can call Activate() and Deactivate() more often than is strictly necessary.   * This Detector base class keeps track of the IsActive status and only dispatches events   * when the status changes.   *    * @since 4.1.2   */  public class Detector : MonoBehaviour {    /** The current detector state.      * @since 4.1.2      */    public bool IsActive{ get{ return _isActive;}}    private bool _isActive = false;    /** Dispatched when the detector activates (becomes true).      * @since 4.1.2     */    [Tooltip("Dispatched when condition is detected.")]    public UnityEvent OnActivate;    /** Dispatched when the detector deactivates (becomes false).      * @since 4.1.2     */    [Tooltip("Dispatched when condition is no longer detected.")]    public UnityEvent OnDeactivate;    /**    * Invoked when this detector activates.    * Subclasses must call this function when the detector's conditions become true.    * @since 4.1.2    */    public virtual void Activate(){      if (!IsActive) {        _isActive = true;        OnActivate.Invoke();      }    }    /**    * Invoked when this detector deactivates.    * Subclasses must call this function when the detector's conditions change from true to false.    * @since 4.1.2    */    public virtual void Deactivate(){      if (IsActive) {        _isActive = false;        OnDeactivate.Invoke();      }    }    //Gizmo colors    protected Color OnColor = Color.green;    protected Color OffColor = Color.red;    protected Color LimitColor = Color.blue;    protected Color DirectionColor = Color.white;    protected Color NormalColor = Color.gray;  }}

(3)DetectorLogicGate.cs

using UnityEngine;using UnityEngine.Events;using System.Collections.Generic;namespace Leap.Unity {  /**   * The DetectorLogicGate detector observes other detectors and activates when   * these other detectors match the specified logic.   *    * A DetectorLogicGate can be configured as an AND gate or an OR gate. You can also   * negate the output (creating a NAND or NOR gate).   *    * Since a DetectorLogicGate is a Detector, it can observe other DetectorLogicGate instances.   * However, before constructing complex logic chains, you should consider whether it is better    * to put such logic into a normal script.   *    * @since 4.1.2   */  public class DetectorLogicGate : Detector {    [SerializeField]    [Tooltip("The list of observed detectors.")]    //需要监听的Detectors    private List<Detector> Detectors;    /**     * When true, all Detector components of the same game object     * are added to the list of watched detectors on Awake. When false,     * you must manually add the desired detectors.     *      * If you have more than one DetectorLogicGate component on a game object,     * do not enable this option on both.      * @since 4.1.2     */    [Tooltip("Add all detectors on this object automatically.")]    //这个选项如果勾选,在Awake的时候,会自动将所有的兄弟Detector全都加进来。所以如果一个object上有两个本脚本,建议不要勾选这个选项。    public bool AddAllSiblingDetectorsOnAwake = true;    /**     * The type of logic for this gate: AND or OR.     * @since 4.1.2     */    [Tooltip("The type of logic used to combine detector state.")]    //AND / OR    public LogicType GateType = LogicType.AndGate;    /**     * Whether to negate the output of the gate. AND becomes NAND; OR becomes NOR.     * @since 4.1.2     */    [Tooltip("Whether to negate the gate output.")]    //NAND / NOR    public bool Negate = false;    /**     * Adds the specified detector to the list of observed detectors.     *      * The same detector cannot be added more than once.     * @param Detector the detector to watch.     * @since 4.1.2     */    public void AddDetector(Detector detector){      if(!Detectors.Contains(detector)){        Detectors.Add(detector);//添加Detector到监听数组中        activateDetector(detector);//激活Detector,也就是为该Detector的OnActivate和OnDeactivate设置监听器(回调函数)      }    }    /**     * Removes the specified detector from the list of observed detectors;     *      * @param Detector the detector to remove.     * @since 4.1.2     */     //移除监听的Detector,没什么好说的。。    public void RemoveDetector(Detector detector){      detector.OnActivate.RemoveListener(CheckDetectors);      detector.OnDeactivate.RemoveListener(CheckDetectors);      Detectors.Remove(detector);    }    /**     * Adds all the other detectors on the same GameObject to the list of observed detectors.     *      * Note: If you have more than one DetectorLogicGate instance on a game object, make sure that     * both objects don't observe each other.     * @since 4.1.2     */    public void AddAllSiblingDetectors(){      //遍历所有兄弟Detector      Detector[] detectors = GetComponents<Detector>();      for(int g = 0; g < detectors.Length; g++){        if ( detectors[g] != this && detectors[g].enabled) {          AddDetector(detectors[g]);        }      }    }    private void Awake(){      for (int d = 0; d < Detectors.Count; d++) {        activateDetector(Detectors[d]);//激活手动设置的监听数组中的Detector      }      if (AddAllSiblingDetectorsOnAwake) {        AddAllSiblingDetectors();//自动激活兄弟Detector      }    }    private void activateDetector(Detector detector){      //为了避免连续添加两次监听器,所以先remove      detector.OnActivate.RemoveListener(CheckDetectors); //avoid double subscription      detector.OnDeactivate.RemoveListener(CheckDetectors);      detector.OnActivate.AddListener(CheckDetectors);      detector.OnDeactivate.AddListener(CheckDetectors);    }    private void OnEnable() {      CheckDetectors();    }    private void OnDisable () {      Deactivate();    }    /**     * Checks all the observed detectors, combines them with the specified type of logic     * and calls the Activate() or Deactivate() function as appropriate.     * @since 4.1.2     */    protected void CheckDetectors(){      if (Detectors.Count < 1)        return;      //遍历所有监听数组中的Detector,取他们的IsActive变量进行计算      bool state = Detectors[0].IsActive;      for(int a = 1; a < Detectors.Count; a++){        if(GateType == LogicType.AndGate){//与计算,需要全部Active          state = state && Detectors[a].IsActive;        } else {//或计算,只需要有一个Actice          state = state || Detectors[a].IsActive;        }      }      if(Negate){//非计算        state = !state;      }      if(state){        Activate();//调用本脚本中的OnActivate回调函数      } else {        Deactivate();//调用本脚本中的OnDeactivate回调函数      }    }  }  /** The type of logic used to combine the watched detectors. */  public enum LogicType{ AndGate, OrGate }}

(4)FingerDirectionDetector.cs

using UnityEngine;using System.Collections;using Leap.Unity.Attributes;namespace Leap.Unity {  /**   * Detects when specified fingers are pointing in the specified manner.   *    * Directions can be specified relative to the global frame of reference, relative to    * the camera frame of reference, or using a combination of the two -- relative to the    * camera direction in the x-z plane, but not changing relative to the horizon.   *    * You can alternatively specify a target game object.   *    * If added to a IHandModel instance or one of its children, this detector checks the   * finger direction at the interval specified by the Period variable. You can also specify   * which hand model to observe explicitly by setting handModel in the Unity editor or    * in code.   *    * @since 4.1.2   */   //检测特定手指是否沿特定方向  public class FingerDirectionDetector : Detector {    /**     * The interval at which to check finger state.     * @since 4.1.2     */    [Units("seconds")]    [Tooltip("The interval in seconds at which to check this detector's conditions.")]    [MinValue(0)]    //每隔Period秒检测一次    public float Period = .1f; //seconds    /**     * The IHandModel instance to observe.      * Set automatically if not explicitly set in the editor.     * @since 4.1.2     */    [AutoFind(AutoFindLocations.Parents)]    [Tooltip("The hand model to watch. Set automatically if detector is on a hand.")]    //手部模型,如果detector在手模型上则自动添加    public IHandModel HandModel = null;      /**     * The finger to compare to the specified direction.     * @since 4.1.2     */    [Tooltip("The finger to observe.")]    //需要检测的手指,index对应的是食指    public Finger.FingerType FingerName = Finger.FingerType.TYPE_INDEX;    /**     * Specifies how to interprete the direction specified by PointingDirection.     *      * - RelativeToCamera -- the target direction is defined relative to the camera's forward vector, i.e. (0, 0, 1) is the cmaera's      *                       local forward direction.     * - RelativeToHorizon -- the target direction is defined relative to the camera's forward vector,      *                        except that it does not change with pitch.     * - RelativeToWorld -- the target direction is defined as a global direction that does not change with camera movement. For example,     *                      (0, 1, 0) is always world up, no matter which way the camera is pointing.     * - AtTarget -- a target object is used as the pointing direction (The specified PointingDirection is ignored).     *      * In VR scenes, RelativeToHorizon with a direction of (0, 0, 1) for camera forward and RelativeToWorld with a direction     * of (0, 1, 0) for absolute up, are often the most useful settings.     * @since 4.1.2     */    [Header("Direction Settings")]    [Tooltip("How to treat the target direction.")]    //pointing的种类,这里用到了AtTarget(指向目标点)和RelativeToWorld(相对于世界坐标系)    public PointingType PointingType = PointingType.RelativeToHorizon;    /**     * The target direction as interpreted by the PointingType setting.     * Ignored when Pointingtype is "AtTarget."     * @since 4.1.2     */    [Tooltip("The target direction.")]    [DisableIf("PointingType", isEqualTo: PointingType.AtTarget)]    //Pointing的方向,只有当不是AtTarget才可以设置    public Vector3 PointingDirection = Vector3.forward;    /**     * The object to point at when the PointingType is "AtTarget." Ignored otherwise.     */    [Tooltip("A target object(optional). Use PointingType.AtTarget")]    [DisableIf("PointingType", isNotEqualTo: PointingType.AtTarget)]    //目标物体,只有当是AtTarget才可以设置    public Transform TargetObject = null;    /**     * The turn-on angle. The detector activates when the specified finger points within this     * many degrees of the target direction.     * @since 4.1.2     */    [Tooltip("The angle in degrees from the target direction at which to turn on.")]    [Range(0, 180)]    //判定为Active的角度范围    public float OnAngle = 15f; //degrees    /**    * The turn-off angle. The detector deactivates when the specified finger points more than this    * many degrees away from the target direction. The off angle must be larger than the on angle.    * @since 4.1.2    */    [Tooltip("The angle in degrees from the target direction at which to turn off.")]    [Range(0, 180)]    //判定为Deactive的角度范围    public float OffAngle = 25f; //degrees    /** Whether to draw the detector's Gizmos for debugging. (Not every detector provides gizmos.)     * @since 4.1.2      */    [Header("")]    [Tooltip("Draw this detector's Gizmos, if any. (Gizmos must be on in Unity edtor, too.)")]    public bool ShowGizmos = true;    private IEnumerator watcherCoroutine;    private void OnValidate(){      if( OffAngle < OnAngle){        OffAngle = OnAngle;      }    }    private void Awake () {      watcherCoroutine = fingerPointingWatcher();    }    private void OnEnable () {      StartCoroutine(watcherCoroutine);    }    private void OnDisable () {      StopCoroutine(watcherCoroutine);      Deactivate();    }    //协程    private IEnumerator fingerPointingWatcher() {      Hand hand;      Vector3 fingerDirection;      Vector3 targetDirection;      int selectedFinger = selectedFingerOrdinal();      while(true){        if(HandModel != null && HandModel.IsTracked){          //从HandModel中获得Hand类型的对象          hand = HandModel.GetLeapHand();          if(hand != null){            //目标方向            targetDirection = selectedDirection(hand.Fingers[selectedFinger].TipPosition.ToVector3());            //当前手指方向            fingerDirection = hand.Fingers[selectedFinger].Bone(Bone.BoneType.TYPE_DISTAL).Direction.ToVector3();            float angleTo = Vector3.Angle(fingerDirection, targetDirection);            //IsTracked表示这个手是否正在被追踪,也就是是否合法            if(HandModel.IsTracked && angleTo <= OnAngle){              Activate();            } else if (!HandModel.IsTracked || angleTo >= OffAngle) {              Deactivate();            }          }        }        //每隔Period秒执行一次        yield return new WaitForSeconds(Period);      }    }    //根据手指的位置以及PointingType,计算出世界坐标系下的方向    private Vector3 selectedDirection(Vector3 tipPosition){      switch(PointingType){        case PointingType.RelativeToHorizon:          Quaternion cameraRot = Camera.main.transform.rotation;          float cameraYaw = cameraRot.eulerAngles.y;          Quaternion rotator = Quaternion.AngleAxis(cameraYaw, Vector3.up);          return rotator * PointingDirection;        case PointingType.RelativeToCamera:          return Camera.main.transform.TransformDirection(PointingDirection);        case PointingType.RelativeToWorld:          return PointingDirection;        case PointingType.AtTarget:          return TargetObject.position - tipPosition;        default:          return PointingDirection;      }    }    //计算手指对应的序号    private int selectedFingerOrdinal(){      switch(FingerName){        case Finger.FingerType.TYPE_INDEX:          return 1;        case Finger.FingerType.TYPE_MIDDLE:          return 2;        case Finger.FingerType.TYPE_PINKY:          return 4;        case Finger.FingerType.TYPE_RING:          return 3;        case Finger.FingerType.TYPE_THUMB:          return 0;        default:          return 1;      }    }  #if UNITY_EDITOR    private void OnDrawGizmos () {      if (ShowGizmos && HandModel != null && HandModel.IsTracked) {        Color innerColor;        if (IsActive) {          innerColor = OnColor;        } else {          innerColor = OffColor;        }        Finger finger = HandModel.GetLeapHand().Fingers[selectedFingerOrdinal()];        Vector3 fingerDirection = finger.Bone(Bone.BoneType.TYPE_DISTAL).Direction.ToVector3();        Utils.DrawCone(finger.TipPosition.ToVector3(), fingerDirection, OnAngle, finger.Length, innerColor);        Utils.DrawCone(finger.TipPosition.ToVector3(), fingerDirection, OffAngle, finger.Length, LimitColor);        Gizmos.color = DirectionColor;        Gizmos.DrawRay(finger.TipPosition.ToVector3(), selectedDirection(finger.TipPosition.ToVector3()));      }    }  #endif  }}

(5)ExtendedFingerDetector.cs

using UnityEngine;using System.Collections;using System;using Leap.Unity.Attributes;namespace Leap.Unity {  /**   * Detects when specified fingers are in an extended or non-extended state.   *    * You can specify whether each finger is extended, not extended, or in either state.   * This detector activates when every finger on the observed hand meets these conditions.   *    * If added to a IHandModel instance or one of its children, this detector checks the   * finger state at the interval specified by the Period variable. You can also specify   * which hand model to observe explicitly by setting handModel in the Unity editor or    * in code.   *    * @since 4.1.2   */   //基本的结构与上一个脚本类似,只不过检测的是手指手否展开  public class ExtendedFingerDetector : Detector {    /**     * The interval at which to check finger state.     * @since 4.1.2     */    [Tooltip("The interval in seconds at which to check this detector's conditions.")]    [Units("seconds")]    [MinValue(0)]    public float Period = .1f; //seconds    /**     * The IHandModel instance to observe.      * Set automatically if not explicitly set in the editor.     * @since 4.1.2     */    [AutoFind(AutoFindLocations.Parents)]    [Tooltip("The hand model to watch. Set automatically if detector is on a hand.")]    public IHandModel HandModel = null;    /** The required thumb state. */    [Header("Finger States")]    [Tooltip("Required state of the thumb.")]    public PointingState Thumb = PointingState.Either;    /** The required index finger state. */    [Tooltip("Required state of the index finger.")]    public PointingState Index = PointingState.Either;    /** The required middle finger state. */    [Tooltip("Required state of the middle finger.")]    public PointingState Middle = PointingState.Either;    /** The required ring finger state. */    [Tooltip("Required state of the ring finger.")]    public PointingState Ring = PointingState.Either;    /** The required pinky finger state. */    [Tooltip("Required state of the little finger.")]    public PointingState Pinky = PointingState.Either;    /** How many fingers must be extended for the detector to activate. */    [Header("Min and Max Finger Counts")]    [Range(0,5)]    [Tooltip("The minimum number of fingers extended.")]    public int MinimumExtendedCount = 0;    /** The most fingers allowed to be extended for the detector to activate. */    [Range(0, 5)]    [Tooltip("The maximum number of fingers extended.")]    public int MaximumExtendedCount = 5;    /** Whether to draw the detector's Gizmos for debugging. (Not every detector provides gizmos.)     * @since 4.1.2      */    [Header("")]    [Tooltip("Draw this detector's Gizmos, if any. (Gizmos must be on in Unity edtor, too.)")]    public bool ShowGizmos = true;    private IEnumerator watcherCoroutine;    void OnValidate() {      int required = 0, forbidden = 0;      PointingState[] stateArray = { Thumb, Index, Middle, Ring, Pinky };      for(int i=0; i<stateArray.Length; i++) {        var state = stateArray[i];        switch (state) {          case PointingState.Extended:            required++;            break;          case PointingState.NotExtended:            forbidden++;            break;          default:            break;        }        MinimumExtendedCount = Math.Max(required, MinimumExtendedCount);        MaximumExtendedCount = Math.Min(5 - forbidden, MaximumExtendedCount);        MaximumExtendedCount = Math.Max(required, MaximumExtendedCount);      }    }    void Awake () {      watcherCoroutine = extendedFingerWatcher();    }    void OnEnable () {      StartCoroutine(watcherCoroutine);    }    void OnDisable () {      StopCoroutine(watcherCoroutine);      Deactivate();    }    //协程    IEnumerator extendedFingerWatcher() {      Hand hand;      while(true){        bool fingerState = false;//手指的状态        if(HandModel != null && HandModel.IsTracked){          hand = HandModel.GetLeapHand();          if(hand != null){            //检测五个手指是否匹配RequiredState            fingerState = matchFingerState(hand.Fingers[0], Thumb)              && matchFingerState(hand.Fingers[1], Index)              && matchFingerState(hand.Fingers[2], Middle)              && matchFingerState(hand.Fingers[3], Ring)              && matchFingerState(hand.Fingers[4], Pinky);            int extendedCount = 0;            for (int f = 0; f < 5; f++) {              if (hand.Fingers[f].IsExtended) {                extendedCount++;              }            }            //检测extendedCount是否合法            fingerState = fingerState &&                          (extendedCount <= MaximumExtendedCount) &&                          (extendedCount >= MinimumExtendedCount);            if(HandModel.IsTracked && fingerState){              Activate();            } else if(!HandModel.IsTracked || !fingerState) {              Deactivate();            }          }        } else if(IsActive){          Deactivate();        }        yield return new WaitForSeconds(Period);      }    }    //判断finger的state是否匹配    private bool matchFingerState (Finger finger, PointingState requiredState) {      return (requiredState == PointingState.Either) ||             (requiredState == PointingState.Extended && finger.IsExtended) ||             (requiredState == PointingState.NotExtended && !finger.IsExtended);    }    #if UNITY_EDITOR    void OnDrawGizmos () {      if (ShowGizmos && HandModel != null && HandModel.IsTracked) {        PointingState[] state = { Thumb, Index, Middle, Ring, Pinky };        Hand hand = HandModel.GetLeapHand();        int extendedCount = 0;        int notExtendedCount = 0;        for (int f = 0; f < 5; f++) {          Finger finger = hand.Fingers[f];          if (finger.IsExtended) extendedCount++;          else notExtendedCount++;          if (matchFingerState(finger, state[f]) &&              (extendedCount <= MaximumExtendedCount) &&              (extendedCount >= MinimumExtendedCount)) {            Gizmos.color = OnColor;          } else {            Gizmos.color = OffColor;          }          Gizmos.DrawWireSphere(finger.TipPosition.ToVector3(), finger.Width);        }      }    }    #endif  }  /** Defines the settings for comparing extended finger states */  public enum PointingState{Extended, NotExtended, Either}}

OK,到目前为止,CoreAsset中所有的Detection类基本都介绍了。我们可以通过这些脚本检验手是否握拳、手指是否展开、手指是否沿指定方向、手掌是否沿指定方向等。

0 0
原创粉丝点击