1

I'm trying to create a simple panel where a 2-dimensional ball is bouncing up and down. I can't get it to work because for some reason I can't call the repaint method more than once a second. The design is basically that there is an object that can be given "an impulse" with the method move(). Everytime the evaluatePosition method is called, the current position will be calculated through the time that has passed, the velocity and the acceleration. The code for the panel is:

public class Display extends JPanel {
  private MovableObject object = new MovableObject(new Ellipse2D.Double(5,5,50,50));
  private static final int DELAY = 1000;

  public Display(){
    object.move(50,50);
    Timer timer = new Timer(DELAY, new ActionListener(){
      @Override
      public void actionPerformed(ActionEvent e){
        object.evaluatePosition();
        repaint();
      }
    });
    timer.start();
  }
}

@Override
public void paintComponent(Graphics g){
  super.paintComponent(g);
  Graphics2D g2 = (Graphics2D)g;
  g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  g.drawOval((int)object.getPosition().getX(), (int)object.getPosition.getY()
     (int)object.getShape().getWidth(), object.getShape().getHeight());
}

This code works for DELAY=1000 but not for DELAY=100 or DELAY=10 and so on. I read some example code here on SO but they all seem to me like what I already did. So why is my code not working?

EDIT (2016-01-30): Since it really seems to be a performance issue, here's the code for the MovableObject (I just thought it would be irrelevant and you will probably see why):

public class MovableObject {
  // I would really like to use Shape instead of Ellipse2D so that 
  // objects of any shape can be created
  private Ellipse2D.Double shape; 
  private Point position;
  // Vector is my own class. I want to have some easy vector addition and
  // multiplication and magnitude methods
  private Vector velocity = new Vector(0, 0);
  private Vector acceleration = new Vector(0, 0); 
  private Date lastEvaluation = new Date();

  public MovableObject(Ellipse2D.Double objectShape){
    shape = objectShape;
  }

  public void evaluatePosition(){
    Date currentTime = new Date();
    long deltaTInS = (currentTime.getTime()-lastEvaluation.getTime())/1000;
    // s = s_0 + v*t + 0.5*a*t^2
    position = new Point((int)position.getX()+ (int)(velocity.getX()*deltaTInS) + (int)(0.5*acceleration.getX()*deltaTInS*deltaTInS),
                        (int)position.getY()+ (int)(velocity.getY()*deltaTInS) + (int)(0.5*acceleration.getY()*deltaTInS*deltaTInS));
    lastEvaluation = currentTime;
  }
}

public void move(Vector vector){
    velocity = velocity.add(vector);
    evaluatePosition(); 
}

public Point getPosition(){
    return position;
}

public Ellipse2D.Double getShape(){
    return shape;
}

My move method does not change position but velocity. Please notice that I just changed the shape Object from Shape to Ellipse2D for testing if my code has a performance issue because of the additional code. So if you think this is more complex than it needs to be: I actually want to add some complexity so that the MovableObject can have the shape of any subclass of shape. I've seen a lot of code that seemed more complex to me and rendered fast. So I'd like to know what's wrong with this (besides the fact that it's a bit too complex for just rendering an ellipse).

