Page 6: Interactive 2D Scenes

CS559 Spring 2021 Sample Solution - Workbook 2

Written by CS559 course staff

You can try out the example solutions here. The interesting code is on page 4 and page 7.

In an immediate-mode API like Canvas, when we draw a primitive, the API converts the converts the primitive (for example, by coloring the pixels) - it doesn’t save the primitive itself. This means if we want to refer to the primitive later, we need to keep track of it ourselves. The standard way to do this is to keep a list of the objects we want to have on the display. We call this list of objects a display list.

In general, the display list might store more complicated objects that we want to keep track of. And it might have a more complex structure than just a list. For example, often it is a tree because we allow objects to be made of other objects. For this reason, the data structure is sometimes called a scene graph (because it is a graph that stores information about the items that make up the scene).

I will use these two terms loosely.

An API like SVG is sometime called a scene graph or display list API because it keeps the data structure for you. Canvas is not a display list API because the program has to keep a display list itself (if the programmer wants one).

The things that SVG was doing “behind the scenes” we will have to do ourselves - this includes keeping a list of objects.

Box 1: Display Lists

Here, I am implementing the animation from the previous page using Canvas in 2 different ways.

Read 02-06-01.js to see how this is implemented differently in each. You can see 02-06-01.html which makes 2 canvases for this program.

The first version of the code, in lines 27-55 is the same as on the previous page. We simply draw each rectangle each time. The only “list of rectangles” is in the code - and even there, they only exist in a loop.

Starting at line 59, we have a second version of the code. In this version we will keep a list of rectangles. On line 66, we make an empty list rects to store the rectangles. On lines 67-71, we have a loop that is similar to the first version of the code - however, rather than calling the Canvas API to draw the rectangle, we create a data structure and add it to the list.

rects.push({"x":30+c*50,"y":20+r*20,"w":30,"h":10,color:"#888"});

This line is worth discussing because it may be unfamiliar if you are new to JavaScript. First note that I am appending something to the end of the rects object (which is a list). For JavaScript arrays, push means append. The thing I am appending is an object - defined in place (with the curly braces).

This is a very simple form of object-oriented programming: I am simply listing the fields I want in the object (x, y, w, h, and color) and specifying their values. JavaScript has other ways to do object-oriented programming, but for this little example I just made the object as I needed it.

But hopefully, you realize that what I have done is stored all the information about a rectangle.

Because I have stores information about my rectangles on a list, when I need to draw those rectangles, I can just loop over the list and draw each one.

If all we were going to do is draw the rectangles, the extra effort of representing them as a list might not be worth it. However, the list will be very helpful when we want to interact with the rectangles.

Also, while in this example I simply kept a list of rectangles, you could imagine keeping other information.

Box 2: Canvas Events

As far as the web browser is concerned, our Canvas is a single element. Even though we may have a list of objects, our entire Canvas is a single element. Only the Canvas element gets events.

When the Canvas element receives an event, such as a mouse click event we need to figure out what to do with it.

One complication is that when we get events, the mouse position is given in the coordinate system of the entire page, not the Canvas itself. That is, the x and y position is relative to the top left corner of the web page. In the case of a box, it’s the box page. Here’s a simple example:

Notice that the position of the event is relative to the web page. When you look at the code in 02-06-02.js you’ll see the we have to figure out where the canvas is (that’s the event.target), and subtract the top left to figure out where the event is relative to the corner of the canvas.

This specific operation - adjusting event coordinates so that we have them in canvas coordinates, rather than page coordinates, is something we will do all of the time. It is a special case of a coordinate system conversion. The browser gives us the event in one coordinate system, but we want to work in another. Changing coordinate systems is something we will do a lot of in graphics.

Box 3: Object Events

If we want to associate an event with an object, like a rectangle, it is our responsibility to figure out which object on the Canvas should receive the event. We get an event for the Canvas, and then need to look through the objects to see which one should get the event.

