1

I'm trying to make my Pedestrian object move, and it moves but at a certain point it flies away from the screen. The Pedestrian moves by a List of points. First the Pedestrian is added to toDraw to paint it and in startAndCreateTimer I loop through the same list to move the Vehicles Maybe it's because of this line i = (double) diff / (double) playTime; I actually don't want to set a playtime how not to do that, could this be the problem or is it something else? Here a link with the point where the Pedestrian flies away (starts north of left roundabout) http://gyazo.com/23171a6106c88f1ba8ca438598ff4153.

class Surface extends JPanel{

Track track=new Track();      
public List<Vehicle> toDraw = new ArrayList<>();
private Long startTime;
private long playTime = 4000;
private double i;

public Surface(){
    startAndCreateTimer();
}

@Override
public void paintComponent(Graphics g) {

    super.paintComponent(g);    
    //Make sure the track is painted first
    track.paint(g);

    for (Vehicle v : toDraw) {
        v.paint(g);
    }


}

public void repaintPanel(){
    this.repaint();
}

private void startAndCreateTimer(){

    Timer timer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (startTime == null) {
                    startTime = System.currentTimeMillis();
                }
                long now = System.currentTimeMillis();
                long diff = now - startTime;

                i = (double) diff / (double) playTime;

                for (Vehicle v : toDraw){
                v.update(i);
                }


                repaintPanel();

            }
        });
        timer.start(); 

    }
}

Pedestrian java

public class Pedestrian extends Vehicle {

BufferedImage pedestrian;
Point pedestrianPosition;
double pedestrianRotation = 0;
int pedestrianW, pedestrianH;
int counter=0;
List<LanePoint>pedestrianPath;
boolean lockCounter=false;

public Pedestrian(int x, int y){
    try {
        pedestrian = ImageIO.read(Car.class.getResource("images/human.png"));
    } catch (IOException e) {
        System.out.println("Problem loading pedestrian images: " + e);
    }

    pedestrianPosition = new Point(x,y);
    pedestrianW = pedestrian.getWidth();
    pedestrianH = pedestrian.getHeight();
}

@Override
public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.rotate(Math.toRadians(pedestrianRotation), pedestrianPosition.x, pedestrianPosition.y);
    g2d.drawImage(pedestrian, pedestrianPosition.x, pedestrianPosition.y, null);
}


@Override
public void setPath(List<LanePoint> path) {
    pedestrianPath=path;
}

