COMP 310

Lab04: Transforming Shapes 
Ball
class, all balls will be affected as your work on your affine
transformbased painting.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.
We learned about the concept of Affine Transformations in lecture. Today, we are going to use that knowledge to actually transform and draw shapes.
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); // This is NOT a unitsized shape and is NOT centered at the origin! g.setColor(Color.BLUE); ((Graphics2D) g).fill(ellipse); }
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:
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(); // Note that the AffineTransform object can be reused because it is being reset each time. at.setToTranslation(6.0, 12.0); // This is just an arbitrary location on the screen so you can see the transformed shape somewhere.
One can then apply this transformation to a shape:
Shape newShape = at.createTransformedShape(originalShape); // Now you can use the fill() method of the graphics object to paint the newShape
Remember that the affine transform translation will simply move the shape by the specified amount; it does not place the image at a specific xy location. This will be apparent if the original shape is not situated at the origin.
(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.)
These steps are a summary of the instructions detailed in the subsequent sections.
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 inheritancebased 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:
Add another ball class called EllipseBall that is a subclass of ABall.
Your EllipseBall will need to contain both an AffineTransform and a Shape, which you should intialize as an Ellipse2D.Double.
Our ellipses are just going to travel straight, so you can have an empty updateState method.
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!
Start simple! First just follow the instructions above to prove that you can make a simple, nonmoving shape appear on the screen and that you can then apply an affine transform to that shape and get the transformed shape to appear on the screen.
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.
To be used as a prototype, the Ellipse2D.Double
shape
needs to
be of unit size and centered on the origin. Careful, remember that the Java library defines an ellipse by upper
lefthand corner and full width and height!
Once your prototype is properly created, you can proceed to set the affine transform to match the current characteristics of your ball and thus enable the image to be painted in a manner that reflects the what one expects to see about the ball.
To test your translating, set your prototype to be centered on the origin, but NOT of unit size. Make it a size that can be easily seen.
Change the values of your affine transform's translation so your prototype will be transformed to be a shape on top of the ball's current location. Your new ball should now move and bounce on the screen though it may look a little odd since the painted image's size does not yet match the ball's current size.
Get translations working before proceeding on to scaling and rotating.
To test scaling, be sure that the prototype is now set to be unit size.
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.
Test scaling by
scale()
method of the AffineTransform
to
transform the prototype to be the same as the current ball size.Get scaling working before proceeding on to rotating.
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.
Recommended: 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! (Read: Put the center of your prototype shape at the origin!)
In order to see if your rotations are working, you will need to use something like an ellipse for your prototype where the major and minor axes are not equal.
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 welldefined 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 hardcode the initialization of the image file into the constructor of your ball.
© 2019 by Stephen Wong