In part 1 of this series we saw how we can make the marble move towards the click point, with a fixed speed. In this post we’ll see, first, how to get rid of Atan2(), sine() and cosine() in our calculations, and, second, reducing the speed of the marble as it approaches the destination, so it looks like the marble is easing into it’s final position. As I mentioned in one of the previous posts, this is achieved by making the speed of the marble a function of the distance between the marble and the destination point.
Getting rid of Atan2(), sine() and cosine()
Ok, to be fair we are not exactly getting rid of these trigonometric functions, rather, replacing one form with another. So instead of writing sin(θ), we write y/length. You see the point. So instead of using the trig functions as below,
double x = destX - marble1.x;
double y = destY - marble1.y;
//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;
we use the following,
double x = destX - marble1.x;
double y = destY - marble1.y;
//distance between destination and marble (before updating marble position)
lengthSqrd = x * x + y * y;
length = Math.Sqrt(lengthSqrd);
//unit vector along the same direction as vector(x, y)
unitX = x / length;
unitY = y / length;
//update marble position
incrX = speed * unitX;
incrY = speed * unitY;
marble1.x += incrX;
marble1.y += incrY;
so we replaced cos(θ) with x/length and sin(θ) with y/length. The result is the same.
Adding oomph to the way it moves
In the last post we had the speed of the marble fixed at 6,
double speed = 6;
to make the marble decelerate as it moves, we have to keep updating the speed of the marble in every frame such that the speed is calculated as a function of the length. So we may have, speed = length/12; ‘length’ keeps decreasing as the marble moves and so does speed. The Form1_MouseUp() function remains the same as before, here is the UpdatePosition() method,
private void UpdatePosition()
{
double incrX = 0, incrY = 0;
double lengthSqrd = 0, length = 0, lengthSqrdNew = 0;
double unitX = 0, unitY = 0;
double speed = 0;
double x = destX - marble1.x;
double y = destY - marble1.y;
//distance between destination and marble (before updating marble position)
lengthSqrd = x * x + y * y;
length = Math.Sqrt(lengthSqrd);
//unit vector along the same direction as vector(x, y)
unitX = x / length;
unitY = y / length;
//speed as a function of length
speed = length / 12;
//update marble position
incrX = speed * unitX;
incrY = speed * unitY;
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 marble (after updating marble position)
x = destX - (marble1.x);
y = destY - (marble1.y);
lengthSqrdNew = x * x + y * y;
/*
* End Condition:
* 1. If there is not much difference between lengthSqrd and lengthSqrdNew
* 2. If the marble has moved more than or equal to a distance of totLenToTravel (see Form1_MouseUp)
*/
x = startPosX - marble1.x;
y = startPosY - marble1.y;
double totLenTraveledSqrd = x * x + y * y;
if ((int)totLenTraveledSqrd >= (int)totLenToTravelSqrd)
{
System.Console.WriteLine("Stopping because Total Len has been traveled");
timer1.Enabled = false;
}
else if (Math.Abs((int)lengthSqrd - (int)lengthSqrdNew) < 4)
{
System.Console.WriteLine("Stopping because no change in Old and New");
timer1.Enabled = false;
}
}
A point to note here is that, in this implementation, the marble never stops because it travelled a distance of totLenToTravelSqrd (first if condition). This happens because speed is a function of the length. During the final few frames length becomes very small and so does speed; and so the amount by which the marble shifts is quite small, and the second if condition always hits true first.
I’ll end this series with a third post. In part 3 we will cover two things, one, when the user clicks, the marble keeps moving in that direction, rebounding off the screen edges and keeps moving forever. Two, when the user clicks on the screen, the marble moves towards it, with it’s speed reducing by every frame. It doesn’t come to a halt when the destination point is reached, instead, it continues to move, rebounds off the screen edges and slowly comes to halt. The amount of time that the marble keeps moving depends on how far the user clicks from the marble. I had mentioned this second situation here.
Finally, here’s a video of this program running,
Part 1, Part 3