COMP 310
Fall 2014

Lab04:  Transforming Shapes

Home  Info  Owlspace   Java Resources  Eclipse Resources  Piazza

Since HW03 does not require strategy-based painting, the best option to do this lab is to either branch your current HW03 if it is essentially operational, or branch your HW02 into a new Lab04 project.     

When you get to HW04, you can simply cut and paste the relevant code from Lab04 into it.  There shouldn't be much code to transfer.

 

Affine Transformations

We learned about the concept of Affine Transformations in lecture.  Today, we are going to use that knowledge to actually transform and draw shapes.

Shape

In homework 1, we created a shape class to allow us to draw shapes on the screen.  This is already a part of Java.  There exists a Shape interface.  There are a few concrete classes that implement the interface, such as Ellipse2D.Double and Rectangle2D.Double.  You can also create an arbitrary shape with a list of points, called a Polygon.

Shapes can be passed to the fill method of a Graphics2D object, which the Graphics object the paintComponent method receives luckily happens to be, and they will be drawn appropriately:

	void paint(Graphics g) {
		Shape ellipse = new Ellipse2D.Double(42, 42, 20.0, 10.0);
		g.setColor(Color.BLUE);
		((Graphics2D) g).fill(ellipse);
	}

AffineTransform

Java also includes an AffineTransform class that can be used to transform shapes.  You can create AffineTransform objects and then set the translation, scale, rotation, and shear to create a transform.  You can then apply the transform to a Shape to create a new Shape.

There are two important things to understand about the AffineTransform class:

  1. The transformations are applied in the reverse order that you add them to the transform.  In other words the last transformation you add will be performed first!
  2. Each type of transformation can be added in two ways, on which begins in "setTo", i.e., "setToTranslation" or "translate".  The "setTo" methods reset the AffineTransform to just have that transformation.  So, you can reuse an AffineTransform object easily by first calling one of the "setTo" methods to install the last transform, then add additional transforms that will be performed earlier.

The following is an example of creating a simple transform that will translate an object by 6 units in the x direction and 12 units in the y direction:

AffineTransform at = new AffineTransform();

at.setToTranslation(6.0, 12.0);

One can then apply this transformation to a shape:

newShape = at.createTransformedShape(originalShape);

Rotation

Given the velocity of our "ball", we need to figure out what angle to rotate our Shape so that it is pointing in the direction that the "ball" is travelling.  Effectively, you have to determine the angle of the velocity vector.  In order to do this, you need to figure out the arctangent of the vector coordinates.  However, the answer is complicated by the fact that the vector could be in one of the four quadrants.  This turns out to be a common operation, so conveniently, Java provides a function for it: Math.atan2(y, x).  The atan2 function returns an angle in radians by properly accounting for the sign of both x and y when calculating the arctangent.  With this rotation angle in hand, the AffineTransform can be set to include a rotation in its transformation.

Note:  AffineTransform has an override of is rotate method, rotate(double vecx, double vecy), that is the equivalent of calling rotate(Math.atan2(vecy,vecx)).    <== Careful, watch the order of the input parameters!

When dealing with rotations, always remember that a rotation is defined about the origin of the coordinate system, not the center of the shape!

Scaling

Scaling will expand or contract the size of an object relative to the origin of the coordinate system.   For instance, if an object is located a distance away from the origin, a scaling operation greater than one will in cause the center of the object to move farther away from the origin as well as increase its size.

 

Adding Shapes to Ballworld

(The following assumes you are working on a branch of HW02.   If you are working on a HW03 branch, then the changes will be made to the Ball class or a subclass of Ball if you desire.)

In order to better understand affine transformations and drawing shapes, we are going to add an "EllipseBall" to your Ballworld program.  You can start with any working version of your inheritance-based Ballworld program, but we recommend just continuing to work on the branch you created for last week's lab.  Then you can do the following:

  1. Add another ball class called EllipseBall that is a subclass of ABall.

  2. Your EllipseBall will need to contain both an AffineTransform and a Shape, which you should intialize as an Ellipse2D.Double.

  3. Our ellipses are just going to travel straight, so you can have an empty updateState method.

  4. You will need to override the paint method in order to properly transform and paint your ellipse.  Make sure your ellipse bounces off the walls correctly and always faces in the direction it is travelling.   Think very hard and carefully about what transforms (rotate, translate, scale) you want to perform and in what order you want them applied.   Remember that the AffineTransform adds transformations in reverse order!

When complete, everything should work as before and you should be able to create an EllipseBall that travels straight.  In the next homework, we will use these concepts to enable you to create other shapes that can be combined with your update strategies.

Question:

How big do you make the ellipse so that it appears to bounce as naturally as possible from the walls?   Try a number of widely varying aspect ratios (width vs. height) for the ellipse and try to come up with well-defined formulat for the "radius" of the "ball" that bounces most naturally for a given aspect ratio.   Drawing pictures of the bouncing ellipse will help a lot here.

If you get this far, continue in lab by trying to get an image to paint.   See Lec 12.   Since you don't have paint strategies yet, just hard-code the initialization of the image file into the constructor of your ball.

 

 


© 2014 by Stephen Wong