1

I'm trying to let an image follow a path. The points of this path are stored in an ArrayList. Right now the image jumps to the next point every two seconds, so I have to use linear interpolation to make the movement smooth. But how can I use linear interpolation in my update() method? I have a searched for this question on the net but couldn't find much information on linear interpolation in the update method in combination with an ArrayList with points.

Update method

public void update(){

    repaint();
    if(counter < Lane.firstLane.size()){

        startPoint = new Point(carPosition.x, carPosition.y);
        endPoint = new Point(Lane.firstLane.get(counter).x, Lane.firstLane.get(counter).y);
        pointOnTimeLine = new Point(startPoint);
        Timer timer = new Timer(40, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (startTime == null) {
                    startTime = System.currentTimeMillis();
                }
                long now = System.currentTimeMillis();
                long diff = now - startTime;
                if (diff >= playTime) {
                    diff = playTime;
                    ((Timer) e.getSource()).stop();
                }
                double i = (double) diff / (double) playTime;
                pointInTime = i;

                //pointOnTimeLine.x = (int) (startPoint.x + ((endPoint.x - startPoint.x) * i));
                //pointOnTimeLine.y = (int) (startPoint.y + ((endPoint.y - startPoint.y) * i));

                //carPosition.setLocation(pointOnTimeLine);
                carPosition.x=(int) lerp(startPoint.x,endPoint.x,i);                       
                carPosition.y=(int)lerp(startPoint.y,endPoint.y,i);

                System.out.println("Car position: x"+carPosition.x+": y"+carPosition.y );
                //System.out.println("Point"+pointOnTimeLine);

                repaint();
                counter++;
            }
        });
        timer.start();

     }
    else{
        //System.out.println("Destination reached");
    }


   //carPosition.x+=1;
   //repaint();    


}


double lerp(double a, double b, double t) {
    return a + (b - a) * t;
}

Thread to move the car

public void moveCar() {
    Runnable helloRunnable = new Runnable() {
        public void run() {

           car.update();
           repaint();


        }
    };

    ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    executor.scheduleAtFixedRate(helloRunnable, 0, 40, TimeUnit.MILLISECONDS);
}

Lane.cs

public class Lane {

     public static List<Point> firstLane = new ArrayList<>(Arrays.asList(new Point(10,135),new Point(124,190),new Point(363,190),new Point(469,210)));

}

EDIT: I have made changes to my code according to MadProgrammers suggestions. The animation works now here's the movie of the animation http://gyazo.com/e6a28b87cb905c0ff5eb023d68955321. My OP is updated with my current code. Next step is the turn part, but I think there is a more elegent way to call the car update method and repaint() in moveCar. I have specified the time in this thread to the same length as in the timer (40ms). Is there a better way to call car.update() and repaint in moveCar()?