Like the SVG version on the last page, 02-06-03.js allows the user to click on rectangles and causes them to change color. You can even click on the moving rectangle (if you can catch it - you need to release the mouse when the cursor is over the box).

The code for this is a bit more complicated than for the SVG version, and it is worth reading through to make sure that you understand. Especially, since it uses some JavaScript tricks. And, we’ll ask you to do something similar on the next page.

As in the previous box, we build a list of rectangles to keep track of them. And just like the earlier box, we’ll draw the rectangles by iterating over the list. One difference: with each rectangle, we’ll also store a flag as to whether or not it has been clicked - so we know which color to draw it.

The bigger piece is handling events. When you look at line 95 in 02-06-03.js you’ll see we set an event handler for the canvas. After converting from page coordinates to canvas coordinates, we then call the clickRectList function - which sees which (if any) of the rectangles the click was in.

Fortunately, all of the objects are rectangles, and checking to see if the mouse position is inside of them is easy.

This code is a little bit tricky - so take the time to understand it. We’re going to ask you to make your own version of it on the next page.

One difference: we will ask you to do circles, not rectangles. So figuring out whether the event position is inside of the circle is a little different.

Box 4: Animation and Events

We can, of course, make our rectangles at any time - not just before we start drawing. And, there are events other than a mouse click. We can get an event every time the mouse moves.

So, here’s a basic “paint” program - we add a rectangle whenever the mouse moves. In this case, we probably don’t have to keep a list of the rectangles and redraw them all - we could just keep adding them to the canvas. But I will re-draw them anyway, since I want to do something with them later…

Again, read the code in 02-06-04.js - it’s simpler in some ways than the rectangles since my objects are just points. Notice how I made the button to clear things.

OK, this is kind of boring - let’s make it more interesting by animating things!

I want to make it look like rain, so I will move each dot downward on each animation step. Of course, I need to be careful: when I dot hits the bottom, I need to remove it from the list (otherwise, I will end up with a lot of dots off the screen).

Look at 02-06-04a.js - you’ll see some examples of functional programming style. Notice the way that I get rid of dots that go off the bottom. While it is concise, it is also inefficient because it is making a copy of the list each time.

Programming in functional style is optional for this class - but it is good to learn. If nothing else, you’ll need to read code written in this style.

One of the more interesting pieces of this: notice how we use animation loops and events.

Box 5: Interaction without Events

As long as we have an animation loop running, we can get by without events for some things by polling: checking the input device on every iteration, and doing something on every animation step - whether there is an event or not.

Notice how this rain is different than the previous one.

This example is 02-06-05a.js. It makes a dot every animation update, whether there is a mouse motion or not.

There is a trick in the code: because the web browser doesn’t allow us to ask where the mouse is, we can only find its location when we get an event. Therefore, we have to keep track of the mouse position (these are the mouseX and mouseY variables). When we get a mouse event, we update the position. When the mouse leaves the canvas (the onmouseleave event), we store a value in the position that lets us know the mouse is outside the canvas (so we don’t make dots outside). A canvas only gets onmousemove events when the mouse is inside of it.

Make sure you understand that example (including transparency). Then check out a different version (try clicking and holding down the mouse button):

Be sure to understand how this works - including how we know if the mouse is pressed. The code is in 02-06-05b.js. Notice how each rectangle not only stores its position, but also its velocity. And also notice that rectangles do get removed (when they get to the edge) - we don’t just keep making more.

You will be required to use all of these ideas on the next page.

Summary: Interaction and Animation with Canvas

There are a few big ideas on this page:

  1. We make display lists to represent graphics objects. If the underlying API does not do it for us, we have to manage it ourselves.
  2. The web browser likes to work in an event-driven model. If we want to do things in a polling (animation-loop) style, we have to adapt.

These are concepts we’ll use a lot as we make more complicated graphics programs.

Make sure you understand the code! It not only shows the concepts, but it also uses a lot of JavaScript idioms.

On the next page, we’ll let you try doing some of this stuff on your own.

Page Rubric

    This page (6) has no points.