2

I'm writing a sprite like simulation where I need to move a label with an image in a circular path. I have 4 starting points; west, north, east and south. The label should move in a clockwise manner.

This is what I wrote and it works well, except for the fact that north and south positions circle counter-clockwise. The reason is that, I've used a separate trig formula to handle directions 2 and 4.

    int xstart = 450 
    int ystart = 325;
    int h = 0;
    double theta = -0.1047;

    for (int p = 0;; p++)
    {
        int xocir = xstart;
        int yocir = ystart;
        int pocir = 0; // perpendicular and base of outer circle
        int bocir = 0;

        if (direction == 1)//west - clockwise
        {
            pocir = (int) (Math.cos(theta) * (h + 300));
            bocir = (int) (Math.sin(theta) * (h + 300));
        }
        else if (direction == 2)//north - counter-clockwise
        {
            pocir = (int) (Math.sin(theta) * (h + 300));//reversed sin,cos
            bocir = (int) (Math.cos(theta) * (h + 300));
        }
        else if (direction == 3)//east - clockwise
        {
            pocir = (int) (Math.cos(theta) * (h - 300));
            bocir = (int) (Math.sin(theta) * (h - 300));
        }
        else if (direction == 4)//south - counter-clockwise
        {
            pocir = (int) (Math.sin(theta) * (h - 300));
            bocir = (int) (Math.cos(theta) * (h - 300));
        }

        xocir = xocir - pocir;
        yocir = yocir - bocir;
        theta = theta - 0.005;

        setBounds(xocir, yocir, 65, 65);
            }

Is there a more efficient way to tackle this? Maybe a simpler trig method. And what can I do to make sure all motion is clockwise?

mKorbel
  • 109,525
  • 20
  • 134
  • 319
Tru
  • 1,467
  • 5
  • 18
  • 27

2 Answers2

4

Just use one of the clockwise if statements instead of a different equation depending on direction. The start position is controlled by the initial value of theta and the parametric equations used.

Let
x = circleCenterX - (int) (Math.sin(theta) * radius),
y = circleCenterY - (int) (Math.cos(theta) * radius),
circleCenterX = 450,
circleCenterY = 325, and
radius = 300.

If theta = 0 deg, then
(x,y) = (450 - sin(0) * 300, 325 - cos(0) * 300)
      = (450, 25) which is north of (450, 325)

If theta = 90 deg, then
(x,y) = (450 - sin(90) * 300, 325 - cos(90) * 300)
      = (150, 325) which is west of (450, 325)

If theta = 180 deg, then
(x,y) = (450 - sin(180) * 300, 325 - cos(180) * 300)
      = (450, 625) which is south of (450, 325)

If theta = 270 deg, then
(x,y) = (450 - sin(270) * 300, 325 - cos(270) * 300)
      = (750, 325) which is east of (450, 325)

The following class encapsulates this idea:

import java.awt.Dimension;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class RotatingLabel extends JLabel {
    private static final long serialVersionUID = 474652899660954020L;
    private final int circleCenterX;
    private final int circleCenterY;
    private final int radius;
    private double theta;
    private final double thetaIncrement;

    public RotatingLabel(String text, //
            int circleCenterX, int circleCenterY, int radius, //
            double initialTheta, double thetaIncrement) {
        super(text);
        this.circleCenterX = circleCenterX;
        this.circleCenterY = circleCenterY;
        this.radius = radius;
        this.theta = initialTheta;
        this.thetaIncrement = thetaIncrement;
        rotate();
    }

    public void rotate() {
        setBounds( //
                this.circleCenterX - (int) (Math.sin(this.theta) * this.radius), //
                this.circleCenterY - (int) (Math.cos(this.theta) * this.radius), //
                (int) getPreferredSize().getWidth(), (int) getPreferredSize().getHeight());
        this.theta -= this.thetaIncrement;
    }

    public static void main(String[] args) {
        double initialTheta = Math.toRadians(0); // start north
        // double initialTheta = Math.toRadians(90); // start west
        // double initialTheta = Math.toRadians(180); // start south
        // double initialTheta = Math.toRadians(270); // start east

        final RotatingLabel label = new RotatingLabel("Foo Bar", //
                450, // circleCenterX
                325, // circleCenterY
                300, // radius
                initialTheta, //
                0.005); // thetaIncrement

        JFrame frame = new JFrame();
        frame.setSize(new Dimension(800, 700));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(label);
        frame.setVisible(true);

        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        label.rotate();
                    }
                });
            }
        }, 0, 1000l / 60l); // 60 frames per second
    }
}
creemama
  • 6,559
  • 3
  • 21
  • 26
  • I started with something similar. Doesn't this always make the starting position as west? Is there a tweak I can make to force it change the quadrant? – Tru Jun 03 '12 at 20:12
  • @Tru, yes, you are right. I updated my answer to explain the start position. – creemama Jun 03 '12 at 20:36
