LESSON 9 - April 12, 2013

HTML5 GAME DEVELOPMENT

Operation C.A.N.T (Canvas Ain't No Thang) LESSON # 9

Ben W. Savage

SCALING

Moving right along, let's get right into the topic of scaling in this second half of our lesson on transformations.

First off, what does scaling mean? In layman's terms it's the stretching around of our shape in reference to the X axis, the Y axis, or both.

Let's start by drawing a tiny 20 X 20 fill rectangle somewhere on the canvas. Plug in this code:

<!doctype html>
<title> SCALING </title>
<canvas id = "canvas" width = "550" height = "400"></canvas>
<style>
canvas
{
background-color: #dddddd;
}
</style>

<script>

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


context.fillStyle = "#ff0000";
context.fillRect(canvas.width / 2,canvas.height / 2,20,20);


</script>

Also note that for the x and y values I've specified canvas.width / 2 and canvas.height / 2. This is a useful little shortcut for centering whatever I'm drawing on the canvas by its upper left corner.

Now as you've seen, to rotate something we used rotate(), to translate the coordinates we used translate(). What do you suppose is the method we use for scaling?

Scale() ?

Sorry you're wrong. It's actually: changeTheCoordinatesAlongTheXOrYAxis() and it has 27 arguments.

Nah, I'm kidding. Of course it's scale().

And scale() has two simple little arguments which are the degree of scaling we want to apply to first the X axis, then the Y axis. Scale has a default of 1, so if we have something like scale(.5,.5) we'd shrink our rectangle by 50% on both axes. A value of scale(20,20), and I don't recommend you do this, would blow up our rectangle to a rectangle 20 times its size. If we plug in a value of scale(20,.5), our rectangle would scale 20 times on the X axis, but only half on the Y axis, making for a very wide and short rectangle.

Simple, right?

So let's go right ahead and add a scale value to our mini rectangle.

Add this line right below the getContext line:

context.scale(1.3,1.3);

Hmmm... so the rectangle DID get bigger, but it's further down and to the right compared to my rectangle. And it's CERTAINLY not in the middle of the canvas where I set the smaller version! What gives?

Well, folks, this is scaling the HTML5 way. Don't forget that all scaling is done in relation to the (0,0) point, so even though we specified that our rectangle belonged on the X and Y axes, the scaling pushed them outwards and down the canvas. If you try scaling down to scale(.5,.5) you'll see that they scale back inwards towards the (0,0) top-left origin point.

Do we like this? Haeeeelll no! Are we gonna stand for this? Haeeeeel no! What are we gonna do? We're gonna tranlsate those points!

So let's translate the origin point like we did when we rotated stuff. I'm not going to go review all that again, so please check yesterday's lesson if you're just sticking your head in my lecture hall without having come to all the other lessons.

So let's get busy and start translating!

Remember how we did this?

Let's take it step by step one more time:

1.We add context.translate() to our code in the <script> tag.

REMEMBER THAT TRANSLATE ALWAYS GOES BEFORE SCALE OR ROTATE.

2. We determine the arguments to add to our context.translate() by using this simple formula:

context.translate( x + .5 * width, y + .5 * height);

So first off, let's multiply .5 by 20. Of course, we get 10. Then let's add canvas.width to 10 resulting in canvas.width + 10;

Then we go to the other side and multiply .5 by 20 and get 10 again. We add canvas.height to 10 and get canvas.height + 10.

And those are our two values, so plug 'em in!

context.translate(canvas.width / 2 + 10, canvas.height / 2 + 10);

Got it?

3. We make sure we have a scale() method directly below our translate() method and it's set to

scale(1.3,1.3);

4. Now we go back and reposition our rectangle on the midpoint through the following formula:

fillRect(-.5 * width, -.5 * height, width, height);

making it:

fillRect(-10, -10, width, height);

This should our rectangle back where it was in the first place, but scaled to a width of 1.3, and a height of 1.3.

5. Save it and run it. Did it work out?

Here's the final code in the <script> tag.

<script>

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

context.translate(canvas.width / 2 + 10, canvas.height / 2 + 10);
context.scale(3,3);
context.fillStyle = "#ff0000";
context.fillRect(-10,-10,20,20);


</script>

Go ahead and mess with this little bugger a bt. A you might've noticed before, if you scaled it too much before translating the origin point, it would've extended too far off the edge of the canvas. Now with our new and improved version, you can set it to much larger numbers and still see it anchored right there in the middle of your screen. Cool, huh? Now we're going places!

Now yesterday we talked about bounding boxes, but we only used rectangular stuff in our rotations, so let's take a look at how to rotate and scale a non-rectangular object. Sound like fun?

Today we'll be working with this little triangle I conjured up. It's a beaut! I think I'll name him Herbert.


<!doctype html>
<title> ROTATING AND SCALING HERBERT</title>
<canvas id = "canvas" width = "1000" height = "700"></canvas>
<style>
canvas
{
background-color: #dddddd;
}
</style>

<script>

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


context.fillStyle = "#44ee00";
context.strokeStyle = "#5500dd";
context.moveTo(200,200);
context.lineTo(300,200);
context.lineTo(150,300);
context.lineTo(200,200);
context.stroke();
context.fill();


</script>

First off, note that I engarged the canvas just so we can see our rotated and scaled triangle before setting it back into place. Once we translate it, you'll see that it goes much further out than the other examples.

So, the good news about rotating and scaling Herbert is that we only need to translate and realign our triangle once, then we can scale and rotate as we please. No need to translate twice in other words.

Our formula remains the same:

context.translate(x +.5 * width, y + .5 * height);

So all we need to do is plug in the formula for width and height.

Now with the rectangle and with images we really had it made because the info that we needed was already in the rectangle itself.

Here, however, we need to fish for it a bit.

Remember first of all that objects are pinned onto the canvas from their top-left corner, right?

Well, to find the (0,0) for Herbert, we just need to look at our points and find the two that would constitute a leftmost x and y axis, then find the top-left meeting point.

So our leftmost point is: the 150 X value in the second lineTo. Perfect. This is the point that will define our Y axis. In a minute I'll draw our two reference lines so you can get a better idea.

Now we need to find our X axis and we do that by finding the lowest Y value in our lines. And that is... 200. Gotcha!

So how do we find our X and Y values? Well, easy! Take a look at these new lines I've added. They help us pinpoint our (0,0) value.

Go ahead and add these 5 lines of code to the bottom of the <script> tag.

context.moveTo(0,200);
context.lineTo(550,200);
context.moveTo(150,0);
context.lineTo(150,400);
context.stroke();

These will give you a visual representation of what we just talked about. So what are the x and y values for our rectangle?

150 X and 200 Y! Great! That's half of our formula. Now we need the width and height. This is super easy. To find this all we need is the largest X value and the largest Y value assigned to our lines and subtract them from the lowest X and Y values. In other words, we subtract the highest X value and the lowest X value. After that we subtract the highest Y value and the lowest Y value.

They are: 150 and 100. Understand where I got that from? Clear? Let's move on.

Add these extra lines and you'll see.

context.moveTo(300,0);
context.lineTo(300,400);
context.moveTo(0,300);
context.lineTo(550,300);
context.stroke();

These two final lines complete, guess what.... our bounding box. It's the box around our triangle in the middle there. Notice that it is, like we said, the SMALLEST POSSIBLE rectangle we can fit around our object. Even though it's more than half empty, this is still the smallest possible rectangle we can draw around this sucker! Just you try to make a smaller one without cutting off a part of it!

Wonderful. Now we have all four pieces of information necessary to rotate or scale our non-rectangular object!

Namely, 150,200,150,100

Let's get to work!

So, as we've seen a gazillion times, our translate mehtod is: context.translate( x + .5 * width, y + .5 * height);

So if we plug in our values: context.translate (150 + .5 * 150, 200 + .5 * 100);

We come up with: context.translate(225,250);

Great! We're getting there. Go ahead and add the translate method to your code.

Oh, and go ahead and get rid of those extra lines. They were just to give you some visuals.

Let's get super ambitious and try rotate AND scale to our friend Herbert the triangle.

So UNDER our context.translate() let's add the following two lines:

context.scale(1.3,1.3);
context.rotate(.02);

Save it, run it and check it out. Whoa! It's all the way down there! But it seems to work so far... Try and fiddle around with some rotation values. Just the slightest bit, though, because you could send your triangle off the canvas and into the "Land Beyond the Canvas" that few objects rarely return from.

When you're ready, we'll bring Herbert back up to where he was before.

The formula for doing that once again is:

(-.5 * width, -.5 * height, width, height);

Now we don't have a recangle readily available to plug these values into, so we need to hunt them down a bit.

What were our width and height again? 150 and 100. Now where do I add these values?? When we moved a rectangle back into place we substituted the X and Y values with a negative half-width and negative half-height, right? (-.5 * width, -.5 * height)

So the point here is to change the x and y values and keep everything proportionate at the same time.

PROPORTIONATE is the key word here, and you'll see in a minute what I mean...

When we plug in our width and height values into our second formula we get: (-75 and -50). Sooo....

This means that our x values must be -75 and -50. So let's look use those as reference points and scale everything proportionately. So we said that our leftmost value was our X value, right? As we said at the beginning of this exercise, our X value was 150. How about our Y value? That's the upmost (or lowest in other words) Y value. As we said before, this was 200.

NOW, we're going to get a magic number we can use to plug into this formula by subtracting 150 from -75 and by subtracting 200 from -50.

Our X magic number is -225. Our Y magic number is -250.

Whaddya know! They're the same as our translate() values! Hate me for making you go the long way?

We got these by subtracting our X value by the new translated X value from the second formula and by subtracting our Y value by the new translated Y value fro the second formula.

Now, we take these magic numbers and add them to all the other X and Y values in the formula.

To save you the work, the resulting code is:

<!doctype html>
<title> ROTATING AND SCALING HERBERT</title>
<canvas id = "canvas" width = "1000" height = "700"></canvas>
<style>
canvas
{
background-color: #dddddd;
}
</style>

<script>

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


context.translate(225,250);
context.scale(1.3,1.3);
context.rotate(.7);

context.fillStyle = "#44ee00";
context.strokeStyle = "#5500dd";
context.moveTo(-25,-50);
context.lineTo(75,-50);
context.lineTo(-75,50);
context.lineTo(-25,-50);
context.stroke();
context.fill();

</script>

So as you can see, the method for rotating and scaling point-based shapes is quite a bit more labor-intensive than the other version, and maybe I've wasted your time by going over it since, as I mentioned, most of the scaling and rotating in games will be done with rectangular shapes like imported images, BUT, you never know when this stuff could come in handy! If you're feeling ambitious, why not mess around and try to make scale or rotate more elaborate images. You might even find a shortcut! If you do, please let me know!

And that does it for our two-part tutorial on transformations! I've got a little extra time today, so I'll do my best to make the other tutorials a little nicer and maybe add screenshots or something. Tomorrow and the next day we talk about the last few things that the canvas can provide us with before moving on to bona fide GAME techniques.

So stay tuned! As always, thanks for following along! Code didn't work for you? Hate me? Are you my illegitimate child? Send input anytime!

Until tomorrow!

-Ben
@benwhi
Onward to Lesson Ten!
Back to Lesson Eight!
Back to Index