5

I have a problem with my current animation that I'm running using Java Swing. It is a discrete event simulation and the text based simulation is working fine, I'm just having problems connecting the simulating to GUI output.

For this example I will have 10 cars to be simulated. The cars are represented by JPanels which I will elaborate on in a few moments.

So consider, the event process_car_arrival. Every time this event is scheduled for execution, I'm adding a Car object to an ArrayList called cars in my Model class. The Car class has the following relevant attributes:

Point currentPos; // The current position, initialized in another method when knowing route.
double speed; // giving the speed any value still causes the same problem but I have 5 atm.
RouteType route; // for this example I only consider one simple route

In addition it has the following method move() :

switch (this.route) {
    case EAST:
        this.currentPos.x -= speed; 
        return this.currentPos;
.
.
.
//only above is relevant in this example

This is all well. so in theory the car traverses along a straight road from east to west as I just invoke the move() method for each car I want to move.

Returning to the process_car_arrival event. After adding a Car object it invokes a method addCarToEast() in the View class. This adds a JPanel at the start of the road going from east to west.

Going to the View class now I have a ** separate** thread which does the following ( the run() method) :

@Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!cars.isEmpty()) {

                cars.get(i).setLocation(
                        new Point(getModel.getCars().get(i).move()));

                if (i == cars.size() - 1) {
                    i = 0;
                } else {
                    i++;
                }
            }
        }
    }

The above does move the car from east to west smoothly at first. But after there is 3-4 cars moving it just ends up being EXTREMELY slow and when I have 10 cars moving it just ends up moving very little.

Just to clear up, at the moment in the Model class there's an ArrayList of Car objects, and in the View class there is also an ArrayList of JPanel objects representing the cars. I'm trying to match the Car objects to the JPanels, but I'm obviously doing a cra**y job.

I suspect that I'm doing something insanely inefficient but I don't know what. I thought initially maybe it's accessing the ArrayList so much which I guess would make it really slow.

Any pointers to what I can change to make it run smoothly?

Force444
  • 3,321
  • 9
  • 39
  • 77
  • 1
    My first concern is it appears you are updating UI elements from outside the Event Dispatching Thread. This is dangerous and extremely inadvisable. Check [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) for more details. The second issue I have is this isn't nearly enough to go on. Can you produce a simple working example? Doesn't need to have images for the car, a simple rectangle representation would be enough. – MadProgrammer Feb 15 '13 at 00:08
  • 1
    On top of what MadProgrammer said, the other red flag is that you are creating separate JPanels. Its been awhile since I've done anything graphics related, but I think you should be using a 2D library and not moving "n" number of JPanels around. – Joe Feb 15 '13 at 00:13
  • @MadProgrammer I'm just working through the link you gave me and trying to see if I can catch anything. Will post back later of a simple working example if I'm unable to get it to work. – Force444 Feb 15 '13 at 00:57
  • @Joe Could you please elaborate on what you mean with not moving JPanels around? Do you mean creating a new rectangle for each movement? wouldn't that be slow ( if that's what you mean) ? – Force444 Feb 15 '13 at 00:58
  • @Jatt Moving panels and drawing directly probably isnt't going to be much of a difference to you right now. You might find, especially over time, that rendering to a backing buffer of some kind and then swapping that out to the UI will result in quicker updates, but I say get the logic running first - IMHO – MadProgrammer Feb 15 '13 at 01:00
  • @Jett It's not a bad idea, I'd be concerned with how you would deal with non-linear paths and variable speeds though. Having the car "know" where it is and where it's going is probably better. Your animation engine should simple let the car know that it needs to update it's position, with out telling it where it should go... – MadProgrammer Feb 15 '13 at 01:42
  • @MadProgrammer - My question dissapeared somehow. But thanks for the answer I will have a go at changing it around for a few hours :) – Force444 Feb 15 '13 at 01:50

2 Answers2

11

