Leap Motion 模拟手势:触击_C++

    鉴于博客里有人留言,发现大家对Leap这一块的兴趣还是很高的,而且最近ZOL今年底的时候也给Leap分配了一定的篇幅来介绍,可见Leap体感(准确说是手势)识别的宣传及普及力度还是有的,至于Leap在未来应用领域的开拓上会有怎样的情况,我们就拭目以待吧。好,闲话不多说,今天翻译另一篇文章——Touch Emulation,模拟手指的触击动作。

    Leap Motion提供了丰富的API,在我们自己开发的应用中,咱可以使用API的输出数据来识别触击动作。需要注意的是,在API中,有关触击的数据是由类Pointable提供的。

1、概述

    为了识别触击动作,Leap设备虚拟出一个自适应的触摸平面,在这个虚拟出来的二维平面上,我们便可以实现数据交互了。虽然虚拟二维平面会根据用户手指及手的位置做调整,但在Leap设备的三维坐标系中,与X-Y面是基本平行的。当用户的手指或者使用工具向前移动时,Leap会判断Pointable对象是在靠近还是在触击虚拟二维平面。借助Leap API,当手指或工具触击到虚拟平面时,API会返回两个值:虚拟面区域的信息、手指与触摸面的距离。

Leap Motion 模拟手势:触击_C++_第1张图片

(图1为虚拟触摸区域,0点左侧+1区域为悬停区,0点右侧-1区域为触击区)

    Leap Motion借助触摸面,能将Pointable对象识别成这样几种情况:悬停在靠近触摸面的地方、触击触摸面、远离触摸面(触击方向有误时也会识别为远离)。三个区域分别称为:“悬停”、“触击”、“无信号”。注意,三个区域的转换常常是延迟于触击diatance的,这样的延迟是用来防止突然性和重复性的转换,所以当在我们自己开发的应用中,在实现触击交互时,我们可以不必过分考虑“触击”区域。

    记住,只有当Pointable对象在悬停区和触击区的时候,触击distance才变为有效。distance是一个在[+1,-1]区间内规格化的数值,当Pointable对象第一次进入悬停区,触击distance为+1.0,随着Pointable对象靠近触击区域,distance的值也逐渐降为0值;当Pointable对象穿透了触摸面(图1中红色网络区域)进入触击区域,distance为0值,随着Pointable对象在触击区域中越来越深,distance值将逐渐变为-1。但注意了,distance值永远不会超出-1。

    在开发应用时,我们可以根据区域信息来判断,是否需更新UI中与悬停和触击动作相关的UI元素。我们还可以根据distance信息表征的触摸面距离信息,进一步修改UI元素。例如,当我们把手指悬在Leap Controller设备的上方、且处在悬停区域时,咱可以把Leap Controller设备当前的聚焦状态显示出来,然后呢,根据distance信息在屏幕上显示对应的游标,作为手指离触摸面距离的反馈信息。

    作为模拟触击API信息中的一部分,Leap Motion提供的Pointable对象位置信息不仅是规格化的,而且数据很稳定。Leap Motion软件内含自适应的滤波器,改滤波器能够对行为动作进行平滑化和平缓操作,以方便用户在屏幕的小块区域进行操作交互(如点击按钮、链接)。当用户的动作缓慢时,平滑化效果会更好,用户点击特殊点的效果会更好,同样,我们可以用这样的方法来做归零矫正。

2.获取触摸面

    触摸面的信息可以通过Pointable类的touchZone属性来获取,触摸面是用Zone枚举产生的,其中包含以下状态信息:

    NONE——此时,pointable对象要么离触摸面太远,要么就是触击方向有问题(比如触击方向为指向用户);

    HOVERING——此时pointable对象指尖已经进入悬停区,但还没有触击;

    TOUCHING——此时pointable对象已经穿过触摸面,进入触击区;

    下面一段代码,描述最前端手指所在区域的检测信息:

    Leap::Frame frame = leap.frame();
    Leap::Pointable pointable = frame.pointables().frontmost();
    Leap::Pointable::Zone zone = pointable.touchZone();

