Simple Animation, Slight Jerkiness

Fellow Java Developers:

I’ve scoured the board for every topic related to animation and timing (and came up with a lot of good ideas and suggestions). Which brings me to my code. I have a simple ball bouncing back and forth (a Pong game). I’m not sure if the slight jerkiness is a performance issue specific to my platform (Ubuntu Linux), or maybe my expectations are too high. I’ve tried the code both with and without using interpolation. Interpolation did help a bit. There are a few values hard-coded in here that I’m aware of (like the boundaries that dictate when to change the direction of the ball). I’m aware of those, I’ve changed them in the actual game version, I minimized the code just to illustrate the animation itself. So here are my questions:

  1. Is this level of smoothness relatively acceptable or have I made a really bad (or obvious) mistake?
  2. Is the use of java.util.Timer and java.util.TimerTask typical for game logic? (Would the LWJGL timer be better?)

I realize this is a very simple example, but I’d like to make sure I have the basic foundation correct before I move on to something larger. At this point I’ve pretty much accepted that it’s as smooth as the animation is going to get so I’ll just complete the game now barring some kind of revelation.

I hope this code isn’t too long for a post, I wanted to make sure it could be compiled. I also wanted to point out that the formal nature of my post is just how I communicate, those questions aren’t out of a textbook. I’m not a student and this isn’t some kind of homework assignment. (I graduated with an AAS 13 months ago). Thank you.

Sincerely,
Nero

public class Pong {
	
	private PongWindow window = new PongWindow("Pong", 800, 600);

	public static void main(String[] args) {
		Pong pong = new Pong();
	}

}
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

import com.sun.opengl.util.FPSAnimator;

public class PongWindow extends Frame implements GLEventListener {
	private final Ball ball = new Ball();
	private final GLCanvas canvas = new GLCanvas();
	private final GLU glu = new GLU();
	private FPSAnimator animator = new FPSAnimator(canvas, 60, true);
	
	public PongWindow(String title, int width, int height) {
		canvas.addGLEventListener(this);
		add(canvas);
		setSize(width, height);
		animator.start();
		
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				new Thread(new Runnable() {
					public void run() {
						System.exit(0);
						animator.stop();
					}
				}).start();
			}
		});
		
		setLocationRelativeTo(null);
		setVisible(true);
	}

	public void display(GLAutoDrawable drawable) {
		final GL gl = drawable.getGL();
		
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT);

		ball.draw(gl);
	}

	public void init(GLAutoDrawable drawable) {
		final GL gl = drawable.getGL();
		
		gl.setSwapInterval(1);
		
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		gl.glColor3f(0.0f, 0.0f, 0.0f);
		gl.glPointSize(10.0f);
		
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity(); 
		glu.gluOrtho2D(0.0, getWidth(), 0.0, getHeight());
	}

	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2) {}
	public void reshape(GLAutoDrawable drawable, int i, int x, int width, int height) {}
}
import java.util.Timer;
import java.util.TimerTask;

import javax.media.opengl.GL;

public class Ball {
	private static int TIME_BETWEEN_UPDATES = 20000000; // nanoseconds
	private float x, y, radius, speed;
	private float interpolation; // value between 0.0 and 1.0
	private long next_update;
	private Timer timer;
	
	public Ball() {
		timer = new Timer();
		timer.schedule(new BallMovement(), 0, TIME_BETWEEN_UPDATES/1000000); // convert to milliseconds
		next_update = System.nanoTime();
		x = 400.0f;
		y = 300.0f;
		radius = 5.0f;
		speed = 5.0f;
	}
	
	public void draw(GL gl) {
		interpolation = (System.nanoTime() + TIME_BETWEEN_UPDATES - next_update) / TIME_BETWEEN_UPDATES;
		float view_position = x + (speed * interpolation);
		gl.glBegin(GL.GL_POINTS);
			gl.glColor3f(1.0f, 1.0f, 1.0f);
			gl.glVertex3f(view_position, y, 0.0f);
		gl.glEnd();
	}
	