Based on this previous answer, the example below simulates a fleet of three cabs moving randomly on a rectangular grid. A javax.swing.Timer drives the animation at 5 Hz. The model and view are tightly coupled in CabPanel, but the animation may provide some useful insights. In particular, you might increase the number of cabs or lower the timer delay.

image

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * @see https://stackoverflow.com/a/14887457/230513
 * @see https://stackoverflow.com/questions/5617027
 */

public class FleetPanel extends JPanel {

    private static final Random random = new Random();
    private final MapPanel map = new MapPanel();
    private final JPanel control = new JPanel();
    private final List<CabPanel> fleet = new ArrayList<CabPanel>();
    private final Timer timer = new Timer(200, null);

    public FleetPanel() {
        super(new BorderLayout());
        fleet.add(new CabPanel("Cab #1", Hue.Cyan));
        fleet.add(new CabPanel("Cab #2", Hue.Magenta));
        fleet.add(new CabPanel("Cab #3", Hue.Yellow));
        control.setLayout(new GridLayout(0, 1));
        for (CabPanel cp : fleet) {
            control.add(cp);
            timer.addActionListener(cp.listener);
        }
        this.add(map, BorderLayout.CENTER);
        this.add(control, BorderLayout.SOUTH);
    }

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

    private class CabPanel extends JPanel {

        private static final String format = "000000";
        private final DecimalFormat df = new DecimalFormat(format);
        private JLabel name = new JLabel("", JLabel.CENTER);
        private Point point = new Point();
        private JLabel position = new JLabel(toString(point), JLabel.CENTER);
        private int blocks;
        private JLabel odometer = new JLabel(df.format(0), JLabel.CENTER);
        private final JComboBox colorBox = new JComboBox();
        private final JButton reset = new JButton("Reset");
        private final ActionListener listener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                int ds = random.nextInt(3) - 1;
                if (random.nextBoolean()) {
                    point.x += ds;
                } else {
                    point.y += ds;
                }
                blocks += Math.abs(ds);
                update();
            }
        };

        public CabPanel(String s, Hue hue) {
            super(new GridLayout(1, 0));
            name.setText(s);
            this.setBackground(hue.getColor());
            this.add(map, BorderLayout.CENTER);
            for (Hue h : Hue.values()) {
                colorBox.addItem(h);
            }
            colorBox.setSelectedIndex(hue.ordinal());
            colorBox.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    Hue h = (Hue) colorBox.getSelectedItem();
                    CabPanel.this.setBackground(h.getColor());
                    update();
                }
            });
            reset.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    point.setLocation(0, 0);
                    blocks = 0;
                    update();
                }
            });
            this.add(name);
            this.add(odometer);
            this.add(position);
            this.add(colorBox);
            this.add(reset);
        }

        private void update() {
            position.setText(CabPanel.this.toString(point));
            odometer.setText(df.format(blocks));
            map.repaint();
        }

        private String toString(Point p) {
            StringBuilder sb = new StringBuilder();
            sb.append(Math.abs(p.x));
            sb.append(p.x < 0 ? " W" : " E");
            sb.append(", ");
            sb.append(Math.abs(p.y));
            sb.append(p.y < 0 ? " N" : " S");
            return sb.toString();
        }
    }

    private class MapPanel extends JPanel {

        private static final int SIZE = 16;

        public MapPanel() {
            this.setPreferredSize(new Dimension(32 * SIZE, 32 * SIZE));
            this.setBackground(Color.lightGray);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            int w = this.getWidth();
            int h = this.getHeight();
            g2d.setColor(Color.gray);
            for (int col = SIZE; col <= w; col += SIZE) {
                g2d.drawLine(col, 0, col, h);
            }
            for (int row = SIZE; row <= h; row += SIZE) {
                g2d.drawLine(0, row, w, row);
            }

            for (CabPanel cp : fleet) {
                Point p = cp.point;
                int x = SIZE * (p.x + w / 2 / SIZE) - SIZE / 2;
                int y = SIZE * (p.y + h / 2 / SIZE) - SIZE / 2;
                g2d.setColor(cp.getBackground());
                g2d.fillOval(x, y, SIZE, SIZE);
            }
        }
    }

    public enum Hue {

        Cyan(Color.cyan), Magenta(Color.magenta), Yellow(Color.yellow),
        Red(Color.red), Green(Color.green), Blue(Color.blue),
        Orange(Color.orange), Pink(Color.pink);
        private final Color color;

        private Hue(Color color) {
            this.color = color;
        }

        public Color getColor() {
            return color;
        }
    }

    private static void display() {
        JFrame f = new JFrame("Dispatch");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        FleetPanel fp = new FleetPanel();
        f.add(fp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        fp.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                display();
            }
        });
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thank you Sir :) Great insight. Learned a much more from reading your code in 1 hour than I did spending all night in frustration! – Force444 Feb 15 '13 at 05:05
5