Sybren
  • 1,071
  • 3
  • 17
  • 51
  • [This example](http://stackoverflow.com/questions/28619150/move-image-in-a-spiral-fashion-in-java/28619554#28619554) and [this example](http://stackoverflow.com/questions/26898536/moving-jlabel-to-other-jlabels-gui/26899099#26899099) essentially use a timeline/key frame concept to animate an object between points in time... – MadProgrammer Mar 10 '15 at 11:02
  • I prefer make my code work instead of implementing the timeline/key frame concept. That must be possible I think? – Sybren Mar 10 '15 at 11:08
  • Probably, but the concept is the same. You have a series of points (key frames), which you object must move through over a period of time (timeline). Take a look at the ideas and see what you can make of it. – MadProgrammer Mar 10 '15 at 11:14
  • I have looked at many examples and this is what I made of it. Someone who can help to make this code working? – Sybren Mar 10 '15 at 11:19
  • At what time over the duration of the animation are the images suppose to move through each point? I think part of the problem is you actually don't understand your own problem/requirements... – MadProgrammer Mar 10 '15 at 11:31
  • When the run method in moveCar() is called the image has to move. It jumps to the next point in the list every two secs now because the are only four points in the list with a big distance between them. I want to calculate points between the current location of the car and the next point in the list (linear interpolation) and let the car drive smooth over this points. – Sybren Mar 10 '15 at 11:43
  • Over what time period? Are the points evenly distributed over the period of time? – MadProgrammer Mar 10 '15 at 20:30
  • Also remember, movement between two points needs one of two things; time or speed (from one you can calculate the other). In my experience, time is the easier of the two to deal with (it will take `t` time to travel between the two points), this will allow you to very the speed if required – MadProgrammer Mar 10 '15 at 23:29
  • @MadProgrammer The points are evenly distrubuted over the period of time. How can I use the time to travel smooth along the points? – Sybren Mar 11 '15 at 10:10

1 Answers1

3

Let's break this down...

Basically you want to move from one point (A) to another (B) over a period of time (t). A given point between A and B at a given point in time is a percentage of the difference between the two (where t is normalized as a fraction between 0 and 1)

Timeline

So, if A is 10 and B is 20 and t is 2 seconds, at 1 second p should be 15 (((B - A) * i) + A) where i is the normalized time difference of 0.5 (50% of 2 seconds = 1 second)

So, given any point in time, you can calculate the difference between the two points and calculate the position it should be at.

If you're wondering why I normalized the time, consider this, if you change t to be 4 seconds instead, the calculations don't change, we just need to calculate the normalized point in time (1 / 4 = 0.25) and run that back through the calculations to give us the desired result.

So, what you need to know is how long it will take to go from point A to point B. Then you need some mechanism which can regularly check the amount of time that has passed and calculate the current position of the object between the two points. For this you could use a Swing Timer to tick at a regular interval (like 40 milliseconds for example) until 2 seconds have elapsed.

Move from A to B

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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
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 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 Long startTime;
        private long playTime = 2000;

        private Point startPoint, endPoint;
        private Point pointOnTimeLine;
        private double pointInTime; // For rendering...

        public TestPane() {
            startPoint = new Point(0, 95);
            endPoint = new Point(190, 95);
            pointOnTimeLine = new Point(startPoint);
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startTime == null) {
                        startTime = System.currentTimeMillis();
                    }
                    long now = System.currentTimeMillis();
                    long diff = now - startTime;
                    if (diff >= playTime) {
                        diff = playTime;
                        ((Timer) e.getSource()).stop();
                    }
                    double i = (double) diff / (double) playTime;
                    pointInTime = i;

                    pointOnTimeLine.x = (int) (startPoint.x + ((endPoint.x - startPoint.x) * i));
                    pointOnTimeLine.y = (int) (startPoint.y + ((endPoint.y - startPoint.y) * i));

                    repaint();
                }
            });

            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            g2d.fill(new Ellipse2D.Double(startPoint.x, startPoint.y, 10, 10));
            g2d.fill(new Ellipse2D.Double(endPoint.x, endPoint.y, 10, 10));
            g2d.setColor(Color.GREEN);
            g2d.fill(new Ellipse2D.Double(pointOnTimeLine.x, pointOnTimeLine.y, 10, 10));
            g2d.dispose();
        }

    }

}

Okay, "but how does this help me?" I hear you asking. Well, the reality is, this is the basics for moving between multiple points.

In your code you have 4 key points in time, evenly distributed, over which the object must move, this means that each point is roughly 33% apart along the time line.

When you calculate the current position along the time line, you need to find the two points that it is between (0-33 is the first and second point, 34-66 is the second and third and 67 > is the third and forth) and then calculate the position of the object between these points.

It's a little more complicated, as you need to take into consideration the amount of time between the two points as a fraction of the over all time, but since the distance in time between the points is mostly even, it shouldn't play a big factor.

This is demonstrated here and here so you will have to forgive me for not reposting the code again.

Now, for my money, I'd also invest some time into getting to know an animation library like Timing Framework and/or Trident and/or Universal Tween Engine

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Can you the a look at the edit part of my OP? You can also see a movie of the result there. – Sybren Mar 12 '15 at 10:48
  • I really don't think you need the `ScheduledExecutorService` and the `Timer`, they are doing the same thing and would be clashing, probably cause double the number of updates. What I might do is replace the `ScheduledExecutorService` with the `Timer` – MadProgrammer Mar 12 '15 at 12:42