## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

No credit card required

# Curve and Circular Movement

Whew! Moving and colliding balls on vectors can create some cool effects. However, moving in straight lines is not the only way you might want to move objects. In this section, we will show you some ways to animate objects using circles, spirals, and curves.

## Uniform Circular Motion

Uniform circular motion occurs when we move an object along the distinct radius of a defined circle. Once we know the radius, we can use our old friends `cosine` and `sine` to find the `x` and `y` locations of the moving object. The equations to find the locations of an object moving uniformly on a defined circle are:

```x = radius * cosine(angle)

We will create an example of uniform circular movement with a circle that has a radius of `125`, with its center position at `250`,`250` on the canvas. We will move a ball along that circle, starting at an `angle` of `0`.

In `canvasApp()`, we will define this circle path as a dynamic object stored in the `circle` variable. While this object defines the properties of a circle, we will not actually draw this circle on the canvas; rather, it defines only the path on which we will move our `ball` object:

```var circle = {centerX:250, centerY:250, radius:125, angle:0}
var ball = {x:0, y:0,speed:.1};```

In `drawScreen()`, we will incorporate the equations for uniform circular movement. To do this, we will set the `x` and `y` properties of the `ball` object to the products of the equations, added to the center location of the circle path on the canvas (`circle``.centerX`, `circle.centerY`):

```ball.x = circle.centerX + Math.cos(circle.angle) * circle.radius;
ball.y = circle.centerY + Math.sin(circle.angle) * circle.radius;```

We then add the speed of the ball to the angle of the circle path. This effectively sets the ball to move to a new location the next time `drawScreen()` is called:

`circle.angle += ball.speed;`

Finally, we draw the ball onto the canvas:

```context.fillStyle = "#000000";
context.beginPath();
context.arc(ball.x,ball.y,15,0,Math.PI*2,true);
context.closePath();
context.fill();```

You can see what the circle path looks like in Figure 5-12. We have drawn the points on the canvas to illustrate the circle path.

You can easily alter the location and size of the circle path by altering the `radius`, `centerX`, and `centerY` properties of the circle path object.

Example 5-9 shows the code for CH5EX9.html.

Example 5-9. Moving in a circle

```<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>CH5EX9: Moving In A Circle</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
canvasApp();

}

function canvasSupport () {
return Modernizr.canvas;
}

function canvasApp() {

if (!canvasSupport()) {
return;
}

function  drawScreen () {

context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);

ball.x = circle.centerX + Math.cos(circle.angle) * circle.radius;
ball.y = circle.centerY + Math.sin(circle.angle) * circle.radius;

circle.angle += ball.speed;

context.fillStyle = "#000000";
context.beginPath();
context.arc(ball.x,ball.y,15,0,Math.PI*2,true);
context.closePath();
context.fill();

}

var circle = {centerX:250, centerY:250, radius:125, angle:0}
var ball = {x:0, y:0,speed:.1};

theCanvas = document.getElementById("canvasOne");
context = theCanvas.getContext("2d");

setInterval(drawScreen, 33);

}

</script>

<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>```

## Moving in a Simple Spiral

There are many complicated ways to move an object on a spiral path. One such way would be to use the Fibonacci sequence, which describes a pattern seen in nature that appears to create perfect spirals. The Fibonacci sequence starts with the number 0, and continues with each subsequent number calculated as the sum of the two previous numbers in the sequence. Each subsequent rotation of the spiral is the sum of the two previous numbers (1, 2, 3, 5, 8, 13, 21, 34, 55, 89...). However, as you might imagine, the math used to create this sequence is quite involved, and it is also difficult to translate to object movement.

For our purposes, we can create a simple spiral by increasing the radius of the circle path on each call to `drawScreen()`. If we take the code from Example 5-9, we would add a `radiusInc` variable, which we will use as the value to add the radius movement path of the circle. We create this new variable in `canvasApp()`:

`var radiusInc = 2;`

Then, in `drawScreen()`, we add the following code to increase the radius of the circle every time we move the object:

`circle.radius += radiusInc;`

In Figure 5-13, you can see what the resulting spiral looks like (to illustrate the path, this example includes the points).

If you want a tighter spiral, decrease the value of `radiusInc`. Conversely, if you want a wider spiral, increase the value of `radiusInc`.

Example 5-10 shows the code for CH5EX10.html from the code distribution.

Example 5-10. Moving in a simple geometric spiral

```<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>CH5EX10: Moving In A Simple Geometric Spiral </title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
canvasApp();

}

