65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (19投票s)

2013年6月23日

CPOL

2分钟阅读

viewsIcon

66722

downloadIcon

4311

使用手势和 Leap Motion 在 WPF 应用程序中旋转 3D 对象

引言

在我之前的文章中,我描述了如何使用 Expression Blend 将 3D 模型插入到 WPF 应用程序中;借助 Blender 的一些帮助。在本文中,我将描述如何使用 Leap Motion 在 WPF 中旋转 3D 模型,从而有效地使用手势创建类似轨迹球的旋转。

要求 

要运行本文的项目,您需要以下内容:

注意:添加对 *LeapCSharp.NET4.0.dll* 的引用,并确保您也已将 *Leap.dll* 和 *LeapCSharp.dll* 添加到项目中。后两个文件的 *复制到输出目录* 属性应设置为 *始终复制*。

Leap Motion

Leap 是一款 3D 运动感应设备,可检测和跟踪手、手指和类似手指的工具。借助 Leap SDK,应用程序可以使用 Leap 控制器捕获的数据。

3D 模型

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

代码

LeapListenerListener 类的一个子类,它定义了一组回调方法,可以重写这些方法来响应 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 日:初始发布
© . All rights reserved.