Wednesday, November 12, 2008

Graphics2D Rotate

Here's another Java hobbyist how-to post. I showed in my last post how to animate a little square by moving it around on a JComponent. In this post, I'm going to look into how to rotate the square to always point toward another square as it moves across the screen.

The basic function that I'll be calling is Graphics2D.rotate(double theta). This function causes "Subsequent rendering is rotated by the specified radians (theta) relative to the previous origin."

A radian is defined as the angle subtended at the center of a circle by an arc that is equal in length to the radius of the circle, or 180/PI degrees. A full circle is 2 PI radians. A half circle is PI radians. Therefore, if I wanted to turn 45 degrees (1/8 circle), I would call Graphics2D.rotate(Math.PI/4).

Now, if I want to rotate my square to point at another square, the easiest thing to do is to calculate the slope between the two squares (double slope = squareY2 - squareY1 / squareX2 - squareX1) and then convert the slope to an angle using Math.atan() function. I could go into more depth on atan, but I'll leave that for another time. UPDATE: the Math.atan2() function effectively combines the slope and atan calculations and deals with some messy edge cases like divide by zero. See below for an example of its use.

Here's the new code:


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;

/**
* A very simple panel that demonstrates
* how to create a Swing component that updates
* and repaints itself with a Timer.
*
* @author Keith Knudsen
*/
public class JSimpleAnimationComponent extends JComponent
implements ActionListener {

/**
* @param args
*/
public static void main(String[] args) {
JFrame frame = new JFrame("AnimationPanel");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(
new JSimpleAnimationComponent(),
BorderLayout.CENTER);
frame.setSize(200,200);
frame.setVisible(true);
}

public JSimpleAnimationComponent () {
Timer timer = new Timer(50, this);
timer.start();
}

/* position of our little moving square */
double squareX1 = 10;
double squareY1 = 10;

/* ... and our fixed square that we point to */
double squareX2 = 100;
double squareY2 = 50;

/**
* Called by the timer. Update the position of
* our little moving square and then call repaint.
*/
public void actionPerformed(ActionEvent e) {
squareX1++;
// keep the little square from running off the edge
if(squareX1 > this.getWidth()) {
squareX1 = 10;
}
repaint();
}

/*
* Paint the background and the little square
* at it's current position.
*/
public void paintComponent ( Graphics g )
{
// clean up the background from the previous draw
super.paintComponent(g);

// need this for rotate function
Graphics2D g2 = (Graphics2D) g;

// draw fixed square
g2.setColor(Color.BLUE);
g2.drawRect((int)squareX2, (int)squareY2, 10, 10);

// draw sprite
g2.translate(squareX1, squareY1);
g2.setColor(Color.RED);
// rotate square1 to point at square2
double radians = Math.atan2(
squareY2-squareY1, squareX2-squareX1);
g2.rotate(radians);

g2.drawRect(0, 0, 10, 10);
}
}

No comments: