Leap Motion 之 官方SDK代码分析
来源:互联网 发布:发票二维码扫描软件 编辑:程序博客网 时间:2024/05/01 19:26
【仅用于个人复习使用,顺序较乱】
[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的结构:
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信息,主要就是开始握紧、保持握紧以及结束握紧三个状态。下面是对应的函数:
通过对三个状态的判断,去执行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
示意图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:
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类基本都介绍了。我们可以通过这些脚本检验手是否握拳、手指是否展开、手指是否沿指定方向、手掌是否沿指定方向等。
- Leap Motion 之 官方SDK代码分析
- Leap Motion 之 准备工作
- Leap Motion 之 Base Class
- Leap Motion SDK学习--API(翻译)
- 【VR】Leap Motion 官方脚本手册翻译之---HandController(手型控制器)
- Leap Motion 之Unity 开发指南(二. 开发架构和SDK)L
- leap motion
- Leap Motion
- Leap Motion
- [资料汇总]Leap Motion官方开发文档翻译
- Leap Motion 入门二:官方Sample个人解读
- Leap Motion 官方文档 SkeletalHand(骨骼手型)
- 【VR】Leap Motion 官方文档 SkeletalHand(骨骼手型)
- Develop Leap Motion in Ubuntu 16.04.02 LTS: SDK install
- Leap Motion自带Sample之详解_Win/C++版本
- Leap Motion自带Sample之详解_Win/C++版本
- Leap Motion自带Sample之详解_Win/C++版本
- Leap Motion自带Sample之详解_Win/C++版本
- Proud Merchants 。。。01背包
- 【爬虫】添加数据外键下载图片
- 深度学习之caffe入门一一配置SSD中遇到的问题
- 第六届蓝桥杯
- 深度优先搜索之城堡问题
- Leap Motion 之 官方SDK代码分析
- Java设计模式笔记之工厂模式
- XMVECTOR学习笔记
- KEIL中怎样添加STC系列单片机
- pat乙级1021
- 三子棋(京东2016实习生真题)
- 中国目前还未掌握的核心技术有哪些?
- B树,B+树,B*树
- android应用卸载后的监听