0

I had 3 days looking for moving on objects on a spiral way but I figured that I have to look for the small part of problem which is moving objects on arc. See my Question :https://stackoverflow.com/questions/36917560/moving-rectangle-spiral-animation-java?noredirect=1#comment61428911_36917560

Now, the problem is how i can calculate the points that are exists on the arc. this is my Approach to get the new X and Y Points( Algorithm not code )

1- Draw arc using this method in JAVA g2d.fillArc(start_point_X_Arc,start_point_Y_Arc,width_of_arc,height_of_arc,start_angle,end_angle);

2- Draw the Object on the Same Start_point_X,Start_point_Y. And here I will draw a rectangle using this method

g2d.drawRect(start_point_X_Rect, Start_point_Y_Rect, 10, 10);

3- Because I'm using a timer and it needs an ActionListener the actionPerformed method will update the Values of Start_point_X, Start_point_Y for the rectangle

AND HERE IS THE PROBLEM I can't calculate the values of the New X,Y values for the object which will do the moving part of the problem ( I know that these word are not professional words ).

Because of that I search how to calculate points on arc and I find the Parametric Equations for a circle

x = center_X + radius * cos(angle)

y = center_y + radius * sin(angle)

and I know that these equation might be used in someway to get the new points but i'm not good in math.

Therefore,I need help with doing the object moving in arc path and i think this would help me to do an object moving in spiral path. If my algorithm is wrong or anything is wrong please give me advice to do it in a simple way.

This is a code that I made it to draw an arc & rectangle and the rectangle is moving in diagonal path.

  import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public class SpiralPath extends Path2D.Double {

        public SpiralPath(int size) {
            int numIterations = 5;
            int arcGrowDelta = (size / numIterations) / 2;
            int arcWidth = 0;

            int centerX = size / 2;
            int centerY = size / 2;
            moveTo(centerX, centerY);

            for (int i = 0; i < numIterations; i++) {
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
                arcWidth += arcGrowDelta;
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
            }
        }

    }

    public class SpiralPath2 extends Path2D.Double {

        public SpiralPath2(int size) {
            int numIterations = 5;
            int arcGrowDelta = (size / numIterations) / 2;
            int arcWidth = 0;

            int centerX = size / 2+200;
            int centerY = size / 2;
            moveTo(centerX, centerY);

            for (int i = 0; i < numIterations; i++) {
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
                arcWidth += arcGrowDelta;
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
            }
        }

    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private SpiralPath spiralPath;
        private final Rectangle box;

        private List<Point2D> points;
        private double angle;
        private Point2D pos;
        private int index;

        private SpiralPath2 spiralPath2;
        private final Rectangle box2;

        private List<Point2D> points2;
        private double angle2;
        private Point2D pos2;
        private int index2;

        protected static final double PLAY_TIME = 5000; // 5 seconds...

        private Long startTime;

        public TestPane() {
            spiralPath = new SpiralPath(150);
            box = new Rectangle(0, 0, 10, 10);

            points = new ArrayList<>(25);
            PathIterator pi = spiralPath.getPathIterator(null, 0.01);
            while (!pi.isDone()) {
                double[] coords = new double[6];
                switch (pi.currentSegment(coords)) {
                    case PathIterator.SEG_MOVETO:
                    case PathIterator.SEG_LINETO:
                        points.add(new Point2D.Double(coords[0], coords[1]));
                        break;
                }
                pi.next();
            }

            spiralPath2 = new SpiralPath2(200);
            box2 = new Rectangle(0, 0, 10, 10);

            points2 = new ArrayList<>(25);
            PathIterator pi2 = spiralPath2.getPathIterator(null, 0.01);
            while (!pi2.isDone()) {
                double[] coords = new double[6];
                switch (pi2.currentSegment(coords)) {
                    case PathIterator.SEG_MOVETO:
                    case PathIterator.SEG_LINETO:
                        points2.add(new Point2D.Double(coords[0], coords[1]));
                        break;
                }
                pi2.next();
            }

            pos = points.get(0);
            pos2 = points2.get(0);
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {

                    if (startTime == null) {
                        startTime = System.currentTimeMillis();
                    }
                    long playTime = System.currentTimeMillis() - startTime;
                    double progress = playTime / PLAY_TIME;
                    if (progress >= 1.0) {
                        progress = 1d;
                        ((Timer) e.getSource()).stop();
                    }

                    int index = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);
                    int index2 = Math.min(Math.max(0, (int) (points2.size() * progress)), points2.size() - 1);

                    pos = points.get(index);
                    pos2 = points2.get(index2);
                    if (index < points.size() - 1) {
                        angle = angleTo(pos, points.get(index + 1));
                    }

                     if (index2 < points2.size() - 1) {
                        angle2 = angleTo(pos2, points2.get(index + 1));
                    }
                    repaint();
                }
            });

            timer.start();
        }

        protected double angleTo(Point2D from, Point2D to) {
            double angle = Math.atan2(to.getY() - from.getY(), to.getX() - from.getX());
            return angle;
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 400);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            applyQualityRenderingHints(g2d);

            g2d.translate(20, 50);
            g2d.draw(spiralPath);
            g2d.draw(spiralPath2);
            AffineTransform at = new AffineTransform();
             AffineTransform at2 = new AffineTransform();

            if (pos != null &&pos2!=null) {

                Rectangle bounds = box.getBounds();
                at.rotate(angle, (bounds.width / 2), (bounds.width / 2));

                Path2D player = new Path2D.Double(box, at);

                g2d.translate(pos.getX() - (bounds.width / 2), pos.getY() - (bounds.height / 2));
                g2d.setColor(Color.RED);
                g2d.draw(player);



            }

            Rectangle bounds2 = box2.getBounds();
                at2.rotate(angle2, (bounds2.width / 2), (bounds2.width / 2));
                Path2D player2 = new Path2D.Double(box2, at2);

                g2d.translate(pos2.getX() - (bounds2.width / 2)+50, pos2.getY() - (bounds2.height / 2));
                g2d.setColor(Color.RED);
                g2d.draw(player2);
            g2d.dispose();
        }

    }

    public static void applyQualityRenderingHints(Graphics2D g2d) {

        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

    }

}
Community
  • 1
  • 1