3.获取触击Distance

    触击distance是通过Pointable类的touchDistance属性来获取的,distance随着手指在虚拟平面的靠近和触击,其范围在+1到-1区间内变化。distance信息没有一个实际的数量意义,它只是反映Leap Motion对pointable距离的检测效果。

    下面一段代码,描述了最前端手指的触击distance:

    Leap::Frame frame = leap.frame();
    Leap::Pointable pointable = frame.pointables().frontmost();
    float distance = pointable.touchDistance();

4.获取Pointable的稳定位置信息

    Pointable对象的位置信息是通过Pointable类的stabilizedTipPosition属性,此位置信息是基于标准Leap Motion十字坐标来输出的,并且经过了上下文相关的滤波和稳定化处理。

    下面一段代码,描述最前端手指的稳定位置信息:

    Leap::Frame frame = leap.frame();
    Leap::Pointable pointable = frame.pointables().frontmost();
    Leap::Vector stabilizedPosition = pointable.stabilizedTipPosition();

5.Leap Motion设备坐标系和App坐标系的转换

    当开发触击模拟相关app应用时,我们必须把Leap Motion设备的坐标系映射到app的屏幕空间中。为了方便映射,Leap Motion的API提供了InteractionBox类。InteractionBox描绘的是在Leap Motion视野范围内一个长方体容器,该类提供了一个函数,函数会把长方体空间内的位置映射到[0,1]规格化区间内,我们先将实际的位置信息规格化,然后根据app的尺寸产生最终的坐标,最后在app坐标系中得到一个点。

    例如,如果我们在客户区内有一窗口,窗口大小由变量windowWidth、windowHeight确定。当我们手指触击的位置在客户区范围内的时候,我们可参考下面的代码,来得到触击点的二维坐标值:

    Leap::Frame frame = leap.frame();
    Leap::Finger finger = frame.fingers().frontmost();
    Leap::Vector stabilizedPosition = finger.stabilizedTipPosition();

    Leap::InteractionBox iBox = leap.frame().interactionBox();
    Leap::Vector normalizedPosition = iBox.normalizePoint(stabilizedPosition);
    float x = normalizedPosition.x * windowWidth;
    float y = windowHeight - normalizedPosition.y * windowHeight;

6.触击案例
    在下面的例子中,对于app应用窗口中检测到的所有Pointable对象,我们通过调用模拟触击的API,将pointable对象的位置显示出来。本例中,在触摸区域内,我们将触击点涂色,并根据触击distance来设置阿尔法值。指尖的稳定位置数据通过类InteractionBox来映射到app窗口:

Leap Motion 模拟手势:触击_C++_第2张图片

    本例中,触击的代码如下(本例通过调用库Cinder来创建app窗口并绘图):

#include "cinder/app/AppNative.h"
#include "cinder/gl/gl.h"
#include "Leap.h"
#include "LeapMath.h"

using namespace ci;
using namespace ci::app;
using namespace std;

class TouchPointsApp : public AppNative {
  public:
        void setup();
        void draw();
  private:
    int windowWidth = 800;
    int windowHeight = 800;
    Leap::Controller leap;
};

void TouchPointsApp::setup()
{
    this->setWindowSize(windowWidth, windowHeight);
    this->setFrameRate(120);
    gl::enableAlphaBlending();
}

void TouchPointsApp::draw()
{
        gl::clear( Color( .97, .93, .79 ) );
    Leap::PointableList pointables = leap.frame().pointables();
    Leap::InteractionBox iBox = leap.frame().interactionBox();

    for( int p = 0; p < pointables.count(); p++ )
    {
        Leap::Pointable pointable = pointables[p];
        Leap::Vector normalizedPosition = iBox.normalizePoint(pointable.stabilizedTipPosition());
        float x = normalizedPosition.x * windowWidth;
        float y = windowHeight - normalizedPosition.y * windowHeight;

        if(pointable.touchDistance() > 0 && pointable.touchZone() != Leap::Pointable::Zone::ZONE_NONE)
        {
            gl::color(0, 1, 0, 1 - pointable.touchDistance());
        }
        else if(pointable.touchDistance() <= 0)
        {
            gl::color(1, 0, 0, -pointable.touchDistance());
        }
        else
        {
            gl::color(0, 0, 1, .05);
        }

        gl::drawSolidCircle(Vec2f(x,y), 40);
    }
}

CINDER_APP_NATIVE( TouchPointsApp, RendererGl )





你可能感兴趣的:(Leap Motion 模拟手势:触击_C++)