作者:电脑信息网日期:
返回目录:win7问题
概述
屏幕类提供了一个在参照Leap Motion帧中显示屏的坐标和朝向。函数Screen::intersect()计算手指或工具指向方向射线与屏幕平面的交点的位置。此外,Screen::project()函数计算某点投影到屏幕平面的位置。
在使用Screen类之前,考虑到注册屏幕位置会需要你花一些精力,并且只有当你移动屏幕或者Leap才是有效的。你的应用对于API的使用必须是充分值得的,这样用户才会觉得他们的努力没有白费。我们建议你,让你的用户清楚了解为什么你需要他们去注册他们屏幕的位置,并且为用户提供一个快速的测试以验证屏幕位置是否依旧准确。
使用屏幕定位功能的说明可以参考Screen location.
屏幕坐标
对于每个已知屏幕,屏幕类提供了一个原点和向量以表示屏幕范围水平和垂直方向。一个正交的向量也会有提供。
上图:原点和已定位屏幕的方向向量
屏幕的原点坐落在下方,屏幕的左手角落。坐标向量与屏幕一边平行。向量的长度表示以毫米为单位的屏幕长度。正交向量是一个单位朝向向量与屏幕表面正交。
交点和投影点
屏幕类提供了一个intersect()方法来计算手指或者工具指向屏幕的交点。这个函数从尖端物体发射一个射线,然后判断射线与屏幕平面在哪里相交。只要尖端物体指向屏幕所处位置,就会返回一个有效的数值。如果交点是平行的或者指向方向不在屏幕上,在交叉向量中的坐标则都为NaN(not-a-number)。
屏幕类还提供了一个近似的intersect()函数,接受位置和朝向向量,并且计算它与屏幕交点,这样就不需要一个尖端物体了。
上图:三个指向以及与之对应的交叉点
@指针A与屏幕平面相交于屏幕外侧。e5a48de588b6e799bee5baa6e997aee7ad94339
@指针B与屏幕直接相交。
@指针C没有与屏幕相交。[是平行的]
另外,屏幕类提供了一个project()函数来计算点在屏幕上的投影位置。与intersect()函数不同的是,project()函数永远返回一个有效值,因为点永远可以投影到平面上。
上图:2个点与相对于的投影点
@点A直接投影到屏幕上。
@点B投影到了屏幕外侧。
你可以依据Leap Motion坐标系统不断调用得到交点和投影点。返回的坐标是三维向量,以Leap Motion为原点毫米为单位衡量的点坐标。
依次的,你可以请求二维归一化的坐标,它们把交点和投影点定义成相对于屏幕低端左下角并且在屏幕平面内的坐标。
当使用归一化坐标时,原点是(0,0,0),相反的右上角坐标为(1,1,0)。这些点在屏幕平面以内,并且它们的x和y数值在0到1之间,表示点距离屏幕两边的比率(当使用归一化坐标时,z坐标的值一直是0)。例如,在屏幕中心的点被归一为(0.5,0.5,0)。对于交点和投影点,归一化的坐标在屏幕边界外时可能小于0也可能大于1。
默认时,intersect()和project()函数将坐标归一化到(clamp夹在这个范围里)坐标范围(0~1,0~1,0)。这意味着,任何在屏幕边框外的交点和投影点实际上被移动到边框上。但调用intersect()或者project()函数时,你可以通过设置clapRatio参数,来修改默认的“夹板区域”。默认的夹板率是1.0。使用一个更小的数可以减小夹板区域,而使用一个大的数值可以增大区域。例如将clampRatio设置为0.5会将交互区域缩小为以屏幕为中心50%区域,并且把说有点的坐标压缩在0.25到0.75范围里。同样的,如果把clampRatio设置为2.0会将交互区域扩大1倍,点的坐标扩展到-0.5到1.5之间[这二个功能很实用啊,这样可以限定交互区域范围,减少开发难度]。无论怎么设置,交互区域永远是以屏幕为中心的[一般都是盯着屏幕操作的(主要是能直接看到操作反馈结果),他们这样设计理所当然]。
找到最近的屏幕
你可以通过控制器实例对象的注册位置得到一张所有屏幕的列表:
Controller controller;
ScreenList screens = controller.calibratedScreens();
注意,屏幕列表永远至少包含一项,即使用户从未注册过屏幕的位置。这项代表用户默认的屏幕。
你可以获取从手指或工具引出的射线会与屏幕列表对象相交的,最近的屏幕[如果有好多屏幕也只会得到最近的那个]:
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreenHit(pointable);
}
同样地,你还可以在屏幕列表对象中,通过一个点(诸如指尖或者工具的末端位置)来得到与之最靠近的屏幕。[注意就最后一行不一样,函数和传入参数都不一样]
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreen(pointable.tipPosition());
}
找到交点的像素坐标
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreenHit(pointable);
Vector normalizedCoordinates = screen.intersect(pointable, true);
int xPixel = (int)(normalizedCoordinates.x * screen.widthPixels());
int yPixel = screen.heightPixels() - (int)(normalizedCoordinates.y * screen.heightPixels());
}
找到在Leap Motion坐标系下的交点坐标
你可以使用intersect()函数得到归一化的坐标,以帮你计算在屏幕上的某一个交点的像素点坐标[这个很必要!]。
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreenHit(pointable);
Vector normalizedCoordinates = screen.intersect(pointable, true);
int xPixel = (int)(normalizedCoordinates.x * screen.widthPixels());
int yPixel = screen.heightPixels() - (int)(normalizedCoordinates.y * screen.heightPixels());
}
设置交互区域的大小
当使用归一化坐标时,通过调用inersect()函数来设置calmpRatio参数,你可以将用户说指向的交互区域扩大或缩小。默认情况下,clampRatio参数为1.0,与屏幕边缘相匹配[自适应很好很强大]。返回任何在屏幕边界外交点的坐标时,它们都会被归一到边界上。(在交互区域内的点不会受到归一影响)
为了将交互区域二等分,在你调用intersect()函数时,可以将clampRatio参数设置为0.5。
Vector normalizedCoordinates = screen.intersect(pointable, true, 0.5);
如果需要把区域扩展一倍,可以将clampRatio也乘以2。
Vector normalizedCoordinates = screen.intersect(pointable, true, 2.0);
找到指向某处的手指或工具与屏幕的距离
为了取得手指或者工具的指向与屏幕交点的坐标,可以简单地将2个表示这些点的向量相减再取模就可以得到结果[中学几何知识]。当调用函数intersect()来获取交点,可以将归一化参数设置为false。
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreenHit(pointable);
Vector intersection = screen.intersect(pointable, false);
Vector tipToScreen = intersection - pointable.tipPosition();
float pointingDistance = tipToScreen.magnitude();
}
为了得到手指、工具与屏幕平面最近的距离,也就是从尖端末梢的点做垂线到屏幕的那个距离,可以使用distanceToPoint()函数传入尖端坐标。
Controller controller;
Frame frame = controller.frame();
if (frame.pointables().count() > 0) {
Pointable pointable = frame.pointables()[0];
ScreenList screens = controller.calibratedScreens();
Screen screen = screens.closestScreenHit(pointable);
float perpendicularDistance = screen.distanceToPoint(pointable.tipPosition());
}
有了Leap Motion,大家可以跟游戏手柄说拜拜了。
现代智能手机凭借着以手势与触控为核心的新型用户界面为我们带来前所未有的使用体验。随着时间的不断推移,我们几乎可以肯定地认为,在计算的未来中触控技术将扮演愈发重要的关键性角色。不过话虽如此,一系列艰难挑战仍然横亘在开发者面前、使他们很难在台式设备上充分发挥触控机制的巨大优势。
深度相机与3D位置追踪器的出现隐隐给触控式互动操作的迅速普及指出了一条光明的发展道路。虽然目前的最新一代技术仍然远远无法与《少数派报告》中的展示效果相提并论,但其中蕴含的可观潜力却绝对不容置疑。
就在去年,我们曾经对Leap Motion控制器进行过一番评测——这款设备利用高精度动作捕捉与手指追踪技术成功在任何标准化台式机上实现了手势输入机制。作为一套经由标准USB端口连接的设备,Leap Motion控制器通过其内置摄像头与红外LED来捕捉用户手指与手部的细微活动。Leap Motion软件会对图像数据加以处理,并将这些信息翻译成手势以及触控事件。
Leap Motion提供一套指向性极广的SDK,旨在帮助开发人员更轻松地在自己的应用程序当中实现对该设备的支持。它支持跨越多种平台的一系列不同编程语言——除了原生桌面软件之外,Leap Motion还提供一套JavaScript库,从而通过构建Leap兼容性网站的方式将开发成果在Web浏览器上加以呈现。
Leap Motion SDK非常容易上手,掌握之后能为我们带来极为丰厚的回报。它以抽象化方式消除了控制器所固有的大部分复杂因素,通过高级别API实现了对手部及手指追踪数据的捕捉。在今天的文章中,我们将共同探讨如何构建起一款能够充分发挥Leap Motion追踪功能优势的前端Web应用程序。作为初始内容,我们先来说明如何在HTML Canvas元素上渲染手指定位点,而后再一步步讨论怎样利用Pixi.js图形库来配合Leap Motion控制机制开发出简单的2D游戏。
正式开始
Leap Motion JavaScript库依赖于WebSockets将来自控制器的数据提交并显示在用户的Web浏览器之上。WebSockets标准的设计目标在于允许JavaScript代码运行在网页当中,从而确保网页与远程服务器之间始终保持有长效连接——这一特性通常被用于创建基于浏览器的聊天客户端以及其它一些实时类Web应用程序。
当用户部署好了自己的Leap Motion设备、并为其安装了附带的软件及驱动程序之后,其中作为内置软件组件之一的轻量级WebSocket服务器就会运行在用户计算机的后台进程当中。由Leap Motion控制器捕捉到的数据会被发往WebSocket服务器,这是为了能够让相关数据直接交由Web浏览器使用、而无需另行安装额外的浏览器插件。Leap Motion JavaScript库与本地WebSocket服务器相连,负责捕捉数据并利用部分简单API进行打包以保证其易于使用。
作为开发工作的第一步,让我们首先创建一个网页,并在其中载入Leap Motion JavaScript库、获取来自设备的数据并记录下浏览器调试控制台中的部分数据:
<html> <head> <script src="http://js.leapmotion.com/leap-0.4.2.js"></script> </head> <body> </body> <script type="text/javascript"> Leap.loop(function(frame) { if (frame.pointables.length > 0) console.log(frame.pointables); }); </script> </html>
上述代码的head元素中包含一个script标签,它的作用是从公司的CDN处下载Leap Motion JavaScript库。Leap Motion针对生产使用环境推出一套精简版本,此外还针对开发用途提供非精简版本。在这里我们使用的是非精简版本,这是为了能够在需要使用浏览器的JavaScript调试器时能够更轻松地对代码进行单步调试。大家可以点击此处访问Leap Motion官方网站,并在这里下载到前面提到的这两种版本。
在第二个script标签中,也就是页面body之下,我们利用Leap.loop方法对来自设备的数据进行捕捉。Leap Motion驱动程序会发出数据“帧(frame)”,这些帧也就是经过处理的控制器视频流快照。该软件每秒大约会产生30帧数据,从而持续不断地为应用程序的运行提供必要信息。被传递至该loop中的匿名函数在每次接收到新帧时都会执行一次。
Leap Motion API大大简化了对手部、手指以及工具位置的检测流程。这里的“工具”被认定为一种延长状物体,例如铅笔,其中一端由用户把持在手中。在Leap Motion的表述体系当中,通用术语“指向物(pointable)”被用于描述作为工具或者手指存在的对象。帧对象当e69da5e887aae79fa5e98193339中包含一项名为pointables的属性,用于显示一系列显示在帧内的指向物对象。
在前面的示例中,每一帧内所包含的一系列pointables都被输出至控制台当中。如果大家检查这些被推送至控制台的指向物对象,就会发现其中有多项属性被用于描述显示信息——例如指向物的长度与宽度、指向物末端的空间坐标以及该指向物末端的移动速度等。
在火狐开发者控制台中查看指向物对象。
计算标准化手指位置
利用指向物数据,我们将建立一套简单的演示范例,用户可以利用它通过移动手指控制屏幕上某个元素的位置。第一步,我们需要获取指尖的具体位置。下面的示例代码为如何获取单个指向物的原始坐标:
Leap.loop(function(frame) { if (frame.pointables.length > 0) { var position = frame.pointables[0].tipPosition; console.log("X: " + position[0] + " Y: " + position[1]); } });
在多数情况下,大家都会优先使用Leap Motion软件所提供的自动稳定功能,而不太可能直接使用原始tipPosition数据。Leap Motion控制器会以极高的精度对图像中的活动对象加以检测,并从中甄别出使用者手部几乎难以察觉的细微摇晃及活动。另一项名为stabilizedTipPosition的备选属性则允许我们收集同样的数据,但在结果中过滤掉上述细微活动。现在我们已经获得了用户手指的物理坐标,接下来要做的就是将其与浏览器窗口内的位置进行关联。
在Leap Motion的术语体系中,由控制器加以追踪的大型虚拟空间被称为互动框(interaction box)。帧对象的interactionBox属性当中包含多种方法与属性,它们负责提供与互动框及其维度相关的具体信息。它采用一种便捷的标准化方法,即将某个物理点的原始坐标换算成能够代表该点在互动框中相对位置的等效数值。
标准化定位机制非常实用,因为它能帮助我们获取手指的原始位置、并将其与应用程序窗口中的对应点加以映射。标准化坐标使用浮点数值格式,具体数值在0到1之间浮动。要想获取浏览器窗口中对应点的相对数值,大家只需将X与Y值同目标区域的高度与宽度相乘即可:
Leap.loop(function(frame) { if (frame.pointables.length > 0) { var position = frame.pointables[0].stabilizedTipPosition; var normalized = frame.interactionBox.normalizePoint(position); var x = window.innerWidth * normalized[0]; var y = window.innerHeight * (1 - normalized[1]); console.log("X: " + x + " Y: " + y); } });
在屏幕上绘制手指位置
利用标准化定位机制,现在我们已经可以在网页上绘制手指的具体位置。为了简单起见,这篇文章将只向大家展示如何利用DOM元素实现这一目标。只需创建一个具备绝对位置的div,而后将其top与left属性分别设置为对应的X与Y坐标,我们就能将Leap Motion loop中的标准化函数用于定位:
<html> <head> <script src="http://js.leapmotion.com/leap-0.4.2.js"></script> <style type="text/css"> #position { width: 25px; height: 25px; position: absolute; background-color: blue; } </style> </head> <body> <div id="position"></div> </body> <script type="text/javascript"> Leap.loop(function(frame) { if (frame.pointables.length > 0) { var position = frame.pointables[0].stabilizedTipPosition; var normalized = frame.interactionBox.normalizePoint(position); var element = document.getElementById("position"); element.style.left = window.innerWidth * normalized[0]; element.style.top = window.innerHeight * (1 - normalized[1]); } }); </script> </html>
这样一来,当用户在Leap Motion控制器前方移动自己的手指时,屏幕上的div元素也将随之发生位移。在目前为止的示例中,我们只涉及单一手指的处理,也就是使用pointables数组中的第一个元素。在接下来的示例中,我们将逐步探寻数组中第一个元素的作用、从而使每根手指都成为可操作对象。这一次,我们将在HTML 5 Canvas上描绘手指位置、而不再使用DOM元素:
<html> <head> <script src="http://js.leapmotion.com/leap-0.4.2.js"></script> </head> <body> <canvas id="canvas" width="800" height="600"></canvas> </body> <script type="text/javascript"> var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); Leap.loop({frameEventName: "animationFrame"}, function(frame) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); frame.pointables.forEach(function(pointable) { var position = pointable.stabilizedTipPosition; var normalized = frame.interactionBox.normalizePoint(position); var x = ctx.canvas.width * normalized[0]; var y = ctx.canvas.height * (1 - normalized[1]); ctx.beginPath(); ctx.rect(x, y, 20, 20); ctx.fill(); }); }); </script> </html>
在上面的示例中,我们利用forEach方法对pointable项目进行了遍历。它会对每一个标准化位置进行识别,而后将其作为矩形绘制在屏幕当中。此外,clearRect方法在处理每一帧图像时都会被调用一次,旨在确保前一帧所绘制的矩形切实得到清除。
上述示例当中还引入了另一项新功能,即frameEventName选项。在默认状态下,Leap.loop回调将被调用至每一个提取自Leap Motion控制器的帧数据。对frameEventName选项中的“animationFrame”值进行设置会改变这一行为机制,从而令Leap.loop与浏览器的绘制周期保持一致。它会利用浏览器的requestAnimationFrame API,从而确保该回调只在浏览器准备进行绘制时被调用。
检测手势
除了手指位置,Leap Motion SDK还能够识别出其它几种手势动作,其中包括扫动与点触。Leap Motion帧对象当中包含一个手势属性,能够提取从帧数据中检测出的一系列手势信息。以下示例代码显示了如何对扫动手势进行迭代、对其起始与结束位置进行标准化处理并最终将结果绘制在Canvs当中:
var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var options = { enableGestures: true, frameEventName: "animationFrame" }; Leap.loop(options, function(frame) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); frame.gestures.forEach(function(gesture) { if (gesture.type != "swipe") return; var start = frame.interactionBox.normalizePoint(gesture.startPosition); var end = frame.interactionBox.normalizePoint(gesture.position); var startX = ctx.canvas.width * start[0]; var startY = ctx.canvas.width * (1 - start[1]); var endX = ctx.canvas.width * end[0]; var endY = ctx.canvas.width * (1 - end[1]); ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); ctx.stroke(); }); });
Leap Motion SDK在默认状态下不会显示手势。为了获取手势数据,我们必须将选项对象的enableGestures属性设为true,而后再将其传递至Leap.loop方法当中。如果该选项没有经过设置,那么frame.gestures数组将直接为空。
每一种手势都具备一项type属性,用于描述该手势的相关性质。在forEach loop当中,首先利用条件表达式来忽略不属于扫动的手势动作。接下来,利用interactionBox对手势的起始与结束位置进行标准化处理。最后,利用标准Canvas绘制API来描绘由起始位置到结束位置的线条。
在面对Leap Motion设备进行手部扫动操作时,大家应该能够在Canvas上看到自己的动作轨迹线条。手势检测在更为广义的互动操作之下起着非常关键的作用。举例来说,大家可以通过向左或者向右的扫动操作帮助用户导航至相册界面的上一个或者下一个显示条目当中。
打开“开始”菜单,在“计算机”上单击鼠标右键,在弹出的快捷菜单中选择“属性”,打开系统属性窗口。在“系统”类别下,有一个名为“笔和触摸” 的属性,只要这里显示为“可用”,那么就表示这台计算机是支持触摸操作的,如图1所示,如果显示为“单点触摸”,表示这台计算机支持单点触摸,也就是可以使用一根手指进行操作;如果显示为“多点触摸”,则表示这台计算机支持多点触摸,可同时使用两根或更多根手指进行操作。
2、首先介绍用触摸方式实现鼠标操作的方法。对于支持触摸屏的设备,可以直接用手指或专用的触控笔在屏幕上进行指点,实现鼠标操作的结果。例如,直接碰触屏幕上的内容一次,可以起到鼠标左键单击的效果,而快速碰e5a48de588b6e79fa5e98193333触两次,则可起到左键双击的效果;如果碰触并保持,稍等片刻,碰触点周围会出现一个逐渐合拢的圆圈图案,当圆圈首尾相接后,就会出现右键菜单,而形成这个圆圈的时间就是为了预防误操作而设置的延迟时间。
左右键单击操作实现后,还有一个比较棘手的问题,那就是鼠标的指向操作。例如,在使用致标时,将鼠标指针指向屏幕上的某个文件,系统会自动用屏幕提示的方式显示有关该文件的相关信息。但在使用触摸方式操作时,这种指向操作就不太容易实现了。如果设备使用了电磁感应式触摸屏,那么把触控笔悬停在屏幕上方lcm左右的距离,即可实现“指向”操作;不过现在很多设备,尤其是多点触摸设备,大部分使用了压感式屏幕,要求必须将手指紧贴屏幕表面才能生效
3、为了解决这一问题,可以使用win7提供的虚拟鼠标,该功能可以在屏幕上虚拟出一个鼠标的图案,这个鼠标有左右键,用户可以通过拖动虚拟鼠标的方式移动鼠标指针,以实现指向、单击、双击,以及左右键单击等效果。该功能默认没有启用,需要按照下列步骤设置。
4、打开“控制面板”,依次进入“硬件和声音”一“笔和触摸”,打开“笔和触摸”对话框切换到“碰”选项卡,在“触摸指针”选项下,选中“与屏幕上的项交互时显示触摸指针”。
5、单击“高级选项”按钮,打开高级选项对话框,在这里可按需要对屏幕上显示的虚拟鼠 标进行设置,例如左手或右手习惯、虚拟鼠标的透明度和大小,以及光标的移动速度等设置完毕单击“确定”按钮,关闭所有打开的对话框。
6、在经过上述设置后,在使用手指或触控笔点击屏幕后,碰触点周围就会出现一个虚拟的鼠标图案,如图2所示,随后可以用手指或触控笔拖动这个鼠标,以移动指针,或者点击该鼠标的左右键,实现鼠标单击操作。