使用 Leap Motion 在 WPF 中进行 3D 对象旋转






4.89/5 (19投票s)
使用手势和 Leap Motion 在 WPF 应用程序中旋转 3D 对象
引言
在我之前的文章中,我描述了如何使用 Expression Blend 将 3D 模型插入到 WPF 应用程序中;借助 Blender 的一些帮助。在本文中,我将描述如何使用 Leap Motion 在 WPF 中旋转 3D 模型,从而有效地使用手势创建类似轨迹球的旋转。
要求
要运行本文的项目,您需要以下内容:
- Leap Motion 控制器,
- Leap Motion SDK,
- VS2010 / VS2012
注意:添加对 *LeapCSharp.NET4.0.dll* 的引用,并确保您也已将 *Leap.dll* 和 *LeapCSharp.dll* 添加到项目中。后两个文件的 *复制到输出目录* 属性应设置为 *始终复制*。
Leap Motion
Leap 是一款 3D 运动感应设备,可检测和跟踪手、手指和类似手指的工具。借助 Leap SDK,应用程序可以使用 Leap 控制器捕获的数据。

3D 模型
我将使用我之前文章中的 3D 模型。该 3D 模型是 XA-20 Razorback 攻击战斗机 的模型。我稍微调整了 Camera
的方向,以及 DirectionalLight
的方向,以便更好地照亮 3D 对象。