	public void move() {
		if(x >= (800 - radius*2) || x <= 0) {
			changeDirection();
		}
		
		x += speed;
	}
	
	private void changeDirection() {
		speed = -speed;
	}
	
	class BallMovement extends TimerTask {
		public void run() {
			move();
			next_update = System.nanoTime() + TIME_BETWEEN_UPDATES;
		}
	}
}

The Timer is not awfully accurate. Just leave it out (you are already using a FPSAnimator to limit the amount of calculations needed)


import javax.media.opengl.GL;

public class Ball {
	private float x, y, radius, speed;
	private long last_update;
	
	public Ball() {
		last_update = System.nanoTime();
		x = 400.0f;
		y = 300.0f;
		radius = 5.0f;
		speed = 0.2f; // in units per millisecond
	}
	
	public void draw(GL gl) {
		move();
		gl.glBegin(GL.GL_POINTS);
			gl.glColor3f(1.0f, 1.0f, 1.0f);
			gl.glVertex3f(x, y, 0.0f);
		gl.glEnd();
	}
	
	public void move() {
		long current= System.nanoTime();
		if(x >= (800 - radius*2) || x <= 0) {
			changeDirection();
		}
		float dt= current-last_update;
		dt/=1000000;
		x += speed*dt;
		last_update= current;
	}
	
	private void changeDirection() {
		speed = -speed;
	}
}

Thank you for your response. I tried the code you used and it does smooth the animation out considerably. Unfortunately it creates two scenarios I’d like to avoid:

  1. The logic becomes coupled with the animation.
  2. Sometimes the ball inexplicably disappears. I think this is a side effect of the ball update being coupled with the animation as it seems to happen when I cause a tooltip to popup by moving the mouse over the standard application buttons (close, minimize, maximize) or when the application is minimized or put into the background. I think it has something to do with the application losing track of the ball’s location when something interrupts the graphic updates. It doesn’t happen every time, sometimes it takes a minute to replicate. This doesn’t happen when I use my original code with a Timer.

I guess this leaves me with needing to create a timer with more accuracy than java.util.Timer. I’m at a loss for how to do this because the only way I know how [ using Thread.sleep() ] would still only have millisecond accuracy. I’ll take a look at LWGJL’s Timer later today to see if I can get better accuracy.

Thanks again,
Nero

Regarding smooth animation in games this is somewhat unavoidable. If you do more complex animation, you can calculate the animation parameters in an offrender thread, but you wouldn’t directly manipulate object locations. With your interpolation approach you were already thinking in that direction.

Don’t know what’s causing this, but it is not a direct cause of the animation on the render thread. There’s probably something we overlooked.

This won’t help anything. If you directly manipulate the objects location from an offrender thread you will introduce two problems:

  1. aliasing

  2. concurrency

  3. Since you can’t rely on the fact that the Timer callback would be synchronized with the rendering you will have situations where the timer update will be called right before the rendering and situations where the timer update will be called after the rendering. This will result in the ball “jumping” every now and then.

  4. Since you will introduce a different Thread, you will at least have to make the coordinate members volatile to ensure that they are written and read in the order you would expect. Otherwise situations might occur where the animation thread reads valus from the CPU cache while the Timer updates the variable in main memory. This gets more complicated on multi CPU environments. Worse since you can’t be sure if the floating point access is atimoc on all platforms (updating multiple values surely isn’t) you might have to surround the variable access in a synchronized block (which might degrade performance).

I found the problem with my original code, it was a pretty newbie mistake. I overlooked an implicit cast which meant the interpolation wasn’t doing anything at all. I did some testing and found out the value of the interpolation was 0.0 almost all the time, meaning it wasn’t predicting the graphics at all. Now that I’ve fixed the problem the graphics are smooth without needing to couple the game updates with the animation. The only thing I had to revise from my original code was to explicitly cast:

interpolation = ((float)(System.nanoTime() + TIME_BETWEEN_UPDATES - next_update) / (float)TIME_BETWEEN_UPDATES);

Thanks for the help,
Nero