I'm making a 2D game in Java where the player guides a polygon through obstacles. The polygon moves up and down and the game world scrolls left and right. I need the polygon to rotate around its center, but since it is constantly being translated the point it is being rotated around moves. Trying to translate it back to the original center, rotate it, and translate it back doesn't work. How do I get the center of the shape?
Here are my motion calculations on a 2ms timer:
@Override
public void actionPerformed(ActionEvent e) {
double theta = angleRad+90;
if (up == true) {
if (accelerating == false) {
time2 = 0;
moveX0 = moveX;
moveY0 = moveY;
accelerating = true;
}
time1++;
double t = time1/500;
if (accCount % 10 == 0) {
DronePilot.velocity++;
}
moveX = moveX0 + velX*Math.cos(theta)*t;
moveY = moveY0 + velY*Math.sin(theta)*t-(1/2d)*g*Math.pow(t, 2);
velX = (DronePilot.velocity)*Math.cos(theta);
velY = (DronePilot.velocity)*Math.sin(theta)-g*(t);
accCount++;
} else if (up == false){
if (accelerating == true) {
time1 = 0;
moveX0 = moveX;
moveY0 = moveY;
accelerating = false;
}
time2++;
double t = time2/500;
moveX = moveX0 + velX*Math.cos(theta)*t;
moveY = moveY0 + velY*Math.sin(theta)*t-(1/2d)*g*Math.pow(t, 2);
accCount = 0;
} if (left == true) {
angleCount++;
if (angleCount % 2 == 0) {
angleDeg++;
}
angleRad = Math.toRadians(angleDeg);
} else if (right == true) {
angleCount--;
if (angleCount % 2 == 0) {
angleDeg--;
}
angleRad = Math.toRadians(angleDeg);
}
repaint();
}
}
And here is my paintComponent method:
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D)g;
Graphics g2 = g.create();
Graphics2D copy = (Graphics2D)g2;
copy.rotate(-angleRad, xPos, yPos);
copy.translate(0, -moveY);
g2D.translate(-moveX, 0);
copy.draw(player.shape);
for (Rectangle2D.Double r: DronePilot.rocksFloorArray) {
g2D.draw(r);
}
for (Rectangle2D.Double r: DronePilot.rocksCeilArray) {
g2D.draw(r);
}
for (Rectangle2D.Double r: DronePilot.roomsArray) {
g2D.draw(r);
}
}
where (xPos, yPos) are the center of the screen.
Transformations are (normally) compounding
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D)g;
Graphics g2 = g.create();
Graphics2D copy = (Graphics2D)g2;
copy.rotate(-angleRad, xPos, yPos);
copy.translate(0, -moveY);
g2D.translate(-moveX, 0);
copy.draw(player.shape);
for (Rectangle2D.Double r: DronePilot.rocksFloorArray) {
g2D.draw(r);
}
for (Rectangle2D.Double r: DronePilot.rocksCeilArray) {
g2D.draw(r);
}
for (Rectangle2D.Double r: DronePilot.roomsArray) {
g2D.draw(r);
}
}
In your above code, you are translating both the original Graphics
context and the copy
. In this context, copy
won't be affected by the original and the original won't be affected by the copy
, BUT, the original context is a shared resource and since you don't reset the translation, you will continue to get a translated context each time (compounding).
As a general rule of thumb, do ALL transformations on a copy and dispose of it when you're done.
For example...
Graphics2D g2d = (Graphics2D)g.create();
AffineTransform at = AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y);
at.rotate(Math.toRadians(angle), player.getBounds2D().getCenterX(), player.getBounds2D().getCenterY());
g2d.setTransform(at);
g2d.setColor(Color.RED);
g2d.fill(player);
g2d.setColor(Color.BLACK);
g2d.draw(player);
g2d.dispose();
This basically translates the position of the object to the player's position and then rotates the object about the centre of the object
You could, also, apply one transformation, create a copy of that context and apply another transformation, which would become compounded (so you could translate
one context, copy it, and then rotate
the copy and the first translation would still applied to the copy)
This incredible simple example demonstrates two basic example...
- Using the
Graphics
context and a AffineTransform
to translate and rotate the player object (about it's centre point)
- Using a
Path2D
to generate a transformed shape (this example makes two objects, but you could use a single AffineTransform
translated and rotated and apply it once).
In both cases, they don't affect the original shape
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Shape player;
private Point playerPoint;
private float angle;
private float deltaZ = 1.0f;
private int deltaX, deltaY;
public TestPane() {
player = new Rectangle(0, 0, 20, 20);
playerPoint = new Point(80, 80);
Random rnd = new Random();
deltaX = 1;
deltaY = -1;
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
playerPoint.x += deltaX;
playerPoint.y += deltaY;
Shape rotatedPlayer = rotatedAndTranslatedPlayer();
Rectangle2D bounds = rotatedPlayer.getBounds2D();
if (bounds.getX() < 0.0) {
playerPoint.x = (int)(bounds.getX() * -1);
deltaX *= -1;
} else if (bounds.getX() + bounds.getWidth() >= getWidth()) {
playerPoint.x = getWidth() - (int)bounds.getWidth();
deltaX *= -1;
}
if (bounds.getY() < 0) {
playerPoint.y = 0;
deltaY *= -1;
} else if (bounds.getY() + bounds.getHeight() > getHeight()) {
playerPoint.y = getHeight() - (int)bounds.getHeight();
deltaY *= -1;
}
angle += deltaZ;
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Shape rotatedAndTranslatedPlayer() {
Path2D.Double rotated = new Path2D.Double(player, AffineTransform.getRotateInstance(
Math.toRadians(angle),
player.getBounds2D().getCenterX(),
player.getBounds2D().getCenterY()));
return new Path2D.Double(rotated, AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y));
}
// Simply paints the "area" that the player takes up when it's rotated and
// translated
protected void paintAutoTranslatedShape(Graphics2D g2d) {
g2d.setColor(Color.DARK_GRAY);
g2d.fill(rotatedAndTranslatedPlayer().getBounds2D());
}
// Uses a AffineTransform to translate and rotate the player
protected void paintPlayer(Graphics2D g2d) {
AffineTransform at = AffineTransform.getTranslateInstance(playerPoint.x, playerPoint.y);
at.rotate(Math.toRadians(angle), player.getBounds2D().getCenterX(), player.getBounds2D().getCenterY());
g2d.setTransform(at);
g2d.setColor(Color.RED);
g2d.fill(player);
g2d.setColor(Color.BLACK);
g2d.draw(player);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintAutoTranslatedShape(g2d);
g2d.dispose();
g2d = (Graphics2D) g.create();
paintPlayer(g2d);
g2d.dispose();
}
}
}
In openGL we save transformation state using pushMatrix()
and popMatrix()
. Here their equivalents are Graphics.create()
and Graphics.dispose()
Graphics g1 = g.create();
//do transformations
g1.dispose();
Graphics g2 = g.create();
//do other stuff
g2.dispose();