Subscribe to Martyr2's Programming Underground        RSS Feed
-----

Transforming Shapes With Java AffineTranform

Icon Leave Comment
You, a retro gamer, are on a new quest to bring back a classic arcade hit to a whole new generation of space faring digital cosmonauts. But it is dangerous out there and you need to show these rookies the dangers of journeying their ships alone in the vastness of space. So you want to create your own game of Asteroids. For those unfamiliar with this game, and I can't imagine there are many of you out there, it is a game where you are a ship tasked with destroying and avoiding asteroids as they are hurled at you. These "asteroids" back in the day were nothing more than polygon line drawings that resembled rocks. Then when you shot them they would break apart into smaller rocks or simply disintegrate.

We are not going to cover the whole game's design, but we can show you one way to create twirling polygons of destruction on a JFrame using a special JPanel. This JPanel will implement its own paintComponent method and use the AffineTransform class to cause its rotation. AffineTransform, as well as other transformations, can get terribly complex involving a lot of geometry and matrices to create all sorts of rotations, skewing, scaling and more in either 2D or 3D space. For our example we are going to cover three small simple examples of rotating, scaling and translating.

The shape transform panel

First we need to create a polygon using a series of x,y coordinate pairs. Our example below creates a shape that has 7 points (heptagon/septagon). We will draw them on a JPanel which we will add to your standard JFrame. To help show the examples, we are going to use a JSlider control which we will anchor to the bottom of the form and set to go from 0 to 360 (Makes it easy for the rotation demo). As the user moves the slider back and forth they can see the shape do things in real time.

We will start by showing you our custom panel. It is extended from the typical JPanel and we have overridden the paintComponent method to call our draw function. The draw function has three transformation lines in it that will alter our shape.

// Specialized panel which will draw our shape
class ShapeTransformPanel extends JPanel {
    private int[] p1x = {150, 210, 280, 210, 120, 110, 100};
    private int[] p1y = {20, 60, 120, 200, 240, 200, 100};
    private double modifyValue = 0;

    public ShapeTransformPanel() { }
	
	
    // Sets our modification value
    public void modify(int value) {
        modifyValue = value;
    }
	
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
		
        drawTransform(g, modifyValue);
    }
	
    // Create our polygon shape with the points, transform it and then draw it to the panel.
    private void drawTransform(Graphics g, double modifier) {
        Polygon examplePolygon = new Polygon(p1x, p1y, p1x.length);
		
        Rectangle rect = examplePolygon.getBounds();
		
        // Create our transform and modify it based on the line we use.
        // Uncomment the lines below to see their effect
        AffineTransform at = new AffineTransform();
		
        at.rotate(Math.toRadians(modifier), rect.getX() + rect.width/2, rect.getY() + rect.height/2);
        //at.scale(modifier / 100.0, modifier/ 100.0);
        //at.translate(modifier, modifier);
		
        // Transform the shape and draw it to screen
        Graphics2D g2d = (Graphics2D) g;
        g2d.draw(at.createTransformedShape(examplePolygon));
    }
}



As you can see from our class above, the drawTransform method is where all the magic happens. It draws our polygon, gets its bounding box (that imaginary box that encompasses our shape) and then sets up our AffineTransform object. There are three lines here, the first one rotates the shape, the second one scales the shape (changes its size) and the third translates the shape (aka moves it along its x/y axis). The AffineTransform class can do many more things including shearing the shape as well as translating coordinates using a transform... returning it as a series of modified points that you can then draw later. Uncomment/comment the lines one by one to see them in action. Then you can try combinations of them to see additional effects.

Once we have our transform set up, we use the AffineTransform to transform our shape. This will return a new shape that we can then give to our 2D drawing context to draw it. Think of this context as a canvas for the JPanel which we can draw on. When we use g2d.draw() we are saying to draw on the "canvas" of the ShapeTransformPanel panel.

Testing program with the JSlider

Here is our main class that we use to set up the frame, create our new panel and add it into the frame. We also attach the JSlider control to the bottom (using the BorderLayout manager) and set it up to go from 0 to 360. As the user slides the JSlider, it calls a change listener, gets its current value and feeds that to the panel we created. It then tells the panel to repaint itself to show the changes.

import java.awt.*;
import java.awt.geom.AffineTransform;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class TransformShapeDemo extends JFrame {
    private JSlider modificationSlider;
    private ShapeTransformPanel panel = new ShapeTransformPanel();
	
    public TransformShapeDemo() {	
        // Setup our JFrame details
        setTitle("Transform Polygon Example");
        setSize(500,500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
		
        // Create a JSlider to rotate our shape with
        modificationSlider = new JSlider(0, 360, 0);
        modificationSlider.addChangeListener(new ChangeListener() {
			
                // For each tick of the thumb on the slider, get the value
                // rotate the image by that many degrees and ask it to repaint itself.
                public void stateChanged(ChangeEvent e) {
                        int modifyValue = ((JSlider)e.getSource()).getValue() % 360;
                        panel.modify(modifyValue);
                        panel.repaint();
                }
        });
		
        // Add our panel to the center and the slider to the bottom.
        add(panel, BorderLayout.CENTER);
        add(modificationSlider, BorderLayout.SOUTH);
    }

    // Create our demo and show it.
    public static void main(String[] args) {
        TransformShapeDemo demo = new TransformShapeDemo();
        demo.setVisible(true);
    }
}



I won't go into too much detail about this part of the program. I will say that we create our new shape panel, add it to the center of the JFrame's layout, create the slider and bolt it to the bottom and let the user see our wicked creation in action! The AffineTransform class can seem a bit complex when you dive into all the math involved with it, but after some basic experimentation you can quickly put together some great animation style effects with it.

You are well on your way

So now that you see how we can alter shapes, it is just a matter of time before you can create twisting and hurling polygons of death in your game. By using the rotation method we can create the spinning and combine that with the translating we can put a rotating polygon on its own trajectory. All that is left for you to do is a little bit of collision detection and you will have a game that all the youngins will enjoy! Even if they don't like it you can still relive your own youth.

I hope you enjoyed our journey through space and time with our simple Asteroid trip down memory lane. If you are interested in more ideas for games you can purchase our ebook which contains games, as well as a multitude of application, project ideas (200 of them to be exact). Thanks for reading! :)

0 Comments On This Entry

 

October 2014

S M T W T F S
   1234
567891011
12131415161718
1920 21 22232425
262728293031