I couldn't resist...

enter image description here

I got 500 cars running on the screen with little slow down (it wasn't the fastest...about 200-300 was pretty good...

This uses panels to represent each vehicle. If you want to get better performance, your probably need to look at using a backing buffer of some kind.

public class TestAnimation10 {

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

    public TestAnimation10() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                final TrackPane trackPane = new TrackPane();
                JSlider slider = new JSlider(1, 500);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        trackPane.setCongestion(((JSlider)e.getSource()).getValue());
                    }
                });
                slider.setValue(5);

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(trackPane);
                frame.add(slider, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class TrackPane extends JPanel {

        private List<Car> cars;
        private int maxCars = 1;

        private List<Point2D[]> points;

        private Ellipse2D areaOfEffect;

        public TrackPane() {

            points = new ArrayList<>(25);

            cars = new ArrayList<>(25);
            setLayout(null);

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {

                    Rectangle bounds = areaOfEffect.getBounds();
                    List<Car> tmp = new ArrayList<>(cars);
                    for (Car car : tmp) {
                        car.move();
                        if (!bounds.intersects(car.getBounds())) {
                            remove(car);
                            cars.remove(car);
                        }
                    }
                    updatePool();
                    repaint();
                }
            });

            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();

            updateAreaOfEffect();
        }

        protected void updateAreaOfEffect() {
            double radius = Math.max(getWidth(), getHeight()) * 1.5d;
            double x = (getWidth() - radius) / 2d;
            double y = (getHeight() - radius) / 2d;
            areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
        }

        @Override
        public void invalidate() {
            super.invalidate();
            updateAreaOfEffect();
        }

        protected void updatePool() {
            while (cars.size() < maxCars) {
//            if (cars.size() < maxCars) {
                Car car = new Car();
                double direction = car.getDirection();
                double startAngle = direction - 180;

                double radius = areaOfEffect.getWidth();
                Point2D startPoint = getPointAt(radius, startAngle);

                int cx = getWidth() / 2;
                int cy = getHeight() / 2;

                double x = cx + (startPoint.getX() - car.getWidth() / 2);
                double y = cy + (startPoint.getY() - car.getHeight() / 2);
                car.setLocation((int)x, (int)y);

                Point2D targetPoint = getPointAt(radius, direction);

                points.add(new Point2D[]{startPoint, targetPoint});

                add(car);

                cars.add(car);
            }
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            Font font = g.getFont();
            font = font.deriveFont(Font.BOLD, 48f);
            FontMetrics fm = g.getFontMetrics(font);
            g.setFont(font);
            g.setColor(Color.RED);
            String text = Integer.toString(maxCars);
            int x = getWidth() - fm.stringWidth(text);
            int y = getHeight() - fm.getHeight() + fm.getAscent();
            g.drawString(text, x, y);
            text = Integer.toString(getComponentCount());
            x = getWidth() - fm.stringWidth(text);
            y -= fm.getHeight();
            g.drawString(text, x, y);
            text = Integer.toString(cars.size());
            x = getWidth() - fm.stringWidth(text);
            y -= fm.getHeight();
            g.drawString(text, x, y);
        }

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

        public void setCongestion(int value) {
            maxCars = value;
        }
    }

    protected static Point2D getPointAt(double radius, double angle) {

        double x = Math.round(radius / 2d);
        double y = Math.round(radius / 2d);

        double rads = Math.toRadians(-angle);

        double fullLength = Math.round((radius / 2d));

        double xPosy = (Math.cos(rads) * fullLength);
        double yPosy = (Math.sin(rads) * fullLength);

        return new Point2D.Double(xPosy, yPosy);

    }

    public class Car extends JPanel {

        private double direction;
        private double speed;
        private BufferedImage background;

        public Car() {
            setOpaque(false);
            direction = Math.random() * 360;
            speed = 5 + (Math.random() * 10);
            int image = 1 + (int) Math.round(Math.random() * 5);
            try {
                String name = "/Car0" + image + ".png";
                background = ImageIO.read(getClass().getResource(name));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            setSize(getPreferredSize());
//            setBorder(new LineBorder(Color.RED));
        }

        public void setDirection(double direction) {
            this.direction = direction;
            revalidate();
            repaint();
        }

        public double getDirection() {
            return direction;
        }

        public void move() {
            Point at = getLocation();
            at.x += (int)(speed * Math.cos(Math.toRadians(-direction)));
            at.y += (int)(speed * Math.sin(Math.toRadians(-direction)));
            setLocation(at);
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = super.getPreferredSize();
            if (background != null) {
                double radian = Math.toRadians(direction);
                double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
                int w = background.getWidth(), h = background.getHeight();
                int neww = (int) Math.floor(w * cos + h * sin);
                int newh = (int) Math.floor(h * cos + w * sin);
                size = new Dimension(neww, newh);
            }
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            int x = (getWidth() - background.getWidth()) / 2;
            int y = (getHeight() - background.getHeight()) / 2;
            g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
            g2d.drawImage(background, x, y, this);
            g2d.dispose();

//            Debug graphics...
//            int cx = getWidth() / 2;
//            int cy = getHeight() / 2;
//
//            g2d = (Graphics2D) g.create();
//            g2d.setColor(Color.BLUE);
//            double radius = Math.min(getWidth(), getHeight());
//            Point2D pointAt = getPointAt(radius, direction);
//            g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//            
//            double xo = cx;
//            double yo = cy;
//            double xPos = cx + pointAt.getX();
//            double yPos = cy + pointAt.getY();
//            
//            g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
//            g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
//            g2d.dispose();
        }
    }
}

