现在,我们来到了本书最激动人心的部分,在这里,我们将通过将肢体语言转换为控制周围物体和计算机的命令,进入新的非接触式人机交互时代。
每天我们都会注意到越来越多的输入界面不再以鼠标为中心,而更倾向于非接触式输入。手势是当今人类与机器自然交流的方式之一。
几十年来,运动控制在我们对未来的憧憬中一直占有一席之地。我们已经看到了流行媒体中的超级英雄、疯狂的科学家和太空牛仔,他们只用一挥手就能控制数字体验。
汤姆·克鲁斯用手势进行计算
我们被这些强大、自然和直观的互动所吸引,想象着在我们的指尖拥有这种力量会是什么样子。例如,星际迷航的全息甲板和少数派报告的犯罪前视觉计算机。你还记得汤姆·克鲁斯(Tom Cruise)是如何在透明显示器上使用手势进行计算的吗?所有这些都散发出一种力量感和掌控感,以及对简单、轻松、直觉和人性的自相矛盾的感觉。简单地说,这些经历让人觉得不可思议。
市场上有几种设备实际上允许我们只使用身体的某些部分与计算机进行交互:许多用于Xbox的游戏,微软游戏控制台,使用Kinect控制器识别用户的身体动作。肌电臂章可以检测肌肉的运动并将其转换为手势,这样你就可以与电脑互动。Leap Motion controller 可识别用户的手和手指,并将动作和手势转换到计算机。
在本章中,您将学习如何使用Leap Motion设备进行手势识别,这是一款非常棒的设备,可以通过非接触方式开发增强的 JavaFX 应用。
以下是我们将在本章中讨论的一些主题:
- 介绍 Leap 控制器,它是如何工作的,在哪里可以买到它
- 获取和安装 SDK,配置其驱动程序,并验证其是否工作
- 基于 Leap 的应用构建块的基础知识
- 开发惊人的非接触式 JavaFX 应用
这是一个非常小的设备,高 13 毫米,宽 30 毫米,深 76 毫米,重 45 克(最终尺寸:0.5 英寸 x1.2 英寸 x3 英寸),在电脑上运行 Leap Motion 软件后,只需将控制器插入 Mac 或 PC 上的 USB 即可(无需任何外部电源)。
在这里,它与上面的内容一起工作,几乎实时(200-300 fps)捕获你的手和手指的单个动作,并将手势转换为在你的计算机上运行的应用上的不同动作。这款售价 79.99 美元的设备于 2013 年推出,名为 Leap Motion controller。
与人手相比的跳跃运动大小
从开发人员的角度来看,该设备允许设计应用,只需用户的手和手指的手势和动作即可控制,就像在报告中一样!
它能感知你如何自然地移动你的手,让你以一种全新的方式使用你的电脑——指向、挥手、伸手、抓取或拿起东西并移动它。你可以做你从未梦想过的事情。
检查你的手;一只手有 29 块骨头、29 个关节、123 条韧带、48 条神经和 30 条动脉。这既复杂又复杂。控制员已经非常接近把一切弄清楚了。
事实上,仔细想想,Leap Motion 的魔力来自于软件,但该公司在硬件上努力工作,以提供他们的技术。自 2011 年启动以来,它一直处于开发阶段。演变如下图所示:
跳跃运动控制器的发展
Leap Motion 的技术依赖于特殊的接收器硬件和定制软件,可以跟踪 1/100 毫米的移动,没有明显的延迟时间。Leap Motion 控制器具有150 度视野,它以 290 fps 的速度跟踪单个手和所有 10 个手指。
该设备的主要硬件由三个红外 LED 和两个单色红外(IR)摄像机组成。当 LED 产生红外光点的 3D 图案时,摄像机以接近 290 fps 的速度扫描反射数据。将以 0.01 mm 的分辨率扫描和处理半径为 50 cm 内的所有物体。装置的主要部件如下图所示:
Leap 运动控制器硬件层和内部组件
这是计算机交互的未来——Leap Motion 难以置信的快速和准确的自然用户界面,它以非常精确的方式将所有运动数据发送到计算机。数据将通过 Leap Motion 专有软件检测算法在主机中进行分析,任何启用 Leap 的应用都可以直接连接,而无需使用任何其他物理输入设备。
在应用中使用 Leap Motion controller 时,将从控制器接收到的坐标值映射到适当的 JavaFX 坐标系是一项基本任务。
从前面的讨论中,您可以看到,该设备可以在超宽的 150 度视野和 z 轴深度范围内检测手、手指和反射工具。这意味着您可以在 3D 中移动手,就像在现实世界中一样。
设备坐标系使用右手笛卡尔坐标系,原点位于设备中心。如下图所示:
以设备为中心的坐标系
每次设备扫描并分析您的手的运动并将其转化为数据时,都会生成一个框架对象,其中包含所有已处理和跟踪数据的实例列表(手、手指和工具),包括在框架中找到的一组运动手势(滑动、点击或旋转。
您可能已经注意到,在大多数计算机图形系统(包括 JavaFX)中,y 轴正方向与向下方向相反。
然而,数据是指设备位置,而不是我们习惯于鼠标和触摸事件的屏幕,这一事实极大地改变了我们需要思考的方式。
幸运的是,API 提供了几种有用的方法来查找我们的手和手指在任何时候指向的位置。
当我们受到这项惊人技术的启发时,我们需要参与并开始开发该设备。所以,我们需要先得到一个。
该设备可从许多供应商获得,如亚马逊、百思买等。不过,您也可以从 Leap Motion 商店(购买 http://store-world.leapmotion.com )。
我在 2014 年底买了我的设备,现在你可能在一些商店里找到特别折扣。
当您购买 Leap Motion 套餐时,它至少应包含下图所示的项目:
Leap Motion 软件包内容
在撰写本报告时,该文件包包括:
- 跳跃运动控制器
- 两条自定义长度 USB 2.0 电缆
- 欢迎卡
- 重要信息指南
既然我们有了硬件,我们需要安装软件并开始开发。这是一项非常容易的任务;只需将鼠标指向您喜爱的浏览器的地址栏,键入 URLhttps://developer.leapmotion.com/downloads ,点击输入键。
在撰写本文时,最新版本是 SDK 2.2.6.29154。单击操作系统图标开始下载支持的版本。或者,只需点击带有标签的绿色按钮,下载 OSX(适用于 Mac OS X)的 SDK 2.2.6.29154 即可。这将检测您的 PC/笔记本电脑操作系统,并允许您下载适合您操作系统的 SDK。
安装过程和让您的设备为交互做好准备需要几个简单的步骤。下载zip
内容后,解压缩,安装软件安装程序,一切就绪:
-
下载、解压缩并运行软件安装程序。
-
After installation, connect your Leap Motion controller and open the Visualizer, as shown in the following screenshot:
运行可视化工具
-
SDK 由一个
LeapJava.jar
库和一组用于控制器集成的本机库组成。在您的系统上集成LeapJava.jar
的一个简单方法是在 Linux 或 Windows(或/Library/Java/Extensions on the Mac
上)的<JAVA_HOME>/jre/lib/ext
中添加 JAR。 -
将本机库(
LeapJava.dll
、Leap.dll
和Leapd.dll
用于 Windows;libLeapJava.dylib
和libLeap.dylib
用于 Mac;libLeapJava.so
和libLeap.so
用于 Linux)复制到<JAVA_HOME>/jre/bin
文件夹中。 -
Alternatively, you can just add the JAR to every project as a dependency and load the native libraries as a VM argument
-Djava.library.path=<native library path>
.SDK 还包括许多基于支持的语言的示例,包括
HelloWorld.java
示例,这是了解如何将控制器与 Java 应用集成的良好基础。
如果一切正常,则任务栏通知区域(Windows)或菜单栏(Mac)上应显示一个小的跳跃运动图标,该图标应为绿色,如前一个屏幕截图所示。设备上的 LED 指示灯应亮起,且面向您,以指示设备的正确方向。
如果您能够在可视化工具打开时进行交互并看到手指和手的可视化,如下面的屏幕截图所示,那么是时候开始开发了。
Leap 运动诊断可视化工具应用
在深入研究我们的应用之前,我想说的是,ListScundSDK 支持多种语言,包括 java 和其他 java 语言,如 JavaScript 用于 Web、C 语言、C++、Python、Unity、Objto-C 和不真实的游戏引擎。
和您一样,我迫不及待地开始开发过程,现在您将学习如何与连接到 Leap motion 设备的基于 JavaFX 8 3D 的应用进行无接触交互。
鉴于本书尚未涉及 3D API,这是一个很好的机会来简要介绍 3D API,并将 Leap Motion v2 骨骼建模(hand in 3D)和一些 3D 交互引入到我们的 JavaFX 应用中。
Leap Motion API v2.0 引入了一个新的骨骼跟踪模型,该模型提供了有关手和手指的附加信息,预测了看不清的手指和手的位置,并改进了总体跟踪数据。有关 API 的更多信息,请访问https://developer.leapmotion.com/documentation/java/devguide/Intro_Skeleton_API.html?proglang=java 。
我们将构建并展示如何将 Leap Motion v2 中的新骨骼模型轻松集成到 JavaFX 3D 场景中。我们将使用 JavaFX 提供的预定义 3D 形状 API 快速创建开箱即用的 3D 对象。这些形状包括我们将在应用中使用的长方体、圆柱体和球体。
3D 表示三维或具有宽度、高度和深度(或长度的物体。我们的物理环境是三维的,我们每天都在三维空间中移动。
JavaFX 3D 图形库包含 Shape3D API,JavaFX 中有两种 3D 形状:
- 预定义形状:提供这些的目的是让您更容易快速创建三维对象。这些形状包括长方体、圆柱体和球体。
- 自定义形状:JavaFX 网格类层次结构包含
TriangleMesh
子类。三角形网格是 3D 布局中使用的最典型的网格类型。
在我们的应用中,我们将使用预定义的形状。有关 JavaFX3D API 和示例的更多信息,请访问http://docs.oracle.com/javase/8/javafx/graphics-tutorial/javafx-3d-graphics.htm 。
在 Leap Motion controller 和普通 Java 应用之间的开发和集成过程中,丰富的资源之一是与 SDK 捆绑的HelloWorld.java
示例。
另一个讨论与 Java 集成的资源是 Leap motion 文档中的Java 开发入门部分,可在上找到 https://developer.leapmotion.com/documentation/java/devguide/Leap_Guides.html 。
在查看HelloWorld.java
示例和文档示例之后,您将注意到以下几点:
- 我们需要一个
Controller
对象,允许 Leap 设备和应用之间的连接。 - 我们需要一个
Listener
子类来处理来自控制器的事件。 - 在
onConnect()
方法中启用手势跟踪。 - 该类中的主要方法是
onFrame()
,当具有运动跟踪数据的新Frame
对象可用时调度的callback
方法。此对象包含手、手指或工具的列表以及几个向量及其位置、方向和移动速度。 - 如果启用了手势,我们还将根据对最后一帧的分析,获得找到的手势列表。此外,您将知道手势的状态,无论它是刚刚开始、正在进行还是已经结束。
我们将在这里讨论的应用是一个复杂的 JavaFX 8 3D 应用,它将帮助您理解基于 Leap 的应用开发结构,与设备交互以识别手的位置,并与手势交互以在 3D 环境中模拟我们的手。
在后面的示例部分中,您可以找到更多资源,包括使用 JavaFX 开发基于 Leap 的应用的更高级概念。
在这个应用中,我们将以圆柱体和球体的形式检测骨骼、手臂和关节(位置和方向),以便在 JavaFX 应用SubScene
中对我们的手进行 3D 建模。然后,我们将检测它们的位置,以模拟跳跃运动装置上方的真实手部运动。
我们还将添加原始的image
,以便您可以在应用的背景中看到模型和真实的手。
该应用由三类组成:
LeapListener.java
:该类是与 Leap Motion controller 线程交互的侦听器,用于将所有分析数据(手臂、骨骼、手指和关节)传输到 JavaFX 应用。LeapJavaFX.java
:这个类是一个 JavaFX 应用线程,它将与LeapListener.java
交互以创建 3D 形状(在每一帧上),而不跟踪以前的形状。由于可观察的 JavaFXbean 属性的强大功能,它允许从 Leap 线程传输的数据被呈现到 JavaFX 线程中。Pair.java
:这是一个小对便利类,用于存储每个关节中链接的两块骨骼。
那么,让我们开始看看我们如何做到这一点。
您必须通过选中常规选项卡下的允许图像选项启用跳跃运动控制面板上的图像,并确保在跟踪选项卡下禁用鲁棒模式选项以获得更高的图像。
首先,我们将解释应用的主桥,即 Leap 事件侦听器LeapListener.java
。
在开发 JavaFX 应用时,主要关注的是如何将 JavaFX 线程与其他非 JavaFX 线程混合,在我们的例子中,是以非常高的速率处理事件的 Leap Motion EventListener
子类。
为了将这些事件带到 JavaFX 线程,我们将使用LeapListener.java
类中的BooleanProperty
对象。因为我们将只监听doneList
对象中的更改,所以我们不需要列表也可以观察到,因为它们将触发任何更改的事件(添加一个骨骼)。
这就是为什么它们是普通列表,在每个 LeapFrame
对象中创建所有列表后,我们只使用一个布尔可观察属性将其设置为 true:
private final BooleanProperty doneList= new
SimpleBooleanProperty(false);
private final List<Bone> bones=new ArrayList<>();
private final List<Arm> arms=new ArrayList<>();
private final List<Pair> joints=new ArrayList<>();
private final List<WritableImage> raw =new ArrayList<>();
为了获取原始图像,我们必须设置此策略onInit()
,并且,出于隐私原因,用户还必须为任何应用启用 Leap Motion 控制面板中的功能以获取原始相机图像。
@Override
public void onInit(Controller controller){
controller.setPolicy(Controller.PolicyFlag.POLICY_IMAGES);
}
(如您所知,如果您想处理手势,这里是您启用此功能的地方,因此您可以对其进行评论。
让我们继续创建 Frame 方法:
@Override
public void onFrame(Controller controller) {
Frame frame = controller.frame();
doneList.set(false);
doneList.set(!bones.isEmpty() || !arms.isEmpty());
}
public BooleanProperty doneListProperty() {
return doneList;
}
对于每一个帧,重置doneList
,处理数据,如果我们有骨骼或手臂,最后将其设置为true
(如果没有手越过跳跃,帧仍在处理中)。在 JavaFX 应用上公开要侦听的属性。
现在处理帧对象数据。首先是图像(这可以在最后完成)。清除每帧上的列表,然后检索图像(从左侧和右侧摄影机)。如果您想了解这是如何工作的,Leap 文档非常有用。访问https://developer.leapmotion.com/documentation/java/devguide/Leap_Images.html 。
事实上,这段代码是第一个示例的一部分,添加了PixelWriter
以生成 JavaFX 图像。因为 Leap 提供了明亮的像素,所以我将它们*(1-(r|g | b)】*取反,以获得一个在手上更清晰可见的负像。另外,我将图像从左向右翻转,如下所示:
(newPixels[i*width+(width-j-1)]).raw.clear();
ImageList images = frame.images();
for(Image image : images){
int width = (int)image.width();
int height = (int)image.height();
int[] newPixels = new int[width * height];
WritablePixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();
WritableImage wi=new WritableImage(width, height);
PixelWriter pw = wi.getPixelWriter();
//Get byte array containing the image data from Image object
byte[] imageData = image.data();
//Copy image data into display object
for(int i = 0; i < height; i++){
for(int j = 0; j < width; j++){
//convert to unsigned and shift into place
int r = (imageData[i*width+j] & 0xFF) << 16;
int g = (imageData[i*width+j] & 0xFF) << 8;
int b = imageData[i*width+j] & 0xFF;
// reverse image
newPixels[i*width+(width-j-1)] = 1- (r | g | b);
}
}
pw.setPixels(0, 0, width, height, pixelFormat, newPixels, 0,width);
raw.add(wi);
}
然后清除骨骼、手臂和关节列表,如下代码所示:
bones.clear();
arms.clear();
joints.clear();
if (!frame.hands().isEmpty()) {
Screen screen = controller.locatedScreens().get(0);
if (screen != null && screen.isValid()){
获取骨骼列表;对于找到的每个手指,重复该手指的骨骼类型(最多 5 个),以避免无名指和中指的掌骨。代码如下:
for(Finger finger : frame.fingers()){
if(finger.isValid()){
for(Bone.Type b : Bone.Type.values()) {
if((!finger.type().equals(Finger.Type.TYPE_RING) &&!finger.type().equals(Finger.Type.TYPE_MIDDLE)) ||!b.equals(Bone.Type.TYPE_METACARPAL)){
bones.add(finger.bone(b));
}
}
}
}
现在,我们将迭代“手”列表,以获得每个手臂,并将其添加到手臂列表中,如下所示:
for(Hand h: frame.hands()){
if(h.isValid()){
// arm
arms.add(h.arm());
现在是手指关节。详细解释如何获得每个关节有点复杂。基本上,我找到了每只手的手指,识别出除了拇指以外的四个手指。代码如下:
FingerList fingers = h.fingers();
Finger index=null, middle=null, ring=null, pinky=null;
for(Finger f: fingers){
if(f.isFinger() && f.isValid()){
switch(f.type()){
case TYPE_INDEX: index=f; break;
case TYPE_MIDDLE: middle=f; break;
case TYPE_RING: ring=f; break;
case TYPE_PINKY: pinky=f; break;
}
}
}
一旦我确定了手指,我就定义了每对手指之间的关节(前三个关节)和手腕的关节(最后一个)。代码如下:
// joints
if(index!=null && middle!=null){
Pair p=new Pair(index.bone(Bone.Type.TYPE_METACARPAL).nextJoint(),middle.bone(Bone.Type.TYPE_METACARPAL).nextJoint());
joints.add(p);
}
if(middle!=null && ring!=null){
Pair p=new Pair(middle.bone(Bone.Type.TYPE_METACARPAL).nextJoint(),
ring.bone(Bone.Type.TYPE_METACARPAL).nextJoint());
joints.add(p);
}
if(ring!=null && pinky!=null){
Pair p=new Pair(ring.bone(Bone.Type.TYPE_METACARPAL).nextJoint(),
pinky.bone(Bone.Type.TYPE_METACARPAL).nextJoint());
joints.add(p);
}
if(index!=null && pinky!=null){
Pair p=new Pair(index.bone(Bone.Type.TYPE_METACARPAL).prevJoint(),pinky.bone(Bone.Type.TYPE_METACARPAL).prevJoint());
joints.add(p);
}
最后,前面的代码返回骨骼集合的新副本,以避免迭代此列表的并发异常。请注意,跳跃帧速率非常高。在一台功能强大的计算机中,这几乎是 5-10 毫秒。代码如下:
public List<Bone> getBones(){
return bones.stream().collect(Collectors.toList());
}
这比 JavaFX 脉冲(60 fps 或大约 16 ms)快,因此可以在渲染骨骼时更改列表。通过这种克隆方法,我们避免了任何并发问题。
LeapJavaFX 应用的侦听器方法如下:
Override
public void start(Stage primaryStage) {
listener = new LeapListener();
controller = new Controller();
controller.addListener(listener);
初始化 Leap 监听器类和控制器,然后添加监听器:
final PerspectiveCamera camera = new PerspectiveCamera();
camera.setFieldOfView(60);
camera.getTransforms().addAll(new Translate(-320,-480,-100));
final PointLight pointLight = new PointLight(Color.ANTIQUEWHITE);
pointLight.setTranslateZ(-500);
root.getChildren().addAll(pointLight);
为 3DsubScene
创建一个透视相机,将其转换到屏幕的中间、底部和用户。另外,增加一些准时的灯光。代码如下:
rawView=new ImageView();
rawView.setScaleY(2);
为 Leap 图像创建一个ImageView
,该图像为 640 x 240,鲁棒模式关闭(取消选中 Leap 控制面板中的选项),因此我们将其放大到 Y 以获得更可见的图像。代码如下:
Group root3D=new Group();
root3D.getChildren().addAll(camera, root);
SubScene subScene = new SubScene(root3D, 640, 480, true,
SceneAntialiasing.BALANCED);
subScene.setCamera(camera);
StackPane pane=new StackPane(rawView,subScene);
Scene scene = new Scene(pane, 640, 480);
使用相机创建一个组,并使用灯光作为subScene
的根创建一个内部组。请注意,为了更好地渲染,启用了深度缓冲区和抗锯齿。摄像机也被添加到subScene
中。
主根为 aStackPane
:背面为ImageView
,正面为透明SubScene
。代码如下:
final PhongMaterial materialFinger = new PhongMaterial(Color.BURLYWOOD);
final PhongMaterial materialArm = new PhongMaterial(Color.CORNSILK);
使用漫反射颜色设置手指和手臂的材质:
listener.doneListProperty().addListener((ov,b,b1)->{
if(b1){
...
}
});
我们倾听对doneList
的变化。无论何时true
(每帧之后!),我们都会处理 3D 手绘制:
List<Bone> bones=listener.getBones();
List<Arm> arms=listener.getArms();
List<Pair> joints=listener.getJoints();
List<WritableImage> images=listener.getRawImages();
首先,获取骨骼、手臂和关节集合的新副本。然后,如果 JavaFX 线程中有有效的图像,我们将图像设置在ImageView
上,并移除除灯光之外的所有根子对象(因此我们再次重新创建手部骨骼):
Platform.runLater(()->{
if(images.size()>0){
// left camera
rawView.setImage(images.get(0));
}
if(root.getChildren().size()>1){
// clean old bones
root.getChildren().remove(1,root.getChildren().size()-1);
}
骨骼在列表上迭代并将骨骼添加到场景中。如果集合发生更改,则在迭代其副本时不会出现任何并发异常。
bones.stream().filter(bone -> bone.isValid() && bone.length()>0).forEach(bone -> {
现在我们为每个骨骼创建一个圆柱体。这涉及到一些计算。如果要深入了解细节,请将每个骨骼作为一个向量,并带有位置和方向。创建一个垂直圆柱体,其半径为骨骼宽度的一半,高度与其长度相同。然后,将其指定为材质。代码如下:
final Vector p=bone.center();
// create bone as a vertical cylinder and locate it at its center position
Cylinder c=new Cylinder(bone.width()/2,bone.length());
c.setMaterial(materialFinger);
然后得到真实骨方向与垂直骨方向的叉积;这给了我们旋转的垂直矢量。(标志是由于坐标系的变化而产生的)。ang
对象是这两个向量之间的角度。变换可以应用到骨骼中心的平移和围绕给定向量的旋转ang
。代码如下:
// translate and rotate the cylinder towards its direction
final Vector v=bone.direction();
Vector cross = (new Vector(v.getX(),-v.getY(), v.getZ())).cross(new Vector(0,-1,0));
double ang=(new Vector(v.getX(),-v.getY(),-v.getZ())).angleTo(new Vector(0,-1,0));
c.getTransforms().addAll(new Translate(p.getX(),-p.getY(),-p.getZ()),new Rotate(-Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())));
// add bone to scene
root.getChildren().add(c);
现在,在每个骨骼的开头和结尾都有球体:
// add sphere at the end of the bone
Sphere s=new Sphere(bone.width()/2f);
s.setMaterial(materialFinger);
s.getTransforms().addAll(new Translate(p.getX(),-p.getY()+bone.length()/2d,-p.getZ()),new Rotate(-Math.toDegrees(ang), 0, -bone.length()/2d, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())));
// add sphere to scene
root.getChildren().add(s);
// add sphere at the beginning of the bone
Sphere s2=new Sphere(bone.width()/2f);
s2.setMaterial(materialFinger);
s2.getTransforms().addAll(new Translate(p.getX(),-p.getY()-bone.length()/2d,-p.getZ()),new Rotate(Math.toDegrees(ang), 0, bone.length()/2d, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())));
// add sphere to scene
root.getChildren().add(s2);
});
现在是接头;我们再次使用钢瓶。连接的两个元素之间的距离给出了长度,我们得到了生成和变换圆柱体的位置和方向。代码如下:
joints.stream().forEach(joint->{
double length=joint.getV0().distanceTo(joint.getV1());
Cylinder c=new Cylinder(bones.get(0).width()/3,length);
c.setMaterial(materialArm);
final Vector p=joint.getCenter();
final Vector v=joint.getDirection();
Vector cross = (new Vector(v.getX(),-v.getY(), v.getZ())).cross(new Vector(0,-1,0));
double ang = (new Vector(v.getX(),-v.getY(),-v.getZ())).angleTo(new Vector(0,-1,0));
c.getTransforms().addAll(new Translate(p.getX(),-p.getY(),-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())));
// add joint to scene
root.getChildren().add(c);
});
最后,我们从肘部和手腕之间的距离取长度。所有这些都在 API 中,网址为:https://developer.leapmotion.com/documentation/java/api/Leap.Arm.html 。代码如下:
arms.stream().
filter(arm->arm.isValid()).
forEach(arm->{
final Vector p=arm.center();
// create arm as a cylinder and locate it at its center position
Cylinder c=new Cylinder(arm.width()/2,arm.elbowPosition().
minus(arm.wristPosition()).magnitude());
c.setMaterial(materialArm);
// rotate the cylinder towards its direction
final Vector v=arm.direction();
Vector cross = (new Vector(v.getX(),-v.getY(),-v.getZ())).cross(new Vector(0,-1,0));
double ang=(new Vector(v.getX(),-v.getY(),-v.getZ())).
angleTo(new Vector(0,-1,0));
c.getTransforms().addAll(new Translate(p.getX(),-p.getY(),-p.getZ()),new Rotate(- Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),- cross.getY(),cross.getZ())));
// add arm to scene
root.getChildren().add(c);
});
祝贺现在连接您的 Leap 控制器(Leap 图标应为绿色)并运行您的应用。如果一切正常,您应该首先看到一个空的应用场景,如以下屏幕截图所示:
Leap JavaFX 应用的初始运行
移动并挥动你的手,你的手的骨骼模型应该以真实的手出现在背景中,响应你的真实运动,如图所示:
Leap JavaFX 应用与 Leap 控制器的交互
尝试不同的手臂或手的模式和位置;您应该在 JavaFX 应用场景中复制此内容,如以下屏幕截图所示:
Leap JavaFX 应用和 Leap 控制器的交互,具有不同的手模式
有关将 JavaFX 与 Leap Motion 设备一起使用的更多示例,请参考在线资源,如http://www.parleys.com/share.html#play/525467d6e4b0a43ac12124ad 或http://jperedadnr.blogspot.com.es/2013/06/leap-motion-controller-and-javafx-new.html 。如需与其他编程语言交互,请访问https://developer.leapmotion.com/gallery 。
在本章中,您了解了令人印象深刻的 Leap Motion 设备,以及使用它增强 JavaFX 应用所产生的非常好的组合效果。
您从了解设备及其工作原理开始。接下来,我们讨论了它的 SDK for Java,并探索了一个简单的应用,在该应用中,您了解了如何在一个线程中侦听和处理来自 Leap 设备的数据,同时触发 JavaFX 线程中的事件以在场景图中处理这些事件。
在下一章中,我将为真正的 JavaFX 大师提供高级工具和资源。