function canvasSupport () {
return Modernizr.canvas;
}

function canvasApp() {

if (!canvasSupport()) {
return;
}

var pointImage = new Image();
pointImage.src = "point.png";
function  drawScreen () {

context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);

ball.x = circle.centerX + Math.cos(circle.angle) * circle.radius;
ball.y = circle.centerY + Math.sin(circle.angle) * circle.radius;
circle.angle += ball.speed;

//Draw points to illustrate path

points.push({x:ball.x,y:ball.y});

for (var i = 0; i< points.length; i++) {
context.drawImage(pointImage, points[i].x, points[i].y,1,1);
}

context.fillStyle = "#000000";
context.beginPath();
context.arc(ball.x,ball.y,15,0,Math.PI*2,true);
context.closePath();
context.fill();

}

var ball = {x:0, y:0,speed:.1};
var points = new Array();

theCanvas = document.getElementById("canvasOne");
context = theCanvas.getContext("2d");

setInterval(drawScreen, 33);

}

</script>

<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>```

## Cubic Bezier Curve Movement

Cubic Bezier curves can be used to define a movement path for an object. Pierre Bezier first popularized these curves in the 1960s. They are widely used in 2D vector graphics to define smooth curves for drawing, but they can also be used in animation to define a path for motion.

A cubic Bezier curve is created using four distinct points—`p0`, `p1`, `p2`, and `p3`:

`p0`

The starting point of the curve. We will refer to these `x` and `y` values as `x0` and `y0`.

`p3`

The ending point of the curve. We will refer to these `x` and `y` values as `x3` and `y3`.

`p1` and `p2`

The control points for the curve. The curve does not pass through these points; instead, the equation uses these points to determine the arc of the curve. We will refer to these `x` and `y` values as `x0`, `x1`, `x2`, `x3`, `y0`, `y1`, `y2`, and `y3`.

### Note

The usage of the `p1` and `p2` points is the biggest stumbling block for understanding Bezier curves. The easiest way to understand the relationship between these points and the curve is to draw them on a bitmapped canvas, which we will do several times in this chapter.

After you have the four points, you need to calculate six coefficient values that you will use to find the `x` and `y` locations as you move an object on the curve. These coefficients are known as `ax`, `bx`, `cx`, `ay`, `by`, and `cy`. They are calculated as follows:

```cx = 3 (x1 - x0)
bx = 3 (x2 - x1) - cx
ax = x3 - x0 - cx - bx
cy = 3 (y1 - y0)
by = 3 (y2 - y1) - cy
ay = y3 - y0 - cy - by
```

After you’ve calculated the six coefficients, you can find the `x` and `y` locations based on the changing `t` value using the following equations. The `t` value represents movement over time:

```x(t) = axt3 + bxt2 + cxt + x0
y(t) = ayt3 + byt2 + cyt + y0
```

For our purposes, the `t` value will be increased by the `speed` at which we want the object to move. You will notice, though, that this value does not easily equate to the `speed` values we used elsewhere in this chapter. The reason is that the `t` value was not created with movement over time for animation in mind. The speed we specify must be smaller than `1` so the movement on the curve will be incremental enough for us to see it as part of the animation. For our example, we will increase `t` by a speed of `.01`, so that we will see `100` points on the movement curve (1/100 = .01). This is advantageous because we will know our object has finished moving when the `t` value is equal to `1`.

For Example 5-11 (CH5EX11.html), we will start by creating the four points of the Bezier curve in the `canvasApp()` function:

```var p0 = {x:60, y:10};
var p1 = {x:70, y:200};
var p2 = {x:125, y:295};
var p3 = {x:350, y:350};```

We then create a new `ball` object with a couple differing properties from those in the other examples in this chapter. The `speed` is `.01`, which means the object will move 100 points along the curve before it is finished. We start the `t` value at `0`, which means the `ball` will begin at `p0`:

`var ball = {x:0, y:0, speed:.01, t:0};`

Next, in the `drawScreen()` function, we calculate the Bezier curve coefficient values (`ax`, `bx`, `cx`, `ay`, `by`, `cy`) based on the four points (`p0`, `p1`, `p2`, `p3`):

```var cx = 3 * (p1.x - p0.x)
var bx = 3 * (p2.x - p1.x) - cx;
var ax = p3.x - p0.x - cx - bx;