matkin
  • 42
  • 1
  • 6
  • Possibly relevant? https://pavelfatin.com/low-latency-painting-in-awt-and-swing/ It's probably that paint() itself is too slow. – ToxicTeacakes Jan 29 '16 at 06:30
  • What's `MoveableObject` look like? – MadProgrammer Jan 29 '16 at 06:39
  • @ToxicTeacakes [4500 objects @ approximately 25fps](http://stackoverflow.com/questions/14886232/swing-animation-running-extremely-slow/14902184#14902184), [10, 000 objecst @ approximately 25fps](http://stackoverflow.com/questions/23417786/rotating-multiple-images-causing-flickering-java-graphics2d/23419824#23419824) - these are by no means optimised in any massive way, and I'll be the first to admit they are overly simple examples, but ... – MadProgrammer Jan 29 '16 at 06:42

1 Answers1

2

Okay, so this is a simple example, based on the out-of-context code snippet you left which doesn't seem to have any problems. It has variable speed controlled by a simple slider...

Bounce

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Display extends JPanel {

    public static void main(String[] args) {
        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 Display());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    private MovableObject object = new MovableObject(new Ellipse2D.Double(5, 5, 50, 50));
    private int delay = 40;

    private Timer timer = new Timer(40, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            object.evaluatePosition(getSize());
            repaint();
        }
    });

    private JSlider slider = new JSlider(5, 1000);

    public Display() {
        object.move(50, 50);

        slider = new JSlider(5, 1000);
        slider.setSnapToTicks(true);
        slider.setMajorTickSpacing(10);
        slider.setMinorTickSpacing(5);
        setLayout(new BorderLayout());
        add(slider, BorderLayout.SOUTH);

        // This is simply designed to put an artificate delay between the
        // change listener and the time the update takes place, the intention
        // is to stop it from pausing the "main" timer...
        Timer delay = new Timer(250, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (timer != null) {
                    timer.stop();
                }
                timer.setDelay(slider.getValue());
                timer.start();
            }
        });

        slider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                delay.restart();
                repaint();
            }
        });

        slider.setValue(40);
    }

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

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.draw(object.getTranslatedShape());
        FontMetrics fm = g2.getFontMetrics();
        String text = Integer.toString(slider.getValue());
        g2.drawString(text, 0, fm.getAscent());
        g2.dispose();
    }

    public class MovableObject {

        private Shape shape;
        private Point location;

        private int xDelta, yDelta;

        public MovableObject(Shape shape) {
            this.shape = shape;
            location = shape.getBounds().getLocation();
            Random rnd = new Random();
            xDelta = rnd.nextInt(8);
            yDelta = rnd.nextInt(8);

            if (rnd.nextBoolean()) {
                xDelta *= -1;
            }
            if (rnd.nextBoolean()) {
                yDelta *= -1;
            }
        }

        public void move(int x, int y) {
            location.setLocation(x, y);
        }

        public void evaluatePosition(Dimension bounds) {
            int x = location.x + xDelta;
            int y = location.y + yDelta;

            if (x < 0) {
                x = 0;
                xDelta *= -1;
            } else if (x + shape.getBounds().width > bounds.width) {
                x = bounds.width - shape.getBounds().width;
                xDelta *= -1;
            }
            if (y < 0) {
                y = 0;
                yDelta *= -1;
            } else if (y + shape.getBounds().height > bounds.height) {
                y = bounds.height - shape.getBounds().height;
                yDelta *= -1;
            }

            location.setLocation(x, y);
        }

        public Shape getTranslatedShape() {
            PathIterator pi = shape.getPathIterator(AffineTransform.getTranslateInstance(location.x, location.y));
            GeneralPath path = new GeneralPath();
            path.append(pi, true);
            return path;
        }

    }
}

You could also have a look at

for some more examples...

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • The problem is that my code is not out of context. I know how to use the timer, and it works for a delay of 1000ms. It stops working for a delay of 100ms and also just the `repaint()` method. I've seen the examples you posted. As far as I understand it, this is a performance issue, because the paint() method doesn't finish before the next repaint() is called. But my code is not 10,000 objects rendered at 25fps it's one ellipse rendered at 1fps. I added some `MovableObject` code, but it's really simple, which is why I didn't add it in the first place. – matkin Jan 30 '16 at 10:58
  • Okay, you've presented some code, it's incomplete, it's uncompilable and un-runnable, it's missing vital context, so, it's out-of-context. I have a runnable example that runs from anything from 5ms - 1000ms, which is more than you provided us. I've seen repaint work just fine as low as 1ms. Have you tried running my example code? Did it work? Did it repeate your problem? – MadProgrammer Jan 30 '16 at 19:56
  • Event with the addition of your `MovableObject`, the code is still not runnable, so it's still out-of-context. 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 Jan 30 '16 at 20:10
  • I tested `long deltaTInS = (currentTime.getTime() - lastEvaluation.getTime()) / 1000;` which at 1000ms produces `1`, but at 100ms produces `0`, which would mean that your object will most likely (I can't actually test it) will never move unless the time is 1000ms, which suggests to me, this isn't an issue with the painting subsystem, but your code, which you don't seem to have made any attempt to debug (a simple `System.out.println` would have highlighted this issue) – MadProgrammer Jan 30 '16 at 20:13
  • Oh god, that was it. Thanks a lot! – matkin Jan 30 '16 at 21:27