Curriculum Graphics 1

From Real Software Documentation

Jump to: navigation, search

Aim
We are working up toward some fun stuff with sprites. Consistent with the development techniques we’ve discussed, we are going to start with one small part and test it. This part is a picture that can point in different directions.

This is also a great example of why you should have paid more attention during trigonometry classes in school…

We’re going to make a class hierarchy for generating and displaying a graphic that can point in different directions. Because of what we’re going to use it for later, we want to be able to pass it x- and y-coordinates, relative to the center of the object, and have it point toward those coordinates. This will involve some trigonometry. But don’t be scared; we’ll explain the trigonometry we use as we go.

Contents

Prereading

You should read about the Canvas and Graphics objects in the User’s Guide before you continue.

The Project

  1. Open the included project, DrawChevron, run it and see what it does.
  2. Examine the code underlying the DirectedChevron class, starting from the top of the class hierarchy:
  • DirectedPicture is an abstract class for reuse;
  • BufferedPicture is a somewhat abstract class that provides a framework for a basic way of approaching this problem: rather than generating an image pointing precisely at the intended point, we have a number (often 8 or 16, identified by the property NumPics) of pre-generated images, and we display the one that points closest to the coordinates we’re interested in.
  • DirectedChevron gives us some actual images for BufferedPicture to display.

Pixels

A picture on a computer screen must ultimately be reduced to a grid of colored squares. Each square is called a pixel (short for picture element).

Coordinates

Traditionally, a location in a 2-dimensional picture on a computer is measured from the top, left corner of the picture. A point is described with two numbers, representing the distance across and down of the point. These distances are usually referred to as the x and the y coordinates, respectively. As a shorthand, we will sometimes write the location of a point as (x, y). So the very top, left point would be (0, 0) and a point 100 units across and 50 units down would be written (100, 50)

The Trigonometry

Trigonometry means “the study of triangles.” Trigonometric functions relate the angles of a right-angled triangle to the lengths of the sides.

One thing you need to know about all of these functions in REAL Studio: angles are measured in radians, for various mathematical reasons we need not worry about now. There are 2 * Pi radians in a complete circle. This just means that where you might be tempted to use “360” (because you are thinking in degrees), you write “2 * Pi” and where you are tempted to use “180”, you write “Pi”. (You’ll also want to define a suitable Pi constant, as we have in this project).

DirectedChevron.GenerateFrame

The role of DirectedChevron is to supply enough pictures to make up a quarter revolution. The superclass will then flip these around to create enough pictures to make up a full revolution. We will place the points of the chevron around a circle that just fits inside ThePicture. We begin by calculating the radius of this circle. This is half the width of ThePicture:

The Chevron within ThePicture.

Next, we calculate how many radians around we are to rotate each image, compared to the last:

AngleInc radians further around for each image.

Next, we calculate where around the circle each point will lie. Each point will be represented by its x (horizontal) and y (vertical) distance from the top, left corner of ThePicture.

The front of the chevron is at AngleInc radians around from 12 O’Clock. We need a way to convert an angle into its x- and y-components.

Basic Trigonometry

Now we need to work out where the three tips of the chevron will be. For this, we need to be able to convert an angle and a distance into x and y coordinates.

Assume for the moment that the center of our chevron is at (0, 0). We want a point rotated p radians, and r pixels away from (0, 0). This lets us draw a triangle:

The task is to find a and b in this triangle.

Our starting point is the bottom point of the triangle, and the point we want to find is the right-hand point of the triangle. We can draw this point because going r pixels at an angle of p radians from the vertical is the same as going up b pixels, then right a pixels. Notice that going straight up and then going straight to the right means those two moves are at right angles. Two trigonometry functions, Sin and Cos let us find a and b:

  • The Sin of p tells us what a/r is. Since we know what r is, and we know what p is, it follows that a is r * Sin(p).
  • Similarly, the Cos of p is b/r, so b is r * Cos(p).