var cy = 3 * (p1.y - p0.y);
var by = 3 * (p2.y - p1.y) - cy;
var ay = p3.y - p0.y - cy - by;```

Then, we take our `t` value and use it with the coefficients to calculate the `x` and `y` values for the moving object. First, we get the `t` value from the `ball` object, and store it locally so we can use it in our calculations:

`var t = ball.t;`

Next, we add the `speed` to the `t` value so that we can calculate the next point on the Bezier path:

`ball.t += ball.speed;`

Then, we use the `t` value to calculate the `x` and `y` values (`xt`, `yt`) using the Bezier curve equations:

```var xt = ax*(t*t*t) + bx*(t*t) + cx*t + p0.x;
var yt = ay*(t*t*t) + by*(t*t) + cy*t + p0.y;```

We add the `speed` to the `t` value of `ball`, then check to see whether `t` is greater than `1`. If so, we don’t increase it any further because we have finished moving on the curve:

```ball.t += ball.speed;

if (ball.t > 1) {
ball.t = 1;
}```

Finally, when we draw the `ball` object on the canvas, we use the `xt` and `yt` values:

`context.arc(xt,yt,5,0,Math.PI*2,true);`

Figure 5-14 shows what Example 5-11 (CH5EX11.html) looks like when it is executed in a web browser. In addition to drawing the points of the path using the `points` array, we also draw the four points of the Bezier curve. These illustrate the relationship of the points to the curve itself. Notice that the curve does not pass through `p1` or `p2`.

Example 5-11 gives the full code listing for CH5EX11.html, including the code to draw the Bezier curve points on the canvas. You can find that code in the `drawScreen()` function following the `//draw the points` comment.

Example 5-11. Moving on a cubic Bezier curve

```<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>CH5EX11: Moving On A Cubic Bezier Curve </title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">
canvasApp();

}
function canvasSupport () {
return Modernizr.canvas;
}

function canvasApp() {

if (!canvasSupport()) {
return;
}

var pointImage = new Image();
pointImage.src = "point.png";

function  drawScreen () {

context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);

var t = ball.t;

var cx = 3 * (p1.x - p0.x)
var bx = 3 * (p2.x - p1.x) - cx;
var ax = p3.x - p0.x - cx - bx;

var cy = 3 * (p1.y - p0.y);
var by = 3 * (p2.y - p1.y) - cy;
var ay = p3.y - p0.y - cy - by;

var xt = ax*(t*t*t) + bx*(t*t) + cx*t + p0.x;
var yt = ay*(t*t*t) + by*(t*t) + cy*t + p0.y;

ball.t += ball.speed;

if (ball.t > 1) {
ball.t = 1;
}

//draw the points

context.font ="10px sans";
context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p0.x,p0.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("0",p0.x-2,p0.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p1.x,p1.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("1",p1.x-2,p1.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p2.x,p2.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("2",p2.x-2, p2.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p3.x,p3.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("3",p3.x-2, p3.y+2);

//Draw points to illustrate path

points.push({x:xt,y:yt});

for (var i = 0; i< points.length; i++) {
context.drawImage(pointImage, points[i].x, points[i].y,1,1);
}

context.closePath();

//Draw circle moving

context.fillStyle = "#000000";
context.beginPath();
context.arc(xt,yt,5,0,Math.PI*2,true);
context.closePath();
context.fill();

}

var p0 = {x:60, y:10};
var p1 = {x:70, y:200};
var p2 = {x:125, y:295};
var p3 = {x:350, y:350};
var ball = {x:0, y:0, speed:.01, t:0};
var points = new Array();

theCanvas = document.getElementById("canvasOne");
context = theCanvas.getContext("2d");

setInterval(drawScreen, 33);

}

</script>

<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>```