/*Update*/
@Override
 public void update(double i){


    if (counter < pedestrianPath.size()) {               
        Point startPoint = new Point(pedestrianPosition.x, pedestrianPosition.y);
        LanePoint endPoint = new LanePoint(pedestrianPath.get(counter).x, pedestrianPath.get(counter).y,pedestrianPath.get(counter).lanePointType,pedestrianPath.get(counter).lanePointToTrafficLight,pedestrianPath.get(counter).laneTrafficLightId,pedestrianPath.get(counter).degreesRotation);

        pedestrianPosition.x=(int)Maths.lerp(startPoint.x,endPoint.x,i);                  
        pedestrianPosition.y=(int)Maths.lerp(startPoint.y,endPoint.y,i);  
        pedestrianRotation=endPoint.degreesRotation;


        if(pedestrianPosition.equals(new Point(endPoint.x,endPoint.y))){ 
            /*PEDESTRIAN SIGN UP*/
            if (endPoint.lanePointType.equals(LanePoint.PointType.TRAFFICLIGHT) && endPoint.lanePointToTrafficLight.equals(LanePoint.PointToTrafficLight.INFRONTOF)){
                try {
                    Roundabout.client.sendBytes(new byte []{0x03,endPoint.laneTrafficLightId.byteValue(),0x01,0x00});
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            /*PEDESTRIAN SIGN OFF*/
            else if (endPoint.lanePointType.equals(LanePoint.PointType.TRAFFICLIGHT) && endPoint.lanePointToTrafficLight.equals(LanePoint.PointToTrafficLight.UNDERNEATH)) {                                        
                if (Surface.trafficLights.get(endPoint.laneTrafficLightId).red) {
                    lockCounter = true;
                } else {
                    try {
                        Roundabout.client.sendBytes(new byte[]{0x03, endPoint.laneTrafficLightId.byteValue(), 0x00, 0x00});
                        lockCounter=false;
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }                  
            }
            if (!lockCounter) {
                counter++; //Increment counter > sets next point
            }
        }
    }
  }
}

Maths.java

public class Maths {

    //Lineat interpolation
    public static double lerp(double a, double b, double t) {
        return a + (b - a) * t;
    }

}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
Sybren
  • 1,071
  • 3
  • 17
  • 51
  • What happens when `diff` >= `startTime`? – MadProgrammer May 25 '15 at 07:22
  • 1
    Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses – MadProgrammer May 25 '15 at 07:23
  • @MadProgrammer nothing happens when `diff >= startTime`, I think the image flies away because of the `playTime`, I want to update as long as the program runs not for 4000 ms, how to do that or could this not cause my problem?. – Sybren May 25 '15 at 07:26
  • 1
    Without some kind of runnable example, there are just two many questions that need to be answered. When calculating the position of the pedestrian, for example, you should be calculating the distance between point A and point B, but you seem to be calculate it's position based on it's current position and point B, but because I can't see what else is going on, I have no idea if this is a good idea or not – MadProgrammer May 25 '15 at 07:30
  • @MadProgrammer yes, I calculate the positions based on the current position and the next position in the list with linear interpolation, I have added my linear interpolation method – Sybren May 25 '15 at 07:43
  • But wouldn't that require you to calculate the position based on a start position and a end position and a percentage of the distance traveled, not the current position and the end position? – MadProgrammer May 25 '15 at 07:44
  • How would I implement the percentage of the distance travelled? – Sybren May 25 '15 at 07:51
  • Something like `startPoint + ((endPoint - startPoint) * t)`, where `startPoint` and `endPoint` remain static points so `t` can travel between them. – MadProgrammer May 25 '15 at 07:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/78665/discussion-between-sybren-and-madprogrammer). – Sybren May 25 '15 at 08:00

1 Answers1

2

So, basically you are calculating the position of the object between to points based on the amount of time that has passed. This is good.

So at t = 0, the object will be at the start point, at t = 0.5, it will be halfway between the start and end point, at t = 1.0 it will be at the end point.

What happens when t > 1.0? Where should the object be? - hint, it should be nowhere as it should have been removed or reset...

This and this are basic examples of "time line" based animation, meaning that, over a period of time, the position of the object is determined by using different points (along a time line)

So, in order to calculate the position along a line, you need three things, the point you started at, the point you want to end at and the duration (between 0-1)

Using these, you can calculate the point along the line between these two points based on the amount of time.

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.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
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 static class TestPane extends JPanel {

        protected static final double PLAY_TIME = 4000.0;

        private Point2D startAt = new Point(0, 0);
        private Point2D endAt = new Point(200, 200);
        private Point2D current = startAt;
        private Long startTime;

        public TestPane() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startTime == null) {
                        startTime = System.currentTimeMillis();
                    }
                    long time = System.currentTimeMillis() - startTime;
                    double percent = (double) time / PLAY_TIME;
                    if (percent > 1.0) {
                        percent = 1.0;
                        ((Timer) e.getSource()).stop();
                    }

                    current = calculateProgress(startAt, endAt, percent);
                    repaint();
                }
            });
            timer.start();
        }

        protected Point2D calculateProgress(Point2D startPoint, Point2D targetPoint, double progress) {

            Point2D point = new Point2D.Double();

            if (startPoint != null && targetPoint != null) {

                point.setLocation(
                                calculateProgress(startPoint.getX(), targetPoint.getY(), progress),
                                calculateProgress(startPoint.getX(), targetPoint.getY(), progress));

            }

            return point;

        }

        protected double calculateProgress(double startValue, double endValue, double fraction) {

            return startValue + ((endValue - startValue) * fraction);

        }

        @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.GREEN);
            g2d.draw(new Line2D.Double(startAt, endAt));
            g2d.setColor(Color.RED);
            g2d.fill(new Ellipse2D.Double(current.getX() - 5, current.getY() - 5, 10, 10));
            g2d.dispose();
        }

    }

}

So, using current = calculateProgress(startAt, endAt, percent);,

NiceMove

you can see that the dot moves evenly between the start and end points.

If we change it to something more like what you seem to be doing, current = calculateProgress(current, endAt, percent);,

BadMove

you can see that it speeds down the line and finally eases out, which isn't what you really want...

Updated with time line theory

Let's imagine you have a time line, which has a length of t and along this time line, you have 5 events (or key frames) (e1 - e5), each occurring after each other.

e1 starts at 0 and e5 ends at 1

Time Line