Dodo Dodo
  • 43
  • 2
  • 10
  • One possibility is to "cheat" a little (or more to the point, use the available APIs), if you can generate a `Path` of the spiral, you can then use a `PathIterator` to find all the points along that path, which you can then use as a bases for your animation/movement, [for example](http://stackoverflow.com/questions/32392095/how-to-rotate-a-rectangle-after-reaching-specified-position/32397121#32397121) – MadProgrammer Apr 29 '16 at 22:04
  • @MadProgrammer If you check my Comment on your Solution & see my Updated code above – Dodo Dodo Apr 29 '16 at 23:50

2 Answers2

6

So based on this idea, you can take advantage of the functionality already available in the 2D Graphics API.

The difficult part would be to get your spiral shape setup as a Path object, lucky for us, the API is very flexible ...

public class SpiralPath extends Path2D.Double {

    public SpiralPath(int size) {
        int numIterations = 5;
        int arcGrowDelta = (size / numIterations) / 2;
        int arcWidth = 0;

        int centerX = size / 2;
        int centerY = size / 2;
        moveTo(centerX, centerY);

        for (int i = 0; i < numIterations; i++) {
            append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
            arcWidth += arcGrowDelta;
            append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
        }
    }

}

Sprial

Now we have that, the rest is (relatively) simple, as it follows a well know pattern...

Sprial

package javaapplication1.pkg005;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public class SpiralPath extends Path2D.Double {

        public SpiralPath(int size) {
            int numIterations = 5;
            int arcGrowDelta = (size / numIterations) / 2;
            int arcWidth = 0;

            int centerX = size / 2;
            int centerY = size / 2;
            moveTo(centerX, centerY);

            for (int i = 0; i < numIterations; i++) {
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth, 2 * arcWidth, 180, 180, Arc2D.OPEN), true);
                arcWidth += arcGrowDelta;
                append(new Arc2D.Double(centerX - arcWidth, centerY - arcWidth, 2 * arcWidth - arcGrowDelta, 2 * arcWidth, 0, 180, Arc2D.OPEN), true);
            }
        }

    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private SpiralPath spiralPath;
        private final Rectangle box;

        private List<Point2D> points;
        private double angle;
        private Point2D pos;
        private int index;

        protected static final double PLAY_TIME = 5000; // 5 seconds...

        private Long startTime;

        public TestPane() {
            spiralPath = new SpiralPath(150);
            box = new Rectangle(0, 0, 10, 10);

            points = new ArrayList<>(25);
            PathIterator pi = spiralPath.getPathIterator(null, 0.01);
            while (!pi.isDone()) {
                double[] coords = new double[6];
                switch (pi.currentSegment(coords)) {
                    case PathIterator.SEG_MOVETO:
                    case PathIterator.SEG_LINETO:
                        points.add(new Point2D.Double(coords[0], coords[1]));
                        break;
                }
                pi.next();
            }

            pos = points.get(0);
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {

                    if (startTime == null) {
                        startTime = System.currentTimeMillis();
                    }
                    long playTime = System.currentTimeMillis() - startTime;
                    double progress = playTime / PLAY_TIME;
                    if (progress >= 1.0) {
                        progress = 1d;
                        ((Timer) e.getSource()).stop();
                    }

                    int index = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);

                    pos = points.get(index);
                    if (index < points.size() - 1) {
                        angle = angleTo(pos, points.get(index + 1));
                    }
                    repaint();
                }
            });

            timer.start();
        }

        protected double angleTo(Point2D from, Point2D to) {
            double angle = Math.atan2(to.getY() - from.getY(), to.getX() - from.getX());
            return angle;
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            applyQualityRenderingHints(g2d);
            int x = (getWidth() - spiralPath.getBounds().width) / 2;
            int y = (getHeight() - spiralPath.getBounds().height) / 2;
            g2d.translate(x, y);
            g2d.draw(spiralPath);
            AffineTransform at = new AffineTransform();

            if (pos != null) {

                Rectangle bounds = box.getBounds();
                at.rotate(angle, (bounds.width / 2), (bounds.width / 2));

                Path2D player = new Path2D.Double(box, at);

                g2d.translate(pos.getX() - (bounds.width / 2), pos.getY() - (bounds.height / 2));
                g2d.setColor(Color.RED);
                g2d.draw(player);

            }
            g2d.dispose();
        }

    }

    public static void applyQualityRenderingHints(Graphics2D g2d) {

        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

    }

}
Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Can anybody explain the downvote? Does the answer not answer the ops question? Am I not suppose to help people? – MadProgrammer Apr 29 '16 at 23:07
  • I don't know who did this. Sir, i have a question. If i need to draw a 2 different spirals located beside each other & these 2 spirals having a rectangles moving with different speeds along the spiral path of each one of them. I tried to duplicate elements in your code but i am getting a translated and shifted spiral. However, the 2 rectangles are moving in same speed and same orientation. I'll Be great full to you if you looked at my updated Code Above. – Dodo Dodo Apr 29 '16 at 23:48
  • The position of the spiral and rectangle are governed by g2d.translate – MadProgrammer Apr 30 '16 at 00:05
  • I solved the Position problem with using this method. AffineTransform oldXForm = g2d.getTransform(); if you can help me withset different speed for each one please ? – Dodo Dodo Apr 30 '16 at 00:12
  • @MadProgrammer The tricky part (that I also referred to in [this comment](http://stackoverflow.com/questions/36917560/moving-rectangle-spiral-animation-java?noredirect=1#comment61407316_36917560) is IMHO the constant speed. In the current solution, the speed will increase along the spiral (lower curvature -> fewer points). If there was a straight `lineTo` in the path, this would be more obvious, as a sudden jump. Avoiding this is tricky (and the input could be of a form that makes a constant, predefined speed impossible). Limiting it so a certain *maximum* speed is a bit fiddly, but doable... – Marco13 Apr 30 '16 at 00:21
  • ... and storing the points as "movement steps" (ignoring `SEG_MOVETO`) makes it far easier than computing the movement steps "on the fly". (I tried the latter [in this class yesterday](https://github.com/javagl/Geom/blob/master/src/main/java/de/javagl/geom/DeltaPathIterator.java), but am not really satisfied with it). In any case, if one knew that it should *always* be a spiral (and not an arbitrary path), the movement could be applied directly, without these `Arc`/`Path` workarounds... – Marco13 Apr 30 '16 at 00:21
  • @Marco13 Given the complexity of the overall problem, using `Path` is a simpler solution as generally it's appling the same mathematical principles you would need to use anyway, just without the need calculate them yourself (at least from my perspective). I've taken a slightly different approach in that the object moves about the path over a given period of time, obviously as points begin bunch, there would be more opportunity for the object to appear to change speed, but it would still take the same amount of time to move from the start of the path to the end – MadProgrammer Apr 30 '16 at 00:27
  • @DodoDodo You need to devise a mechanism which can get the "point" of the object along the path at a given point in time. This would suggest that you need two durations and a `Timer` implementation which could track the progress of those two "time lines" – MadProgrammer Apr 30 '16 at 00:28
  • @DodoDodo This is a complex problem with no "simple" answer, each answer will require a certain amount of complexity to solve it. You need to first understand the solution as it stands, how each element ties into each other and then figure out how you would compartimislise core concepts in such away that you could then replicate them in isolation. My "idea" would be to generate some kind of "time line" which, given "time" value could calculate the `Point` value for the object. This would allow you to have `n` time lines of variable durations, all which could be used to generate the points – MadProgrammer Apr 30 '16 at 00:35
  • @Marco13 Back in the day, I did a lot of work in calculating the "line" segments for lines and curves, it was very interesting work, which frankly, I'd prefer not to have to replicate, so I made use of the available APIs which provide a reasonable (IMHO) solution. Personally, I'm focusing on a time driven solution (ie the object needs to move from a-b over t) instead of trying to smooth out the path so that the object seems to move at a constant speed, that's just my take on the solution – MadProgrammer Apr 30 '16 at 00:39
  • @MadProgrammer Sure, no offense. One could go arbitrarily far here (and looking at what a flattening `PathIterator` does for flattening the curves is intimidating). The solution of creating a list of points and iterating over these has likely the best effort-to-quality ratio (when keeping in mind that a `moveTo` or `lineTo` in the path may have odd effects). – Marco13 Apr 30 '16 at 00:55
  • [`WarpImage`](http://stackoverflow.com/a/9238247/230513) takes a similar approach. – trashgod Apr 30 '16 at 01:55
  • @MadProgrammer thanks a lot bro for the tips. i changed this PathIterator pi = spiralPath.getPathIterator(null, 0.01); to PathIterator pi = spiralPath.getPathIterator(null, 0.07); and it make them seems with different speed – Dodo Dodo Apr 30 '16 at 08:16
0

Try a little program (with no timers no nothing just this plane):

 protected void paintComponent(Graphics g) {
    super.paintComponent(g); 
    Graphics2D g2d = (Graphics2D)g;
    g2d.setColor(Color.BLACK);
    g2d.drawArc(200,200,200,200,0,90);
    g2d.setColor(Color.magenta);
    for(double t=0; t<Math.PI/2; t+=Math.PI/100) {
      int x = 300 + (int)(100 * Math.cos(t));
      int y = 300 + (int)(100 * Math.sin(t));
      g.fillOval(x, y , 5 , 5);
    }
}

If your arc is 200 wide and high and the arc goes from 0 to 90 (from the right x axis) this should draw the points on the arc.

I think you can generalize this to whatever center you have what width/height etc.

You can also change the angle

int y = 300 + (int)(100 * Math.sin(-t));

if you want to draw backwards.

gpasch
  • 2,672
  • 3
  • 10
  • 12
  • Okay I will generalize it but Actually I've ran your code but it enters with an infinite loop and just open the white frame and stop. Please check it – Dodo Dodo Apr 29 '16 at 21:21
  • Sir, Just re-define your t with double t instead of int t and the code will work fine. I will try to to make object moving around this points – Dodo Dodo Apr 29 '16 at 21:34