Previously we did a simple 3D maze and moved around in it. That becomes boring after a while, so this time we will look at how to interact with objects in the 3D world. Once again we are going to be using a card game as a base, and again this article will only cover the basics. By the end of this article you will be able to interact with objects in a 3D world using the mouse.
Capturing the Mouse
Initially when I started this article, I was not too sure on how to properly capture the mouse and the VB.NET article used a secondary Click hotspot to locate where in the game board the mouse was clicked. With some help from the forum Guru's a good method was found. This relies on several API's.
Private Declare Function ShowCursor Lib "user32" _ (ByVal bShow As Long) As Long Private Declare Function SetCursorPos Lib "user32" _ (ByVal x As Int32, ByVal Y As Int32) As Int32 Private Declare Function GetCursorPos Lib "user32" _ (ByRef lpPoint As PointAPI) As Boolean
Now let's look at how these API's help us with capturing the mouse. First, ShowCursor is an API used to show and hide the Mouse pointer in our applications form. Its use is reasonably simple. ShowCursor (True) will show the mouse pointer and ShowCursor (False) will hide the mouse pointer.
Next we look at GetCursorPos and SetCursorPos; these two API's allow us to get the current location of the mouse and to set the position of the mouse. The second part of this is very important, because if the mouse reaches the end of the form or window, we no longer receive movements for that direction. The Basics here is to set the mouse position in the middle of the form, and when it moves from this position, we process the distances moved, and then place it back in the middle. This allows us to have an infinitive movement in any direction.
So how do these all help and work in our application. Let's start by adding it to our Maze Project from the previous article. To start let's do our declarations first.
Structure PointAPI Public X As Int32 Public Y As Int32 End Structure Private Showmouse As Boolean = True Private Declare Function ShowCursor Lib "user32" (ByVal bShow As Long) As Long Private Declare Function SetCursorPos Lib "user32" (ByVal X As Int32, ByVal Y As Int32) As Int32 Private Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As PointAPI) As Boolean
The Boolean Variable ShowMouse is for us to track the current visibility of the mouse pointer, by default the mouse is visible so we set this default.
Next we put in a few subs to handle some specific functions.
Private Sub DOShowMouse() If Showmouse Then ShowCursor(True) Else ShowCursor(False) SetCenter() End If End Sub Private Sub SetCenter() Dim CPos As System.Windows.Point CPos.X = CInt(Me.Left + (Me.Width / 2)) CPos.Y = CInt(Me.Top + (Me.Height / 2)) SetCursorPos(CPos.X, CPos.Y) End Sub
These two subs handle the two functions explained previously. The DoShowMouse function handles the simple issue of switching the mouse on and off as we require it, and the SetCenter sub centralizes the mouse pointer on our form. Also you will notice that when we hide the cursor we call the sub to center the mouse; this ensures that we start with a 0 offset when it comes time to process the mouse movements.
Now all that is left to do is capture the mouse events; with the two following events we've added total Mouse capture functionality to our application.
Private Sub Window1_PreviewMouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp Showmouse = Not (Showmouse) DOShowMouse() End Sub Private Sub Window1_PreviewMouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.PreviewMouseMove If Not Showmouse Then Dim position As PointAPI GetCursorPos(position) Dim pX As Double pX = position.X Dim pY As Double pY = position.Y Dim CPos As System.Windows.Point CPos.X = CInt(Me.Left + (Me.Width / 2)) CPos.Y = CInt(Me.Top + (Me.Height / 2)) SetCursorPos(CPos.X, CPos.Y) Location.Hangle += (CPos.X - position.X) * Steps Location.VAngle += (CPos.Y - position.Y) * Steps End If End Sub
The first event we're interested in is the Mouse button Left click. The premise is that when we want to interact with it, we click on the form and take control of the mouse. Clicking a second time will release the mouse.
The Next Event we're interested in is the Mouse Movement event. In this event we calculate the mouse movement from form center and apply it to our maze. In this case moving the mouse will alter the viewing angle in the maze.
NOTE: As described before, we're using the forms Preview events to capture mouse events regardless of what object on the form the mouse is sitting over.
Running the project (complete adjusted project in downloads section) will load the maze as usual; now move the mouse over the form and click once. The Pointer will disappear and now any mouse movement will rotate your view of the maze.
This is the first step to interacting with objects in a 3D space.
On the next page we are going to look at creating our own pointer in the 3D world and moving it around.
In this Project we are going to create a 3D card world, much like the VB.NET article on object interaction. However that's where the similarities end.
First let's look at the graphics needed. In the Previous article the fronts and backs of each card were separate, however with what we've already learned about WPF skinning we know that the entire skin of one object needs to be a single image. So the Card Images are modified a little. We merge the back and front into a single image.
From two separate Images to a single image
Remember that when skinning we select what portion of the image to use for each face. Once rendered the cards look like this.
Now I'm sure your itching to see the code. Much of the code is the same as used in previous WPF articles, so I'm only going to cover what's new or changed.
The first thing we are going to look at is the new 3D pointer that we will be rendering. The pointer movement will actually only be in 2 dimensions, however the original keyboard movement keys will allow us to float around the board and look down over the cards.
We are using the code from page one of this article, with a few small changes. The first change is to move our pointer around with the mouse move event.
Private Sub Window1_PreviewMouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles Me.PreviewMouseMove If Not Showmouse Then Dim position As PointAPI GetCursorPos(position) Dim pX As Double pX = position.X Dim pY As Double pY = position.Y Dim CPos As System.Windows.Point CPos.X = CInt(Me.Left + (Me.Width / 2)) CPos.Y = CInt(Me.Top + (Me.Height / 2)) SetCursorPos(CPos.X, CPos.Y) PLocation.X += (CPos.X - position.X) * 0.005 PLocation.Z += (CPos.Y - position.Y) * 0.005 Dim transform As Transform3DGroup = New Transform3DGroup() Dim translateTrans As TranslateTransform3D = New TranslateTransform3D(PLocation.X, PHeight, PLocation.Z) transform.Children.Add(translateTrans) PGeometry.Transform = transform Dim tmpgeo As GeometryModel3D = CardGeometry(1) End If End Sub
The Best method to move an Object around in WPF appears to be applying a transformation to the object, so here we apply one to the Pointer object to move it according to how the mouse originally moved. Also we are only moving it on the X and Z axis.
Here we use a translate transform to move the home point of the pointer to where we want it. By simply giving it the target location of the object, WFP will move the object to where you request using any sort of animation or time frame required. Here we do not apply a time frame as we actually want the movement to mimic the mouse movement as much as possible.
Next we're going to use the Right click to switch in the mouse pointer and left click to interact with the cards on the playing field.
Private Sub Window1_PreviewMouseRightButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.PreviewMouseRightButtonUp Showmouse = Not (Showmouse) DOShowMouse() End Sub Private Sub Window1_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonDown If Not Showmouse Then Dim tmp As Int16 For tmp = 1 To 52 With CardGeometry(tmp).Bounds If .X < PLocation.X And .X + .SizeX > PLocation.X Then If .Z < PLocation.Z And .Z + .SizeZ > PLocation.Z Then Dim Cube_ani1 As New DoubleAnimation(0, 360, TimeSpan.FromSeconds(2)) Dim Cube_rot1 As New AxisAngleRotation3D() Cube_rot1.Axis = New Vector3D(0, 0, 1) Dim transform As Transform3DGroup = New Transform3DGroup() Dim RotateTrans As RotateTransform3D = New RotateTransform3D(Cube_rot1, _ New Point3D(.X + (.SizeX / 2), 0, .Z + (.SizeZ / 2))) transform.Children.Add(RotateTrans) CardGeometry(tmp).Transform = transform Cube_rot1.BeginAnimation( AxisAngleRotation3D.AngleProperty, Cube_ani1) End If End If End With Next End If End Sub
The Right Click event is the same as the previous left click event and should not require any explanation.
The Left Click event however is where our interaction happens. Here we look at each card's location, comparing it to the current pointer location. However we only have to look at the X and Z axis as this is our working planes. Once we find the Card that our pointer is hovering over, we apply a transformation to it, however in this transform we apply a rotation transformation. This is so that a click on the card will cause the card to rotate to show what card it is.
As you can see here, Interaction with objects is still relatively easy, and it's also easy to completely take over the mouse and use it's movement within your animation. We also see how to use timed transforms to apply animation to an object under specific conditions.
Remember these projects are designed to give you the startup tools needed to start creating your own 3D worlds. You may notice that at the moment the code triggers a transform and moves on, essentially forgeting about it, so it is posible to retriger them multiple times.
Happy 3D card game writing.