As you can see, the events occur at irregular intervals and run for different lengths of time.

  • t1 runs for 25% of the time line
  • t2 runs for 25% of the time line
  • t3 runs for 12.5% of the time line
  • t3 runs for 37.5% of the time line

So, based on t, you need to determine which events are been executed. So when t is 0.12, we are running about half way through t1 (between e1 & e2).

You then need to calculate local time/difference between the key frames (0-0.25 along the timeline)

localTime = 1.0 - ((t - e1) / (e2 - e1))
          = 1.0 - ((0.12 - 0) / (0.25 - 0))
          = 1.0 - (0.12 / 0.25)
          = 1.0 - 0.48
          = 0.52

Where t is the time along the time line, e1 is the time of the first event (0) and e2 is the time of the second event (0.25), which gives us the duration along the t1 (in this example)

This is then the value of your linear interpolation for the given time slice.

Runnable example...

I took a look at your code, but there's a lot of work that needs to be done to get this to work.

Basically, you need to know how long the path is and the amount that each segment is of that path (as a percentage). With this, we can create a "time line" of "key frames" which determines how far along the "path" your object is based on the amount of time that has passed and the amount of time it "should" take to travel.

So, the first thing I did was create a Path class (kind of mimics your Lists, but has some additional methods)

public class Path implements Iterable<Point> {

    private List<Point> points;
    private double totalLength = 0;

    public Path(Point... points) {
        this.points = new ArrayList<>(Arrays.asList(points));
        for (int index = 0; index < size() - 1; index++) {
            Point a = get(index);
            Point b = get(index + 1);
            double length = lengthBetween(a, b);
            totalLength += length;
        }
    }

    public double getTotalLength() {
        return totalLength;
    }

    public int size() {
        return points.size();
    }

    public Point get(int index) {
        return points.get(index);
    }

    public double lengthBetween(Point a, Point b) {

        return Math.sqrt(
                        (a.getX() - b.getX()) * (a.getX() - b.getX())
                        + (a.getY() - b.getY()) * (a.getY() - b.getY()));

    }

    @Override
    public Iterator<Point> iterator() {
        return points.iterator();
    }

}

Mostly, this provides the totalLength of the path. We use this to calculate how much each segment takes up later

I then borrowed the TimeLine class from this previous answer

public class Timeline {

    private Map<Double, KeyFrame> mapEvents;

    public Timeline() {
        mapEvents = new TreeMap<>();
    }

    public void add(double progress, Point p) {
        mapEvents.put(progress, new KeyFrame(progress, p));
    }

    public Point getPointAt(double progress) {

        if (progress < 0) {
            progress = 0;
        } else if (progress > 1) {
            progress = 1;
        }

        KeyFrame[] keyFrames = getKeyFramesBetween(progress);

        double max = keyFrames[1].progress - keyFrames[0].progress;
        double value = progress - keyFrames[0].progress;
        double weight = value / max;

        return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);

    }

    public KeyFrame[] getKeyFramesBetween(double progress) {

        KeyFrame[] frames = new KeyFrame[2];
        int startAt = 0;
        Double[] keyFrames = mapEvents.keySet().toArray(new Double[mapEvents.size()]);
        while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
            startAt++;
        }

        if (startAt >= keyFrames.length) {
            startAt = keyFrames.length - 1;
        }

        frames[0] = mapEvents.get(keyFrames[startAt - 1]);
        frames[1] = mapEvents.get(keyFrames[startAt]);

        return frames;

    }

    protected Point blend(Point start, Point end, double ratio) {
        Point blend = new Point();

        double ir = (float) 1.0 - ratio;

        blend.x = (int) (start.x * ratio + end.x * ir);
        blend.y = (int) (start.y * ratio + end.y * ir);

        return blend;
    }

    public class KeyFrame {

        private double progress;
        private Point point;

        public KeyFrame(double progress, Point point) {
            this.progress = progress;
            this.point = point;
        }

        public double getProgress() {
            return progress;
        }

        public Point getPoint() {
            return point;
        }

    }

}

Now, as they stand, they are not compatible, we need to take each segment and calculate the length of the segment as a percentage of the total length of the path and create a key frame for the specified point along the time line...