代码
LeapListener
是 Listener
类的一个子类,它定义了一组回调方法,可以重写这些方法来响应 Leap 派发的事件。
Imports Leap
Public Class LeapListener
Inherits Listener
Public Overrides Sub OnConnect(ctlr As Controller)
ctlr.Config.SetFloat("Gesture.Swipe.MinLength", 10)
ctlr.Config.SetFloat("Gesture.Swipe.MinVelocity", 100)
ctlr.Config.Save()
ctlr.EnableGesture(Gesture.GestureType.TYPESWIPE)
End Sub
Public Overrides Sub OnFrame(ctlr As Controller)
Dim currentFrame As Frame = ctlr.Frame
If (Not currentFrame.Hands.Empty) Then
Dim firstHand As Hand = currentFrame.Hands(0)
Dim fingers As FingerList = firstHand.Fingers
If (Not fingers.Empty) Then
Dim gestures As GestureList = currentFrame.Gestures
For Each gst As Gesture In gestures
If gst.Type = Gesture.GestureType.TYPESWIPE Then
Dim swipe As New SwipeGesture(gst)
If (Math.Abs(swipe.Direction.x) > Math.Abs(swipe.Direction.y)) Then ' Horizontal swipe
If (swipe.Direction.x > 0) Then ' right swipe
SwipeAction(fingers, SwipeDirection.Right)
Else ' left swipe
SwipeAction(fingers, SwipeDirection.Left)
End If
Else ' Vertical swipe
If (swipe.Direction.y > 0) Then ' upward swipe
SwipeAction(fingers, SwipeDirection.Up)
Else ' downward swipe
SwipeAction(fingers, SwipeDirection.Down)
End If
End If
Exit For
End If
Next
End If
End If
End Sub
Public Delegate Sub SwipeEvent(ByVal sd As SwipeDirection)
Public Event LeapSwipe As SwipeEvent
Private fingersCount As Integer
Private Sub SwipeAction(ByVal fingers As FingerList, ByVal sd As SwipeDirection)
fingersCount = fingers.Count
If (fingersCount = 5) Then
Select Case sd
Case SwipeDirection.Left
RaiseEvent LeapSwipe(SwipeDirection.Right)
Exit Select
Case SwipeDirection.Right
RaiseEvent LeapSwipe(SwipeDirection.Left)
Exit Select
Case SwipeDirection.Up
RaiseEvent LeapSwipe(SwipeDirection.Up)
Exit Select
Case SwipeDirection.Down
RaiseEvent LeapSwipe(SwipeDirection.Down)
Exit Select
End Select
End If
End Sub
End Class
using System;
using System.Linq;
using Leap;
using XA_20_Razorback.Enums;
namespace XA_20_Razorback
{
class LeapListener: Listener
{
public override void OnConnect(Controller ctlr)
{
ctlr.Config.SetFloat("Gesture.Swipe.MinLength", 10);
ctlr.Config.SetFloat("Gesture.Swipe.MinVelocity", 100);
ctlr.Config.Save();
ctlr.EnableGesture(Gesture.GestureType.TYPESWIPE);
}
public override void OnFrame(Controller ctrl)
{
Frame currentFrame = ctrl.Frame();
if (!currentFrame.Hands.Empty)
{
Hand firstHand = currentFrame.Hands[0];
FingerList fingers = firstHand.Fingers;
if (!fingers.Empty)
{
GestureList gestures = currentFrame.Gestures();
foreach (Gesture gst in gestures)
{
SwipeGesture swipe = new SwipeGesture(gst);
if (Math.Abs(swipe.Direction.x) > Math.Abs(swipe.Direction.y)) // Horizontal swipe
{
if (swipe.Direction.x > 0) // right swipe
{
SwipeAction(fingers, SwipeDirection.Right);
}
else // left swipe
{
SwipeAction(fingers, SwipeDirection.Left);
}
}
else // Vertical swipe
{
if (swipe.Direction.y > 0) // upward swipe
{
SwipeAction(fingers, SwipeDirection.Up);
}
else // downward swipe
{
SwipeAction(fingers, SwipeDirection.Down);
}
}
}
}
}
}
public delegate void SwipeEvent(SwipeDirection sd);
public event SwipeEvent LeapSwipe;
private int fingersCount;
public void SwipeAction(FingerList fingers, SwipeDirection sd)
{
fingersCount = fingers.Count();
if (fingersCount == 5)
{
switch (sd)
{
case SwipeDirection.Left:
if (LeapSwipe != null)
{
LeapSwipe(SwipeDirection.Left);
}
break;
case SwipeDirection.Right:
if (LeapSwipe != null)
{
LeapSwipe(SwipeDirection.Right);
}
break;
case SwipeDirection.Up:
if (LeapSwipe != null)
{
LeapSwipe(SwipeDirection.Up);
}
break;
case SwipeDirection.Down:
if (LeapSwipe != null)
{
LeapSwipe(SwipeDirection.Down);
}
break;
}
}
}
}
}
当您的应用程序连接到 Leap 时,会调用 OnConnect()
回调方法。这是您启用滑动姿势检测并设置检测此姿势所需的最小长度和速度的方法(长度和速度值分别以毫米和毫米/秒为单位)。由于我想将轻微的手/手指移动检测为滑动姿势,因此我将最小长度和速度的值设置为非常小的值,10 毫米和 100 毫米/秒。
当 Leap 生成新的运动跟踪数据帧时,会调用 OnFrame()
回调方法。这是我处理来自 Leap 的数据以确定滑动姿势方向的地方。
SwipeDirection
枚举包含我感兴趣的四个滑动姿势方向的值,
Public Enum SwipeDirection
Up
Down
Left
Right
End Enum
namespace XA_20_Razorback.Enums
{
enum SwipeDirection
{
Up,
Down,
Left,
Right
}
}
接下来是 RazorbackViewModel
,它包含 3D 对象将绑定到的一些属性,
Imports System.ComponentModel
Imports Leap
Public Class RazorbackViewModel
Implements INotifyPropertyChanged
Private ctlr As Controller
Private listener As LeapListener
Private Const SHIFT As Double = 1
Public Sub New()
ctlr = New Controller
listener = New LeapListener
ctlr.AddListener(listener)
AddHandler listener.LeapSwipe, AddressOf SwipeAction
End Sub
Private _xAngle As Double
Public Property XAngle As Double
Get
Return _xAngle
End Get
Set(value As Double)
_xAngle = value
OnPropertyChanged("XAngle")
End Set
End Property
Private _yAngle As Double
Public Property YAngle As Double
Get
Return _yAngle
End Get
Set(value As Double)
_yAngle = value
OnPropertyChanged("YAngle")
End Set
End Property
Private Sub SwipeAction(ByVal sd As SwipeDirection)
Select Case sd
Case SwipeDirection.Up
XAngle += SHIFT
Exit Select
Case SwipeDirection.Down
XAngle -= SHIFT
Exit Select
Case SwipeDirection.Left
YAngle += SHIFT
Exit Select
Case SwipeDirection.Right
YAngle -= SHIFT
Exit Select
End Select
End Sub
Public Sub DisposeListener()
ctlr.RemoveListener(listener)
ctlr.Dispose()
End Sub
Public Event PropertyChanged(sender As Object,
e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
using System;
using System.Linq;
using System.ComponentModel;
using XA_20_Razorback.Enums;
using Leap;
namespace XA_20_Razorback
{
class RazorbackViewModel : INotifyPropertyChanged
{
private LeapListener listener;
private Controller ctrl;
private const double SHIFT = 1;
public RazorbackViewModel()
{
listener = new LeapListener();
ctrl = new Controller();
ctrl.AddListener(listener);
listener.LeapSwipe += SwipeAction;
}
private double _xAngle;
public double XAngle
{
get { return _xAngle; }
set
{
_xAngle = value;
OnPropertyChanged("XAngle");
}
}
private double _yAngle;
public double YAngle
{
get { return _yAngle; }
set
{
_yAngle = value;
OnPropertyChanged("YAngle");
}
}
private void SwipeAction(SwipeDirection sd)
{
switch (sd)
{
case SwipeDirection.Up:
XAngle += SHIFT;
break;
case SwipeDirection.Down:
XAngle -= SHIFT;
break;
case SwipeDirection.Left:
YAngle += SHIFT;
break;
case SwipeDirection.Right:
YAngle -= SHIFT;
break;
}
}
public void DisposeListener()
{
ctrl.RemoveListener(listener);
ctrl.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
窗口的 DataContext
在 XAML 标记中设置,
<Window.DataContext>
<local:RazorbackViewModel/>
</Window.DataContext>
我只需要绕其两个轴旋转 3D 模型,特别是其 X 轴和 Y 轴,
<ModelVisual3D x:Name="DefaultGroup">
<ModelVisual3D.Transform>
<Transform3DGroup>
<Transform3DGroup.Children>
<RotateTransform3D x:Name="VerticalRTransform">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1 0 0" Angle="{Binding XAngle}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D x:Name="HorizontalRTransform">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0 1 0" Angle="{Binding YAngle}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup.Children>
</Transform3DGroup>
</ModelVisual3D.Transform>
...
绕其 X 轴和 Y 轴旋转 3D 对象足以实现轨迹球效果。下一步是连接您的 Leap Motion 控制器,运行项目,并挥动手以旋转 3D 模型。
结论
就是这样,我希望您从本文中学到了一些有用的东西。
历史
- 2013 年 6 月 23 日:初始发布