Updated with optimized version

I did a little bit of code optimisation with the creation of the car objects (there's still room for improvement) and ehanched the graphics ouput (made it look nicer).

Basically, now, when a car leaves the screen, it's placed in a pool. When another car is required, if possible, it's pulled from the pool, otherwise a new car is made. This has reduced the overhead of creating and destorying so many (relativly) short lived objects, which makes the memory usage a little more stable.

On my 2560x1600 resolution screen (running maximised), I was able to get 4500 cars running simultaneously. Once the object creation was reduced, it ran relatively smoothly (it's never going to run as well as 10, but it didn't suffer from a significant reduction in speed).

public class TestAnimation10 {

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

    public TestAnimation10() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                final TrackPane trackPane = new TrackPane();
                JSlider slider = new JSlider(1, 5000);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        trackPane.setCongestion(((JSlider) e.getSource()).getValue());
                    }
                });
                slider.setValue(5);

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(trackPane);
                frame.add(slider, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class TrackPane extends JPanel {

        private List<Car> activeCarList;
        private List<Car> carPool;
        private int maxCars = 1;
        private List<Point2D[]> points;
        private Ellipse2D areaOfEffect;

        public TrackPane() {

            points = new ArrayList<>(25);

            activeCarList = new ArrayList<>(25);
            carPool = new ArrayList<>(25);
            setLayout(null);

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {

                    Rectangle bounds = areaOfEffect.getBounds();
                    List<Car> tmp = new ArrayList<>(activeCarList);
                    for (Car car : tmp) {
                        car.move();
                        if (!bounds.intersects(car.getBounds())) {
                            remove(car);
                            activeCarList.remove(car);
                            carPool.add(car);
                        }
                    }
                    updatePool();
                    repaint();
                }
            });

            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();

            updateAreaOfEffect();
        }

        protected void updateAreaOfEffect() {
            double radius = Math.max(getWidth(), getHeight()) * 1.5d;
            double x = (getWidth() - radius) / 2d;
            double y = (getHeight() - radius) / 2d;
            areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
        }

        @Override
        public void invalidate() {
//            super.invalidate();
            updateAreaOfEffect();
        }

        protected void updatePool() {
            if (activeCarList.size() < maxCars) {
                int count = Math.min(maxCars - activeCarList.size(), 10);
                for (int index = 0; index < count; index++) {
                    Car car = null;

                    if (carPool.isEmpty()) {
                        car = new Car();
                    } else {
                        car = carPool.remove(0);
                    }

                    double direction = car.getDirection();
                    double startAngle = direction - 180;

                    double radius = areaOfEffect.getWidth();
                    Point2D startPoint = getPointAt(radius, startAngle);

                    int cx = getWidth() / 2;
                    int cy = getHeight() / 2;

                    double x = cx + (startPoint.getX() - car.getWidth() / 2);
                    double y = cy + (startPoint.getY() - car.getHeight() / 2);
                    car.setLocation((int) x, (int) y);

                    Point2D targetPoint = getPointAt(radius, direction);

                    points.add(new Point2D[]{startPoint, targetPoint});

                    add(car);

                    activeCarList.add(car);
                }
            }
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            Font font = g.getFont();
            font = font.deriveFont(Font.BOLD, 48f);
            FontMetrics fm = g.getFontMetrics(font);
            g.setFont(font);
            g.setColor(Color.RED);
            String text = Integer.toString(maxCars);
            int x = getWidth() - fm.stringWidth(text);
            int y = getHeight() - fm.getHeight() + fm.getAscent();
            g.drawString(text, x, y);
            text = Integer.toString(getComponentCount());
            x = getWidth() - fm.stringWidth(text);
            y -= fm.getHeight();
            g.drawString(text, x, y);
            text = Integer.toString(activeCarList.size());
            x = getWidth() - fm.stringWidth(text);
            y -= fm.getHeight();
            g.drawString(text, x, y);
            text = Integer.toString(carPool.size());
            x = getWidth() - fm.stringWidth(text);
            y -= fm.getHeight();
            g.drawString(text, x, y);
        }

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

        public void setCongestion(int value) {
            maxCars = value;
        }

        @Override
        public void validate() {
        }

        @Override
        public void revalidate() {
        }

