Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

3D object Rotation in WPF using Leap Motion

0.00/5 (No votes)
24 Jun 2013 1  
Rotating a 3D object in a WPF application using hand gestures and Leap Motion

Introduction

In my previous article I described how you can go about inserting a 3D model into a WPF application using Expression Blend; with a little help from Blender. In this article I will describe how you can go about using Leap Motion to rotate a 3D model in WPF, effectively creating trackball-like rotation using hand gestures.

Requirements 

To run the article's project you require the following; 

NB: Add a reference to LeapCSharp.NET4.0.dll and ensure that you have also added Leap.dll and LeapCSharp.dll to the project. The Copy to Output Directory property of the latter two files should be set to Copy always

Leap Motion

The Leap is a 3D motion sensing device that detects and tracks hands, fingers and finger-like tools. With the use of the Leap SDK an application can make use of the data captured by the Leap controller.

The 3D Model

I will be making use of the 3D model from my previous article. The 3D model is that of a XA-20 Razorback Strike Fighter. I have adjusted the orientation of the Camera slightly, and the orientation of the DirectionalLight so that the 3D object is better illuminated.

The Code

LeapListener is a subclass of the Listener class, which defines a set of callback methods that can be overridden to respond to events dispatched by the 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;
                }
            }
        }
    }
}

The OnConnect() callback method is called when your application connects to the Leap. This is the method where you enable detection of swipe-gestures and set the minimum length and velocity required to detect this gesture (The length and velocity values are in mm and mm/s respectively). Since I want to detect slight hand/finger movements as swipe-gestures I have set the values of the minimum length and velocity to very small values, 10mm and 100mm/s.

The OnFrame() callback method is called when the Leap generates a new frame of motion tracking data. This is where I process the data from the Leap to determine the direction of a swipe gesture.

The SwipeDirection enum contains values for four swipe-gesture directions I'm interested in,

Public Enum SwipeDirection
    Up
    Down
    Left
    Right
End Enum
namespace XA_20_Razorback.Enums
{
    enum SwipeDirection
    {
        Up,
        Down,
        Left,
        Right
    }
}

Next is the RazorbackViewModel which contains some properties that the 3D object will bind to,

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));
            }
        }
    }
}

The DataContext of the window is set in the XAML markup,

<Window.DataContext>
    <local:RazorbackViewModel/>
</Window.DataContext>

I need to rotate the 3D model on only two of its axes, specifically its X and Y axis,

<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>
     ...

Rotating the 3D object around its X and Y axes is sufficient to achieve the trackball effect. The next step is to connect your Leap Motion controller, run the project, and wave your hands about to rotate the 3D model.

Conclusion

That's it, I hope you learnt something useful from this article.

History

  • 23rd June 2013: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here