## Moving an Image

Moving an image on a cubic Bezier curve path is just as easy as moving a circular drawing object, as we’ll demonstrate in the next two examples. Suppose you are making a game where bullseyes move across the canvas and the player must shoot at them. You could use cubic Bezier curve paths to create new and interesting patterns for the bullseyes to move along.

For this example, we first create a global variable named `bullseye`, which we will use to hold the bullseye.png image that we will load to display on the canvas:

```var bullseye;
bullseye = new Image();
bullseye.src = "bullseye.png"
}```

In `canvasApp()`, we will create a different path for the curve from the one in the first example by setting new values for `p0`, `p1`, `p2`, and `p3`. Using these values, the `bullseye` will move on a parabola-like path (Figure 5-15 shows the path of the curve):

```var p0 = {x:60, y:10};
var p1 = {x:150, y:350};
var p2 = {x:300, y:375};
var p3 = {x:400, y:20};```

We also need to create a `player` object that represents the bullseye on the canvas:

`var player = {x:0, y:0, speed:.01, t:0};`

In `drawImage()`, after we calculate `t`, `xt`, and `yt`, we draw the image on the canvas:

```player.x = xt-bullseye.width/2;
player.y = yt-bullseye.height/2;

context.drawImage(bullseye,player.x,player.y);```

The rest of Example 5-12 works just like Example 5-11.

Example 5-12. Moving an image

```<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>CH5EX12: Moving An Image </title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">

var bullseye;
bullseye = new Image();
bullseye.src = "bullseye.png"
}

canvasApp();
}

function canvasSupport () {
return Modernizr.canvas;
}

function canvasApp() {

if (!canvasSupport()) {
return;
}

var pointImage = new Image();
pointImage.src = "point.png";
function  drawScreen () {

context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);

var t = player.t;

var cx = 3 * (p1.x - p0.x)
var bx = 3 * (p2.x - p1.x) - cx;
var ax = p3.x - p0.x - cx - bx;

var cy = 3 * (p1.y - p0.y);
var by = 3 * (p2.y - p1.y) - cy;
var ay = p3.y - p0.y - cy - by;

var xt = ax*(t*t*t) + bx*(t*t) + cx*t + p0.x;

var yt = ay*(t*t*t) + by*(t*t) + cy*t + p0.y;

player.t += player.speed;

if (player.t > 1) {
player.t = 1;
}      //draw the points

context.font = "10px sans";

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p0.x,p0.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("0",p0.x-2,p0.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p1.x,p1.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("1",p1.x-2,p1.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p2.x,p2.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("2",p2.x-2, p2.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p3.x,p3.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("3",p3.x-2, p3.y+2);

//Draw points to illustrate path

points.push({x:xt,y:yt});

for (var i = 0; i< points.length; i++) {
context.drawImage(pointImage, points[i].x, points[i].y,1,1);
}

context.closePath();

player.x = xt-bullseye.width/2;
player.y = yt-bullseye.height/2;

context.drawImage(bullseye,player.x,player.y);

}

var p0 = {x:60, y:10};
var p1 = {x:150, y:350};
var p2 = {x:300, y:375};
var p3 = {x:400, y:20};
var player = {x:0, y:0, speed:.01, t:0};
var points = new Array();

theCanvas = document.getElementById("canvasOne");
context = theCanvas.getContext("2d");

setInterval(drawScreen, 33);

}

</script>

<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>```

