Search code examples
javagraphicspaintcomponent

Rotating Java 2D Graphics Around Specified Point


I'm trying to write a program that will draw several shapes rotated around a center point. The result should be something like a Spirograph. I'm trying to test it using rectangles but I can only get two of them to appear in the window. One of them is where it should be, but then after the first rotation it throws the other square way up in the top left corner of the window. It should be rotated and drawn around the center point. Here is a portion of my code.

import java.awt.*;
import java.awt.geom.Ellipse2D;

import javax.swing.JPanel;

public class Shapes extends JPanel
{
private double angle;
private int type;
private int radius;
private int repeats;

public Shapes(int t, int r, int z)
{
   type = t;
   radius = r;
   repeats = z;      
   angle = 360 / repeats;
}

public void paintComponent( Graphics g )
{
  super.paintComponent( g );
  Graphics2D g2d = (Graphics2D)g;

  g.setColor(Color.red);

  int centerX = getWidth()/2;
  int centerY = getHeight()/2;

  double curAngle = 0;

  for(int i=0; i<=repeats; i+=1)
  {
      g.setColor(Color.blue);

      Rectangle tangle = new Rectangle(0, 0, radius, radius);
      g2d.rotate(Math.toRadians(curAngle));
      g2d.translate(centerX, centerY);
      g2d.draw(tangle);
      curAngle += angle;
  }

}


Solution

  • Remember, translate and rotate are compounding, that is, each time you apply them, they add to the current transformation.

    There are a number of ways you might be able to achieve this, but first, we need to make a copy of the Graphics context. It's kind of important any time you mess with the transform in particular, you do so with a copy of the Graphics context, as this will leave the original unaffected (and won't effect anything that might be painted after)

    Graphics2D g2d = (Graphics2D) g.create();
    //...
    g2d.dispose();
    

    So anything you do to g2d now, won't affect the state of g

    Now, we can use the "compounding" effect of a transform to our advantage

    Rectangle tangle = new Rectangle(0, 0, radius, radius);
    //g2d.translate(centerX, centerY);
    g2d.rotate(Math.toRadians(angle), centerX, centerY);
    g2d.draw(tangle);
    

    So, all this does is applies the value of angle to the copy of the Graphics context on each loop. The anchor point is set to the centerX and centerY points

    Spiral 01

    Okay, that looks okay, but what happens if I resize the window?

    Spiral 02

    Okay, not really what I was expecting. This occurs for a combination of reasons, the size of the shape, the anchor point, the rotation, the translation offset, these are all compounding to produce the effect (cool though).

    Okay, so, instead, we can simply create a new transform for each loop instead, supplying the exact parameters we want, for example...

    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setColor(Color.blue);
    
    Rectangle tangle = new Rectangle(0, 0, radius, radius);
    g2d.translate(centerX, centerY);
    g2d.rotate(Math.toRadians(curAngle));
    g2d.draw(tangle);
    curAngle += angle;
    g2d.dispose();
    

    This now produces...

    Spiral 03

    While playing around with the code, I noticed that as I increased the number of repeats, I wasn't getting the desired results.

    After same digging, I changed

    angle = 360 / repeats;
    

    to

    angle = 360.d / repeats;
    

    which stopped the integer division issue (and truncating of the decimal values) and started to produce the desired results

    A simple before and after comparison...

    BeforeAfter