Leap Motion 之 官方SDK代码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/goodboy0077/article/details/68936080

【仅用于个人复习使用,顺序较乱】
[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类基本都介绍了。我们可以通过这些脚本检验手是否握拳、手指是否展开、手指是否沿指定方向、手掌是否沿指定方向等。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页