Assignment 03: Index Page

Workbook 3 - page 1, Translate Transformations

In the previous workbook, we commented on the idea of a coordinate system (you may want to review the Tutorial: Points, Vectors, Coordinates). Recall that in Canvas and SVG, we interpret a coordinate (x,y) by starting at the top left corner of the canvas element and measuring x units ("html pixels") to the right, and y units down.

The JavaScript for this page is in A03-1-intro.js - you will want to read it as you read the text. The HTML is not so important.

Box 1: The idea of transformation

Let's start with a really simple example.

Here, I'll draw a simple object: a square with a triangle inside of it. Notice that this shape involves two primitives (a square and a triangle), 7 "points" (the corners of the 2 polygons), and a whole bunch of pixels (all the pixels that get colored when things get drawn).

Which is drawn with pretty simple code:

function drawTriSquare(context) {
    context.fillStyle = "goldenrod";
    context.fillRect(20,20,20,20);
    context.fillStyle = "red";
    context.beginPath();
    context.moveTo(25,25);
    context.lineTo(25,35);
    context.lineTo(35,30);
    context.fill();
}

Now, suppose that I want to move this shape around with a slider. I need to redraw it in different positions. I don't need to move all the pixels (since we figure them out from the points), but I do need to move all the points.

Let me show you two ways to do this. Be sure to look at the JavaScript file as well.

Way 1: Parametric Geometry

The obvious way to do this is to just replace every position in our code with a parameter so we can move it around:

function drawTriSquareParameter(context,xval) {
    context.fillStyle = "goldenrod";
    context.fillRect(20+xval,20,20,20);
    context.fillStyle = "red";
    context.beginPath();
    context.moveTo(25+xval,25);
    context.lineTo(25+xval,35);
    context.lineTo(35+xval,30);
    context.fill();
}

Notice that I had to (carefully) change every x coordinate in the code to use a new x value. Each coordinate (x,y) was changed to (x+xval,y).

Way 2: Transformations

Instead, rather than moving the points, we move the coordinate system.

function drawTriSquareTransform(context,xval) {
    context.save();
    context.translate(xval,0);
    drawTriSquare(context);
    context.restore();
}

This code deserves careful understanding, from the inside out.

You can think about translate as applying an addition of the translation amount to each of the coordinates that are used for drawing. All drawing commands take coordinates and apply the "current translation" to it before using it. This is built in to all the drawing commands. Part of the context is to keep track of the "current translation."

More generally, translate is a specific type of transformation. A transformation is a function that takes a point and returns a new point. So, translate(a,b) can be thought of as a function f(x,y) => (x+a,y+b). This function is applied to all coordinates when we're drawing.

However, we can also think of translate (or any transformation) as changing the coordinate system that we use to interpret the coordinates for drawing. For various reasons (that may not be obvious until you've been doing graphics for a while), this is a more convenient way to think about things.

Box 2: Repeat after me...

Remember: translate moves the current coordinate system (relative to the current coordinate system). Understanding this idea is important. Try to understand it now, when we're dealing with a single, simple transformation (translation). Soon, we add more types.

Because transformations (including translate) change the current coordinate system, they combine (this is known as composition). If you move the coordinate system to the right, and then you move it to the right again, you've moved it twice as much to the right.

Also, because we can make many different coordinate systems, we can draw the same object multiple times. With translation, we can make it appear in different places. In the future, we can create other differences. Look at this example:

    drawTriSquare(context2);
    context2.translate(40,0);
    drawTriSquare(context2);
    context2.translate(40,0);
    drawTriSquare(context2);
    context2.translate(40,0);
    drawTriSquare(context2);

Notice how in the code, the translations add (since I don't save and restore). We'll discuss this more in Box 4 below.

This idea of re-using the same "object" over and over is known as instancing. Here, the object is only represented in code (since we're using an immediate mode API). Later, we'll see it in a retained mode API. Either way, we define it once, and re-use it over and over.

Box 3: Using Transformations for Convenient Coordinates

Right from the beginning we saw the advantages of working in convenient coordinates. The fact that we program in Canvas relative to the Canvas element (instead of the coordinate system of the window) means we don't need to worry about where on the screen the Canvas element is.

Don't take this concept for granted: the ability to work in convenient coordinate systems is really important. It becomes useful because we can change coordinate systems easily.

Often, it is useful to define objects such that the object origin is at 0,0. This way, all coordinates in the object's definition at relative to the object. When the object is placed into some other coordinate system, things will get moved appropriately.

So, from the original example, you might notice that the "object" of the triangle in square is positioned at 20,20. The insides had to be positioned relative to that (I had to know 25,25 was the corner of the triangle).

Instead, we have a convention that all objects are drawn with their origin at 0,0. This makes it easier to define objects, but also to use them. There is still a question of where the "origin" should be (for this example, I'll call it the upper left of the square). So, we can do:

For this one, you have to go look at the code. But make sure you understand why this one uses save and restore, while Box 2 did not.

Also, this example uses JavaScript modules. This is a good opportunity to learn about them. Read about them in your favorite JavaScript book, or try this chapter in Understanding ES6.

Box 4: Multiple Translations

We aren't limited to just one translation. After we do a first translation, we can do a second translation, and a third, and so forth.

The way to think about this: the first translation moves the initial coordinate system. The second translation moves that coordinate system (the result of the first movement). The third translation moves that coordinate system. And so forth. Using the paper analogy... each translation moves the piece of paper from wherever it is. When we draw, we draw on the piece of paper wherever it is.

We already took advantage of that in the code above (Box 2), where I translated, drew something, translated again, drew some more. You can think of this as keeping your pen in the same place and moving the piece of paper.

The process of combining transformations is called composition. When we apply one transformation after the other, the result is the composition of the two.

When the transformations are translation, the composition process is simple: we just add things up. Since addition is commutative (remember that word from high school algebra?), the order that we do the additions (or translations) doesn't matter. However, this is not true for transformations in general. When we start to combine transformations we will see cases where order matters.

Exercise 1: Fix my code

The canvas has a triangle in it. When the button is pressed, the triangle should move to the right. When the button is released, the triangle should move to its original position. Right now, each time it jumps farther to the right!

Understand why the initial code is wrong (it's in the A03-1-intro.js file) and does what it does. Fix it without using negative numbers. Note: if you move the mouse outside of the button, the mouseup event is missed. You don't have to fix that problem.

You should only change the boxEdraw function, and not use negative numbers.

SPOILER HINT: (read this only after trying to fix it yourself). Remember that the translation is part of the drawing context. We need to worry about save and restore.

Even if you needed the hint, the grader will check that you made this work correctly.

Summary - Transformations and Coordinate Systems

Hopefully, you now have an idea of what we mean by using transformations to change the coordinate system. We only did simple changes (moving the coordinate system with translate), but we'll see some other transformations next, and why these things are so useful.

On to page two!