//        @Override
//        public void repaint(long tm, int x, int y, int width, int height) {
//        }
//
//        @Override
//        public void repaint(Rectangle r) {
//        }
//        public void repaint() {
//        }
        @Override
        protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
            System.out.println(propertyName);
//            // Strings get interned...
//            if (propertyName == "text"
//                            || propertyName == "labelFor"
//                            || propertyName == "displayedMnemonic"
//                            || ((propertyName == "font" || propertyName == "foreground")
//                            && oldValue != newValue
//                            && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
//                super.firePropertyChange(propertyName, oldValue, newValue);
//            }
        }

        @Override
        public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
        }
    }

    protected static Point2D getPointAt(double radius, double angle) {

        double x = Math.round(radius / 2d);
        double y = Math.round(radius / 2d);

        double rads = Math.toRadians(-angle);

        double fullLength = Math.round((radius / 2d));

        double xPosy = (Math.cos(rads) * fullLength);
        double yPosy = (Math.sin(rads) * fullLength);

        return new Point2D.Double(xPosy, yPosy);

    }

    public class Car extends JPanel {

        private double direction;
        private double speed;
        private BufferedImage background;

        public Car() {
            setOpaque(false);
            direction = Math.random() * 360;
            speed = 5 + (Math.random() * 10);
            int image = 1 + (int) Math.round(Math.random() * 5);
            try {
                String name = "/Car0" + image + ".png";
                background = ImageIO.read(getClass().getResource(name));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            setSize(getPreferredSize());
//            setBorder(new LineBorder(Color.RED));
        }

        public void setDirection(double direction) {
            this.direction = direction;
            revalidate();
            repaint();
        }

        public double getDirection() {
            return direction;
        }

        public void move() {
            Point at = getLocation();
            at.x += (int) (speed * Math.cos(Math.toRadians(-direction)));
            at.y += (int) (speed * Math.sin(Math.toRadians(-direction)));
            setLocation(at);
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = super.getPreferredSize();
            if (background != null) {
                double radian = Math.toRadians(direction);
                double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
                int w = background.getWidth(), h = background.getHeight();
                int neww = (int) Math.floor(w * cos + h * sin);
                int newh = (int) Math.floor(h * cos + w * sin);
                size = new Dimension(neww, newh);
            }
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            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);
            int x = (getWidth() - background.getWidth()) / 2;
            int y = (getHeight() - background.getHeight()) / 2;
            g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
            g2d.drawImage(background, x, y, this);
            g2d.dispose();

//            Debug graphics...
//            int cx = getWidth() / 2;
//            int cy = getHeight() / 2;
//
//            g2d = (Graphics2D) g.create();
//            g2d.setColor(Color.BLUE);
//            double radius = Math.min(getWidth(), getHeight());
//            Point2D pointAt = getPointAt(radius, direction);
//            g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//            
//            double xo = cx;
//            double yo = cy;
//            double xPos = cx + pointAt.getX();
//            double yPos = cy + pointAt.getY();
//            
//            g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
//            g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
//            g2d.dispose();
        }

        @Override
        public void invalidate() {
        }

        @Override
        public void validate() {
        }

        @Override
        public void revalidate() {
        }

        @Override
        public void repaint(long tm, int x, int y, int width, int height) {
        }

        @Override
        public void repaint(Rectangle r) {
        }

        @Override
        public void repaint() {
        }

        @Override
        protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
//            System.out.println(propertyName);
//            // Strings get interned...
//            if (propertyName == "text"
//                            || propertyName == "labelFor"
//                            || propertyName == "displayedMnemonic"
//                            || ((propertyName == "font" || propertyName == "foreground")
//                            && oldValue != newValue
//                            && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
//                super.firePropertyChange(propertyName, oldValue, newValue);
//            }
        }

        @Override
        public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
        }
    }
}

ps - I should add 1- My 10 month old loved it 2- It reminded me of the run to work :P

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • +1 fun; also entertaining with [`RobotChase`](http://sourceforge.net/p/robotchase/code/67/tree/trunk/images/) cast members. – trashgod Feb 15 '13 at 21:05
  • @MadProgrammer - Thanks for that. Will study your code and see what I can learn from it (probably a lot!). Just out of curiosity, how long have you been programming in Swing? I've only just started a month ago, but finding it overwhelming as there are so many different ways of doing things and sometimes it hard to figure out whats best etc. Is this just a problem that I have, or is it in general something newbies experience? I'm hoping that I will get as good as programmers like yourself and trashgod one day even though it seems like it's very very far away at the moment! – Force444 Feb 16 '13 at 17:44
  • 4
    I've been using Java/Swing since Java 1.3, somewhere around 1999. The problem your facing is faced by every developer all the time, in fact, I think the more you know, the worst it gets. The difference is, you tend to the experience to be able to discard certain ideas more quickly. Don't be afraid to try something and discard it. The worst thing you can do is continue down a path simply because you've put so much time into it. If you can a better way, it may be better to start again – MadProgrammer Feb 16 '13 at 20:01