Applications: The mathematics of movement, Part 1

Posted by TechTwaddle on Geeks with Blogs See other posts from Geeks with Blogs or by TechTwaddle
Published on Sun, 23 May 2010 16:30:19 GMT Indexed on 2010/05/23 16:51 UTC
Read the original article Hit count: 319

Filed under:

Before you continue reading this post, a suggestion; if you haven’t read “Programming Windows Phone 7 Series” by Charles Petzold, go read it. Now. If you find 150+ pages a little too long, at least go through Chapter 5, Principles of Movement, especially the section “A Brief Review of Vectors”. This post is largely inspired from this chapter.

At this point I assume you know what vectors are, how they are represented using the pair (x, y), what a unit vector is, and given a vector how you would normalize the vector to get a unit vector.

Our task in this post is simple, a marble is drawn at a point on the screen, the user clicks at a random point on the device, say (destX, destY), and our program makes the marble move towards that point and stop when it is reached. The tricky part of this task is the word “towards”, it adds a direction to our problem. Making a marble bounce around the screen is simple, all you have to do is keep incrementing the X and Y co-ordinates by a certain amount and handle the boundary conditions. Here, however, we need to find out exactly how to increment the X and Y values, so that the marble appears to move towards the point where the user clicked. And this is where vectors can be so helpful.

The code I’ll show you here is not ideal, we’ll be working with C# on Windows Mobile 6.x, so there is no built-in vector class that I can use, though I could have written one and done all the math inside the class. I think it is trivial to the actual problem that we are trying to solve and can be done pretty easily once you know what’s going on behind the scenes. In other words, this is an excuse for me being lazy.

The first approach, uses the function Atan2() to solve the “towards” part of the problem. Atan2() takes a point (x, y) as input, Atan2(y, x), note that y goes first, and then it returns an angle in radians. What angle you ask. Imagine a line from the origin (0, 0), to the point (x, y). The angle which Atan2 returns is the angle the positive X-axis makes with that line, measured clockwise. The figure below makes it clear, wiki has good details about Atan2(), give it a read.

image

The pair (x, y) also denotes a vector. A vector whose magnitude is the length of that line, which is Sqrt(x*x + y*y), and a direction ?, as measured from positive X axis clockwise. If you’ve read that chapter from Charles Petzold’s book, this much should be clear. Now Sine and Cosine of the angle ? are special. Cosine(?) divides x by the vectors length (adjacent by hypotenuse), thus giving us a unit vector along the X direction. And Sine(?) divides y by the vectors length (opposite by hypotenuse), thus giving us a unit vector along the Y direction. Therefore the vector represented by the pair (cos(?), sin(?)), is the unit vector (or normalization) of the vector (x, y). This unit vector has a length of 1 (remember sin2(?) + cos2(?) = 1 ?), and a direction which is the same as vector (x, y). Now if I multiply this unit vector by some amount, then I will always get a point which is a certain distance away from the origin, but, more importantly, the point will always be on that line. For example, if I multiply the unit vector with the length of the line, I get the point (x, y). Thus, all we have to do to move the marble towards our destination point, is to multiply the unit vector by a certain amount each time and draw the marble, and the marble will magically move towards the click point.

Now time for some code. The application, uses a timer based frame draw method to draw the marble on the screen. The timer is disabled initially and whenever the user clicks on the screen, the timer is enabled. The callback function for the timer follows the standard Update and Draw cycle.

private double totLenToTravelSqrd = 0;
private double startPosX = 0, startPosY = 0;
private double destX = 0, destY = 0;

private void Form1_MouseUp(object sender, MouseEventArgs e)
{
    destX = e.X;
    destY = e.Y;

    double x = marble1.x - destX;
    double y = marble1.y - destY;

    //calculate the total length to be travelled
    totLenToTravelSqrd = x * x + y * y;

    //store the start position of the marble
    startPosX = marble1.x;
    startPosY = marble1.y;

    timer1.Enabled = true;
}

private void timer1_Tick(object sender, EventArgs e)
{
    UpdatePosition();
    DrawMarble();
}

Form1_MouseUp() method is called when ever the user touches and releases the screen. In this function we save the click point in destX and destY, this is the destination point for the marble and we also enable the timer. We store a few more values which we will use in the UpdatePosition() method to detect when the marble has reached the destination and stop the timer. So we store the start position of the marble and the square of the total length to be travelled. I’ll leave out the term ‘sqrd’ when speaking of lengths from now on. The time out interval of the timer is set to 40ms, thus giving us a frame rate of about ~25fps. In the timer callback, we update the marble position and draw the marble. We know what DrawMarble() does, so here, we’ll only look at how UpdatePosition() is implemented;