In general, these functions can be used in any triangle with a right angle in one corner. The side opposite the right angle is called the hypotenuse. The Sin of one of the angles that isn’t the right angle is the ratio of the side opposite that angle to the hypotenuse; the Cos of the angle is the ratio of the adjacent side to the hypotenuse. There is also a Tan function, which is the ratio of the opposite side to the adjacent side.

You can remember this with the acronym SOHCAHTOA (Sin: Opposite/Hypotenuse; Cos: Adjacent/Hypotenuse; Tan: Opposite/Adjacent).

Now, we want to center this not at (0, 0), but in the middle of the picture, at (r, r). So we add r to every x and y value. We also define a constant SideAngle, which is how far around from the top of the chevron to its opposite side that we place the rear points of the chevron. That gives us the following code to draw the chevron:

Sub GenerateFrame(n As Integer, g As Graphics)
//Generates a chevron pointed at the nth point out of UBoundPics + 1 total
//Pre: Picture width and height are identical
//0<=n<=UBoundPics
//UBoundPics >= 6, UBoundPics is even
//Post: p contains the chevron
Dim r As Integer
Dim AngleInc As Double
Dim Points(8) As Integer
Const PI=3.14159265358979323846264338327950
Const SideAngle = 0.8
//Proportion of half a revolution around for back of chevron
r = ThePicture.width/2
AngleInc = 2*Pi*(n-1)/NumPics //How far around we are to rotate
//Point of chevron
Points(1) = r + r * sin(AngleInc)
Points(2) = r + r * cos(AngleInc)
//Right side
Points(3) = r + r * sin(Pi*SideAngle + AngleInc)
Points(4) = r + r * cos(Pi*SideAngle + AngleInc)
//Center
Points(5) = r
Points(6) = r
//Left side
Points(7) = r + r * Sin(Pi*(-SideAngle) + AngleInc)
Points(8) = r + r * Cos(Pi*(-SideAngle) + AngleInc)
//Draw it
g.ForeColor = DarkBevelColor
g.ClearRect 0, 0 ,g.Width,g.Width
g.FillPolygon(Points)

As usual, you should look up any of the Real Studio commands you are not familiar with. Briefly, though, FillPolygon takes an array of integers, treats each pair of numbers as a point, and draws a polygon (straight-sided figure) between those points, and filled with whatever the current ForeColor is.

Which Image Points Here?

The other method for which we need some trigonometry is the one that works out which image to draw to make the image point at a particular location.

What we need for this is the inverse of the Tan function mentioned earlier: this time, we have the location, and we want to work out the angle. The part of the location divided by the x part of the coordinate is just the Tan of the angle, as we mentioned above.

There is an Atan function that calculates the angle from the ratio, but in fact, Real Studio has an easier function for us to use: Atan2 takes an x and a y value, and returns the angle from (0, 0) to that point. So we just need to subtract r from everything, to move it back to (0, 0), and ask for Atan2. Then we just round to the nearest angle our images point at (remember how we did:

AngleInc = 2*Pi*(n-1)/NumPics)

in GenerateFrame? We just need to work out how many of those are in our angle). This gives us this method:

Sub FaceToward(X As Integer, Y As Integer)
Dim NextPic As Integer
Const PI=3.14159265358979323846264338327950

NextPic = ((Round(atan2(X, Y)*NumPics/(2*Pi)+ NumPics) mod NumPics)) + 1
If NextPic <> CurrentPic Then
CurrentPic = NextPic
ThePicture.DrawPicture ThePics(CurrentPic), 0, 0
End If

You should look through the project now, run and trace it, and tinker with it until you can see how it works.

Further Exercises

There are many possible ways to extend this project, and you should try to come up with your own ideas. Some specific ideas might be:

  • Make the classic “pair of eyes watching the cursor” application, with circles for pupils. Bonus points for extending the application so it follows the pointer inside either of the eyes properly (meaning that if the pointer goes inside one of the eyes, the pupil will be centered under the pointer);
  • Draw a more complex shape (say, a tank or robot), rather than the chevron; or
  • Create a class that can start from two bitmap images (one pointing straight up; one pointing at 45°), and will then generate the full 8 images by rotating each of the two original images through three right angles.
Personal tools