3

Again, just use one equation for all quadrants. For example this code shows 4 JLabels, one in each quadrant (each rotated Math.PI/2 from its neighbor), and the same equation is used to rotate all the labels:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

public class RotateLabels extends JPanel{
   private static final int PREF_W = 600;
   private static final int PREF_H = PREF_W;
   private static final int TIMER_DELAY = 30;
   protected static final double DELTA_THETA = Math.PI / 120;
   private JLabel[] labels = new JLabel[4];
   private double theta = -Math.PI / 4.0;

   public RotateLabels() {
      setLayout(null);
      for (int i = 0; i < labels.length; i++) {
         labels[i] = new JLabel("Label " + (i + 1));
         labels[i].setSize(labels[i].getPreferredSize());
         add(labels[i]);
      }
      setAllLabelLocations();
      new Timer(TIMER_DELAY, new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            theta += DELTA_THETA;
            setAllLabelLocations();
         }
      }).start();
   }

   private void setAllLabelLocations() {
      for (int i = 0; i < labels.length; i++) {
         setLabelLocation(labels[i], i);
      }
      repaint();
   }

   private void setLabelLocation(JLabel label, int i) {
      double radius = PREF_W / 3.0;
      int w = label.getWidth();
      int h = label.getHeight();

      double angle = theta + i * Math.PI / 2.0;
      double centerX = radius * Math.cos(angle);
      double centerY = radius * Math.sin(angle);

      int x = (int)(centerX - w / 2.0) + PREF_W / 2;
      int y = (int)(centerY - h / 2.0) + PREF_H / 2;
      label.setLocation(x, y);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private static void createAndShowGui() {
      JFrame frame = new JFrame("Rotate");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(new RotateLabels());
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Note that this code is not very efficient. If I were to create this "for real" I'd probably create a position look-up table so as to avoid all the floating point calculations that occur during the program's running.

Or more generally:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

@SuppressWarnings("serial")
public class RotateLabels2 extends JPanel{
   private static final int PREF_W = 600;
   private static final int PREF_H = PREF_W;
   private static final int TIMER_DELAY = 10;
   protected static final double DELTA_THETA = Math.PI / 420;
   private static final int LABEL_COUNT = 12;
   private JLabel[] labels = new JLabel[LABEL_COUNT];
   private double theta = -Math.PI / 4.0;

   public RotateLabels2() {
      setLayout(null);
      for (int i = 0; i < labels.length; i++) {
         labels[i] = new JLabel("Label " + (i + 1));
         labels[i].setSize(labels[i].getPreferredSize());
         add(labels[i]);
      }
      setAllLabelLocations();
      new Timer(TIMER_DELAY, new ActionListener() {

         @Override
         public void actionPerformed(ActionEvent arg0) {
            theta += DELTA_THETA;
            setAllLabelLocations();
         }
      }).start();
   }

   private void setAllLabelLocations() {
      for (int i = 0; i < labels.length; i++) {
         setLabelLocation(labels[i], i);
      }
      repaint();
   }

   private void setLabelLocation(JLabel label, int i) {
      double radius = PREF_W / 3.0;
      int w = label.getWidth();
      int h = label.getHeight();

      double angle = theta + i * 2 * Math.PI / labels.length;
      double centerX = radius * Math.cos(angle);
      double centerY = radius * Math.sin(angle);

      int x = (int)(centerX - w / 2.0) + PREF_W / 2;
      int y = (int)(centerY - h / 2.0) + PREF_H / 2;
      label.setLocation(x, y);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private static void createAndShowGui() {
      JFrame frame = new JFrame("Rotate");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(new RotateLabels2());
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373