Click here to Skip to main content
15,885,278 members
Articles / Desktop Programming / WPF

3D object Rotation in WPF using Leap Motion

Rate me:
Please Sign up or sign in to vote.
4.89/5 (19 votes)
24 Jun 2013CPOL2 min read 64.2K   4.2K   26   22
Rotating a 3D object in a WPF application using hand gestures and Leap Motion
Image 1

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.

Image 2

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.

Image 3

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.

VB.NET
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
C#
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,

VB.NET
Public Enum SwipeDirection
    Up
    Down
    Left
    Right
End Enum
C#
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,

VB.NET
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
C#
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,

XML
<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,

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Kenya Kenya
Experienced C# software developer with a passion for WPF.

Awards,
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • CodeProject MVP 2021

Comments and Discussions

 
QuestionReference problem Pin
Member 105433012-Mar-16 1:26
Member 105433012-Mar-16 1:26 
QuestionReference problem Pin
Member 105433012-Mar-16 1:17
Member 105433012-Mar-16 1:17 
QuestionHow to replace the 3D model Pin
Member 111210689-Dec-14 13:10
Member 111210689-Dec-14 13:10 
AnswerRe: How to replace the 3D model Pin
Meshack Musundi9-Dec-14 21:43
professionalMeshack Musundi9-Dec-14 21:43 
QuestionHELP needed! Pin
Member 111210682-Oct-14 10:31
Member 111210682-Oct-14 10:31 
AnswerRe: HELP needed! Pin
Meshack Musundi2-Oct-14 21:50
professionalMeshack Musundi2-Oct-14 21:50 
GeneralRe: HELP needed! Pin
Member 111210686-Oct-14 6:25
Member 111210686-Oct-14 6:25 
GeneralRe: HELP needed! Pin
Member 111210686-Oct-14 6:29
Member 111210686-Oct-14 6:29 
GeneralRe: HELP needed! Pin
Meshack Musundi6-Oct-14 7:12
professionalMeshack Musundi6-Oct-14 7:12 
GeneralRe: HELP needed! Pin
Member 111210686-Oct-14 10:46
Member 111210686-Oct-14 10:46 
GeneralRe: HELP needed! Pin
Member 111210686-Oct-14 12:06
Member 111210686-Oct-14 12:06 
QuestionCan you help me, please? Pin
lfsoares.prof26-May-14 4:19
lfsoares.prof26-May-14 4:19 
AnswerRe: Can you help me, please? Pin
Member 111210682-Oct-14 12:15
Member 111210682-Oct-14 12:15 
Question360 degree controlling Pin
Qiang Zheng8-Sep-13 23:55
Qiang Zheng8-Sep-13 23:55 
AnswerRe: 360 degree controlling Pin
Meshack Musundi9-Sep-13 0:50
professionalMeshack Musundi9-Sep-13 0:50 
GeneralRe: 360 degree controlling Pin
Qiang Zheng9-Sep-13 4:44
Qiang Zheng9-Sep-13 4:44 
QuestionException from HRESULT 0x8007007E Pin
Qiang Zheng3-Sep-13 0:45
Qiang Zheng3-Sep-13 0:45 
AnswerRe: Exception from HRESULT 0x8007007E Pin
Meshack Musundi3-Sep-13 1:08
professionalMeshack Musundi3-Sep-13 1:08 
GeneralRe: Exception from HRESULT 0x8007007E Pin
Qiang Zheng3-Sep-13 1:35
Qiang Zheng3-Sep-13 1:35 
GeneralRe: Exception from HRESULT 0x8007007E Pin
Meshack Musundi3-Sep-13 1:40
professionalMeshack Musundi3-Sep-13 1:40 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Jul-13 20:50
professionalȘtefan-Mihai MOGA13-Jul-13 20:50 
GeneralRe: My vote of 5 Pin
Meshack Musundi14-Jul-13 6:42
professionalMeshack Musundi14-Jul-13 6:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.