## Creating a Cubic Bezier Curve Loop

You can create some very interesting paths using the four points in a cubic Bezier curve. One such effect is a loop. To create a loop, you need to make sure the points form an X, with `p0` diagonal from `p1`, and `p2` and `p3` on an opposite diagonal from the other two points. `p0` and `p3` must be closer to the center of the canvas than `p1` or `p2`. Below are the points we will use to create this effect in Example 5-13:

```var p0 = {x:150, y:440;
var p1 = {x:450, y:10};
var p2 = {x:50, y:10};
var p3 = {x:325, y:450};```

Since it is much easier to show than tell when it comes to cubic Bezier curves, look at Figure 5-16. It shows what the looping curve looks like when Example 5-13 is executed in a web browser.

### Note

This effect can only be created with the four points of a cubic Bezier curve. There is also a three-point Bezier curve known as a quadratic Bezier curve. You cannot create loops or S curves with quadratic Bezier curves because the three points are not as precise as the four points of a cubic Bezier curve.

Since the code for this example is essentially the same as in Example 5-12 (besides the four points), we have highlighted in bold the changed code in Example 5-13. We have done this to show you that—with relatively simple changes—you can create dramatic animation effects using cubic Bezier curves.

Example 5-13. Bezier curve loop

```<!doctype html>
<html lang="en">
<meta charset="UTF-8">
<title>CH5EX13: Bezier Curve Loop </title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">

var bullseye;
bullseye = new Image();
bullseye.src = "bullseye.png"
}

canvasApp();
}

function canvasSupport () {
return Modernizr.canvas;
}

function canvasApp() {

if (!canvasSupport()) {
return;
}

var pointImage = new Image();
pointImage.src = "point.png";
function  drawScreen () {

context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);

var t = player.t;

var cx = 3 * (p1.x - p0.x)
var bx = 3 * (p2.x - p1.x) - cx;
var ax = p3.x - p0.x - cx - bx;

var cy = 3 * (p1.y - p0.y);
var by = 3 * (p2.y - p1.y) - cy;
var ay = p3.y - p0.y - cy - by;

var xt = ax*(t*t*t) + bx*(t*t) + cx*t + p0.x;

var yt = ay*(t*t*t) + by*(t*t) + cy*t + p0.y;

player.t += player.speed;

if (player.t > 1) {
player.t = 1;
}
//draw the points

context.font = "10px sans";

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p0.x,p0.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("0",p0.x-2,p0.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p1.x,p1.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("1",p1.x-2,p1.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p2.x,p2.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("2",p2.x-2, p2.y+2);

context.fillStyle = "#FF0000";
context.beginPath();
context.arc(p3.x,p3.y,8,0,Math.PI*2,true);
context.closePath();
context.fill();
context.fillStyle = "#FFFFFF";
context.fillText("3",p3.x-2, p3.y+2);

points.push({x:xt,y:yt});

for (var i = 0; i< points.length; i++) {
context.drawImage(pointImage, points[i].x, points[i].y,1,1);
}

context.closePath();

player.x = xt-bullseye.width/2;
player.y = yt-bullseye.height/2;

context.drawImage(bullseye,player.x,player.y);

}

var p0 = {x:150, y:440};
var p1 = {x:450, y:10};
var p2 = {x:50, y:10};
var p3 = {x:325, y:450};
var player = {x:0, y:0, speed:.01, t:0};

var points = new Array();

theCanvas = document.getElementById("canvasOne");
context = theCanvas.getContext("2d");

setInterval(drawScreen, 33);

}

</script>

<body>
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</body>
</html>```

## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

No credit card required