private void UpdatePosition()
{
    //the vector (x, y)
    double x = destX - marble1.x;
    double y = destY - marble1.y;

    double incrX=0, incrY=0;
    double distanceSqrd=0;

    double speed = 6;

    //distance between destination and current position, before updating marble position
    distanceSqrd = x * x + y * y;

    double angle = Math.Atan2(y, x);

    //Cos and Sin give us the unit vector, 6 is the value we use to magnify the unit vector along the same direction
    incrX = speed * Math.Cos(angle);
    incrY = speed * Math.Sin(angle);

    marble1.x += incrX;
    marble1.y += incrY;

    //check for bounds
    if ((int)marble1.x < MinX + marbleWidth / 2)
    {
        marble1.x = MinX + marbleWidth / 2;
    }
    else if ((int)marble1.x > (MaxX - marbleWidth / 2))
    {
        marble1.x = MaxX - marbleWidth / 2;
    }

    if ((int)marble1.y < MinY + marbleHeight / 2)
    {
        marble1.y = MinY + marbleHeight / 2;
    }
    else if ((int)marble1.y > (MaxY - marbleHeight / 2))
    {
        marble1.y = MaxY - marbleHeight / 2;
    }

    //distance between destination and current point, after updating marble position
    x = destX - marble1.x;
    y = destY - marble1.y;
    double newDistanceSqrd = x * x + y * y;

    //length from start point to current marble position
    x = startPosX - (marble1.x);
    y = startPosY - (marble1.y);
    double lenTraveledSqrd = x * x + y * y;

    //check for end conditions
    if ((int)lenTraveledSqrd >= (int)totLenToTravelSqrd)
    {
        System.Console.WriteLine("Stopping because destination reached");
        timer1.Enabled = false;
    }
    else if (Math.Abs((int)distanceSqrd - (int)newDistanceSqrd) < 4)
    {
        System.Console.WriteLine("Stopping because no change in Old and New position");
        timer1.Enabled = false;
    }

}

Ok, so in this function, first we subtract the current marble position from the destination point to give us a vector. The first three lines of the function construct this vector (x, y). The vector (x, y) has the same length as the line from (marble1.x, marble1.y) to (destX, destY) and is in the direction pointing from (marble1.x, marble1.y) to (destX, destY). Note that marble1.x and marble1.y denote the center point of the marble. Then we use Atan2() to get the angle which this vector makes with the positive X axis and use Cosine() and Sine() of that angle to get the unit vector along that same direction. We multiply this unit vector with 6, to get the values which the position of the marble should be incremented by. This variable, speed, can be experimented with and determines how fast the marble moves towards the destination. After this, we check for bounds to make sure that the marble stays within the screen limits and finally we check for the end condition and stop the timer.

The end condition has two parts to it. The first case is the normal case, where the user clicks well inside the screen. Here, we stop when the total length travelled by the marble is greater than or equal to the total length to be travelled. Simple enough. The second case is when the user clicks on the very corners of the screen. Like I said before, the values marble1.x and marble1.y denote the center point of the marble. When the user clicks on the corner, the marble moves towards the point, and after some time tries to go outside of the screen, this is when the bounds checking comes into play and corrects the marble position so that the marble stays inside the screen. In this case the marble will never travel a distance of totLenToTravelSqrd, because of the correction is its position. So here we detect the end condition when there is not much change in marbles position. I use the value 4 in the second condition above. After experimenting with a few values, 4 seemed to work okay. There is a small thing missing in the code above. In the normal case, case 1, when the update method runs for the last time, marble position over shoots the destination point. This happens because the position is incremented in steps (which are not small enough), so in this case too, we should have corrected the marble position, so that the center point of the marble sits exactly on top of the destination point. I’ll add this later and update the post.

This has been a pretty long post already, so I’ll leave you with a video of how this program looks while running. Notice in the video that the marble moves like a bot, without any grace what so ever. And that is because the speed of the marble is fixed at 6. In the next post we will see how to make the marble move a little more elegantly. And also, if Atan2(), Sine() and Cosine() are a little too much to digest, we’ll see how to achieve the same effect without using them, in the next to next post maybe. Ciao!


© Geeks with Blogs or respective owner