double totalLength = path.getTotalLength();
timeLine = new Timeline();
timeLine.add(0, path.get(0));
// Point on time line...
double potl = 0;
for (int index = 1; index < path.size(); index++) {
    Point a = path.get(index - 1);
    Point b = path.get(index);
    double length = path.lengthBetween(a, b);
    double normalised = length / totalLength;
    // Normalised gives as the percentage of this segment, we need to
    // translate that to a point on the time line, so we just add
    // it to the "point on time line" value to move to the next point :)
    potl += normalised;
    timeLine.add(potl, b);
}

I did this deliberately, to show the work you are going to need to do.

Need, I create a Ticker, which just runs a Swing Timer and reports ticks to Animations

public enum Ticker {

    INSTANCE;

    private Timer timer;
    private List<Animation> animations;

    private Ticker() {
        animations = new ArrayList<>(25);
        timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // Prevent possible mutatation issues...
                Animation[] anims = animations.toArray(new Animation[animations.size()]);
                for (Animation animation : anims) {
                    animation.tick();
                }
            }
        });
    }

    public void add(Animation animation) {
        animations.add(animation);
    }

    public void remove(Animation animation) {
        animations.remove(animation);
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

}

public interface Animation {

    public void tick();

}

This centralises the "clock", be allows Animations to determine what they would like to do on each tick. This should be more scalable then creating dozens of Timers

Okay, that's all fun and games, but how does it work together? Well, here's a complete runnable example.

It takes one of your own paths and creates a TimeLine out of it and animates a object moving along it.

FollowMeHome

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.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
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();
                }

                Path path = new Path(
                                new Point(440, 40),
                                new Point(440, 120),
                                new Point(465, 90),
                                new Point(600, 180),
                                new Point(940, 165),
                                new Point(940, 145),
                                new Point(1045, 105),
                                new Point(1080, 120),
                                new Point(1170, 120),
                                new Point(1200, 120),
                                new Point(1360, 123),
                                new Point(1365, 135),
                                new Point(1450, 170),
                                new Point(1457, 160),
                                new Point(1557, 160));

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

                Ticker.INSTANCE.start();
            }
        });
    }

    public enum Ticker {

        INSTANCE;

        private Timer timer;
        private List<Animation> animations;

        private Ticker() {
            animations = new ArrayList<>(25);
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    // Prevent possible mutatation issues...
                    Animation[] anims = animations.toArray(new Animation[animations.size()]);
                    for (Animation animation : anims) {
                        animation.tick();
                    }
                }
            });
        }

        public void add(Animation animation) {
            animations.add(animation);
        }

        public void remove(Animation animation) {
            animations.remove(animation);
        }

        public void start() {
            timer.start();
        }

        public void stop() {
            timer.stop();
        }

    }

    public interface Animation {

        public void tick();

    }

    public static final double PLAY_TIME = 4000d;

    public class TestPane extends JPanel implements Animation {

        private Path path;
        private Path2D pathShape;
        private Timeline timeLine;

        private Long startTime;
        private Point currentPoint;

        public TestPane(Path path) {
            this.path = path;

            // Build the "path" shape, we can render this, but more importantally
            // it allows use to determine the preferred size of the panel :P
            pathShape = new Path2D.Double();
            pathShape.moveTo(path.get(0).x, path.get(0).y);
            for (int index = 1; index < path.size(); index++) {
                Point p = path.get(index);
                pathShape.lineTo(p.x, p.y);
            }

            // Build the time line.  Each segemnt (the line between any two points)
            // makes up a percentage of the time travelled, we need to calculate
            // the amount of time that it would take to travel that segement as
            // a percentage of the overall length of the path...this
            // allows us to even out the time...
            double totalLength = path.getTotalLength();
            timeLine = new Timeline();
            timeLine.add(0, path.get(0));
            // Point on time line...
            double potl = 0;
            for (int index = 1; index < path.size(); index++) {
                Point a = path.get(index - 1);
                Point b = path.get(index);
                double length = path.lengthBetween(a, b);
                double normalised = length / totalLength;
                // Normalised gives as the percentage of this segment, we need to
                // translate that to a point on the time line, so we just add
                // it to the "point on time line" value to move to the next point :)
                potl += normalised;

                timeLine.add(potl, b);
            }

            currentPoint = path.get(0);

            Ticker.INSTANCE.add(this);

        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = pathShape.getBounds().getSize();
            size.width += pathShape.getBounds().x;
            size.height += pathShape.getBounds().y;
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.GREEN);
            g2d.draw(pathShape);

            g2d.setColor(Color.RED);
            g2d.fill(new Ellipse2D.Double(currentPoint.x - 5, currentPoint.y - 5, 10, 10));
            g2d.dispose();
        }

        @Override
        public void tick() {
            if (startTime == null) {
                startTime = System.currentTimeMillis();
            }

            long diff = System.currentTimeMillis() - startTime;
            double t = (double)diff / PLAY_TIME;
            if (t > 1.0) {
                t = 1.0d;
                // Don't call me any more, I'm already home
                Ticker.INSTANCE.remove(this);
            }

            currentPoint = timeLine.getPointAt(t);
            repaint();

        }

    }

    public class Path implements Iterable<Point> {

        private List<Point> points;
        private double totalLength = 0;

        public Path(Point... points) {
            this.points = new ArrayList<>(Arrays.asList(points));
            for (int index = 0; index < size() - 1; index++) {
                Point a = get(index);
                Point b = get(index + 1);
                double length = lengthBetween(a, b);
                totalLength += length;
            }
        }

        public double getTotalLength() {
            return totalLength;
        }

        public int size() {
            return points.size();
        }

        public Point get(int index) {
            return points.get(index);
        }

        public double lengthBetween(Point a, Point b) {

            return Math.sqrt(
                            (a.getX() - b.getX()) * (a.getX() - b.getX())
                            + (a.getY() - b.getY()) * (a.getY() - b.getY()));

        }

        @Override
        public Iterator<Point> iterator() {
            return points.iterator();
        }

    }

    public class Timeline {

        private Map<Double, KeyFrame> mapEvents;

        public Timeline() {
            mapEvents = new TreeMap<>();
        }

        public void add(double progress, Point p) {
            mapEvents.put(progress, new KeyFrame(progress, p));
        }

        public Point getPointAt(double progress) {

            if (progress < 0) {
                progress = 0;
            } else if (progress > 1) {
                progress = 1;
            }

            KeyFrame[] keyFrames = getKeyFramesBetween(progress);

            double max = keyFrames[1].progress - keyFrames[0].progress;
            double value = progress - keyFrames[0].progress;
            double weight = value / max;

            return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);

        }

        public KeyFrame[] getKeyFramesBetween(double progress) {

            KeyFrame[] frames = new KeyFrame[2];
            int startAt = 0;
            Double[] keyFrames = mapEvents.keySet().toArray(new Double[mapEvents.size()]);
            while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
                startAt++;
            }

            if (startAt >= keyFrames.length) {
                startAt = keyFrames.length - 1;
            }

            frames[0] = mapEvents.get(keyFrames[startAt - 1]);
            frames[1] = mapEvents.get(keyFrames[startAt]);

            return frames;

        }

        protected Point blend(Point start, Point end, double ratio) {
            Point blend = new Point();

            double ir = (float) 1.0 - ratio;

            blend.x = (int) (start.x * ratio + end.x * ir);
            blend.y = (int) (start.y * ratio + end.y * ir);

            return blend;
        }

        public class KeyFrame {

            private double progress;
            private Point point;

            public KeyFrame(double progress, Point point) {
                this.progress = progress;
                this.point = point;
            }

            public double getProgress() {
                return progress;
            }

            public Point getPoint() {
                return point;
            }

        }

    }
}

Now, if I was doing this, I would create a method either in Path or as a static utility method, that took a Path and returned a TimeLine automatically ;)

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I don't have acces to the start and end position in the panel class, and i'm dealing with a list of points and a list of images to draw and move, how to do that? – Sybren May 25 '15 at 08:59
  • Look at the chat please. – Sybren May 25 '15 at 09:31
  • @Sybren I took a look at you code but after about an hour of trying to figure out how I might "fix" it, I thought it better to simply take the concept of what you have and merge the concept of the `TimeLine` into that as an example. See update – MadProgrammer May 26 '15 at 02:51
  • That looks very cool and smooth. I'm trying to create Car class implementing the `Animation` interface, this is the correct approach right? But `repaint` is not available, does Car need to extend `JComponent`? – Sybren May 26 '15 at 07:13
  • Well, in my example I just extended from `JPanel`, but you may find `JComponent` more suitable as it's transparent by default, but that comes down to needs ;) – MadProgrammer May 26 '15 at 07:15
  • Your components or objects don't "have" to implement `Animation`, they could just be simple "containers" which paint content (probably at a desired location). You could then create a custom `Animation` passing it the information you need (`Path`, the object to be painted/updated) and it could calculate the new position and trigger an update via your object or to a central "container" depending on how you want to implement it – MadProgrammer May 26 '15 at 07:17