Cubic Spline Interpolation - LWJGL

Hi there,

I’ve been trying to get cubic spline interpolation into a sample project for some time, without any success.

I’m trying to simulate a network environment whereby the player controlls one sprite, another sprite follows using cubic spline interpolation.

I’ve based this sample on http://archive.gamedev.net/reference/articles/article914.asp, but I’ve not had much luck.

Has anyone had any experience of cubic spline interpolation or is even brave enough to take a look at this sample?

Interpolation project: http://pastebin.com/8yR5hGCH

Cubic Interpolator Class: http://pastebin.com/p1L9k4SJ

Netbeans sample project:
http://www.fileswap…plines.zip.html

Maybe taking a look (or a copy) here might help: http://java.net/projects/openmali/sources/svn/content/trunk/src/org/openmali/curves/CubicBezierSpline.java?rev=218

I have a spline interpolator that I want to share.
It is spline interpolation that you want right? Not bezier curves?

If you want to have more dimensions, just interpolate each dimension separately.




public class SplineInterpolation {

	public static final double CR00 = -0.5;
	public static final double CR01 = 1.5;
	public static final double CR02 = -1.5;
	public static final double CR03 = 0.5;

	public static final double CR10 = 1.0;
	public static final double CR11 = -2.5;
	public static final double CR12 = 2.0;
	public static final double CR13 = -0.5;

	public static final double CR20 = -0.5;
	public static final double CR21 = 0.0;
	public static final double CR22 = 0.5;
	public static final double CR23 = 0.0;

	public static final double CR30 = 0.0;
	public static final double CR31 = 1.0;
	public static final double CR32 = 0.0;
	public static final double CR33 = 0.0;

	public static double interpolateLinearEnds(double x,
			double... internalKnots) {
		return interpolate(x, getLinearEndKnots(internalKnots));
	}

	public static double interpolate(double x, double... knots) {
		int nknots = knots.length;
		int nspans = nknots - 3;
		int knot = 0;
		if (nspans < 1) {
			System.out.println(SplineInterpolation.class.getName()
					+ " Spline has too few knots");
			return 0.0;
		}
		x = MathUtils.clamp(x, 0.0, 0.9999) * nspans;
		// println("clamped x: " + x);
		int span = (int) x;
		// println("span before: " + span);
		if (span >= nknots - 3) {
			span = nknots - 3;
		}
		// println("span after: " + span);
		x -= span;
		knot += span;

		// println("knot: " + knot + " knots.length: " + knots.length);

		double knot0 = knots[knot];
		double knot1 = knots[knot + 1];
		double knot2 = knots[knot + 2];
		double knot3 = knots[knot + 3];

		double c3 = CR00 * knot0 + CR01 * knot1 + CR02 * knot2 + CR03 * knot3;
		double c2 = CR10 * knot0 + CR11 * knot1 + CR12 * knot2 + CR13 * knot3;
		double c1 = CR20 * knot0 + CR21 * knot1 + CR22 * knot2 + CR23 * knot3;
		double c0 = CR30 * knot0 + CR31 * knot1 + CR32 * knot2 + CR33 * knot3;
		return ((c3 * x + c2) * x + c1) * x + c0;
	}

	public static void interpolate(double x, double[] result, double[]... knots) {
		int nknots = knots.length;
		int nspans = nknots - 3;
		int knot = 0;
		if (nspans < 1) {
			System.out.println(SplineInterpolation.class.getName()
					+ " Spline has too few knots");
			return;
		}
		x = MathUtils.clamp(x, 0.0, 0.9999) * nspans;
		// println("clamped x: " + x);
		int span = (int) x;
		// println("span before: " + span);
		if (span >= nknots - 3) {
			span = nknots - 3;
		}
		// println("span after: " + span);
		x -= span;
		knot += span;

		// println("knot: " + knot + " knots.length: " + knots.length);

		int dimension = result.length;
		for (int i = 0; i < dimension; i++) {
			double knot0 = knots[knot][i];
			double knot1 = knots[knot + 1][i];
			double knot2 = knots[knot + 2][i];
			double knot3 = knots[knot + 3][i];

			double c3 = CR00 * knot0 + CR01 * knot1 + CR02 * knot2 + CR03
					* knot3;
			double c2 = CR10 * knot0 + CR11 * knot1 + CR12 * knot2 + CR13
					* knot3;
			double c1 = CR20 * knot0 + CR21 * knot1 + CR22 * knot2 + CR23
					* knot3;
			double c0 = CR30 * knot0 + CR31 * knot1 + CR32 * knot2 + CR33
					* knot3;

			result[i] = ((c3 * x + c2) * x + c1) * x + c0;
		}
	}

	public static double[] interpolateArray(double[] inputs, double... knots) {
		double[] result = new double[inputs.length];
		for (int i = 0; i < inputs.length; i++) {
			result[i] = interpolate(inputs[i], knots);
		}
		return result;
	}

	public static double[] interpolateLinearEndsArray(double[] inputs,
			double... internalKnots) {
		double[] knots = getLinearEndKnots(internalKnots);
		double[] result = new double[inputs.length];
		for (int i = 0; i < inputs.length; i++) {
			result[i] = interpolate(inputs[i], knots);
		}
		return result;
	}

	public static double[] interpolateLinearEndsArray(double minInputValue,
			double maxInputValue, int n, double... internalKnots) {
		double[] inputs = new double[n];
		double stepLength = (maxInputValue - minInputValue) / (n - 1);
		for (int i = 0; i < n; i++) {
			inputs[i] = minInputValue + i * stepLength;
		}
		return interpolateLinearEndsArray(inputs, internalKnots);
	}

	// Default range between 0.0 and 1.0
	public static double[] interpolateLinearEndsArray(int n,
			double... internalKnots) {
		return interpolateLinearEndsArray(0.0, 1.0, n, internalKnots);
	}

	public static double[] getLinearEndKnots(double... internalKnots) {
		double[] result = new double[internalKnots.length + 2];
		double diff1 = internalKnots[1] - internalKnots[0];
		double diff2 = internalKnots[internalKnots.length - 1]
				- internalKnots[internalKnots.length - 2];
		result[0] = internalKnots[0] - diff1;
		result[result.length - 1] = internalKnots[internalKnots.length - 1]
				+ diff2;
		for (int i = 1; i < result.length - 1; i++) {
			result[i] = internalKnots[i - 1];
		}
		return result;
	}

}


Edit: here is the clamp function:


	public static double clamp(double x, double a, double b) {
		return (x < a ? a : (x > b ? b : x));
	}


Here is also an interpolator ported from C++ to Java with the original version in the book “Numerical Recipies”:



public abstract class BaseInterpolator {
	int n;
	int mm;
	int jsav;
	int cor;
	int dj;
	double[] xx;

	public BaseInterpolator(double[] x, int m) {
		n = x.length;
		mm = m;
		jsav = 0;
		cor = 0;
		xx = x;
		dj = Math.min(1, (int) Math.pow((double) n, 0.25));
	}

	public double interpolate(double x) {
		int jlo = (cor != 0) ? hunt(x) : locate(x);
		return rawInterpolate(jlo, x);
	}

	public void interpolate(double x, double[] input, double[] result) {
		int jlo = (cor != 0) ? hunt(x) : locate(x);
		rawInterpolate(jlo, x, input, result);
	}

	public double interpolate(double x, double[] input) {
		int jlo = (cor != 0) ? hunt(x) : locate(x);
		return rawInterpolate(jlo, x, input);
	}

	public int locate(final double x) {
		int ju, jm, jl;
		if (n < 2 || mm < 2 || mm > n) {
			System.out.println(this + " Locate size error");
			return 0;
		}
		boolean ascnd = (xx[n - 1] >= xx[0]);
		jl = 0;
		ju = n - 1;
		while (ju - jl > 1) {
			jm = (ju + jl) >> 1;
			if (x >= xx[jm] == ascnd) {
				jl = jm;
			} else {
				ju = jm;
			}
		}
		cor = Math.abs(jl - jsav) > dj ? 0 : 1;
		jsav = jl;
		return Math.max(0, Math.min(n - mm, jl - ((mm - 2) >> 1)));
	}

	public int hunt(final double x) {
		int jl = jsav;
		int jm, ju;
		int inc = 1;
		if (n < 2 || mm < 2 || mm > n) {
			System.out.println(this + " Hunt size error");
			return 0;
		}
		boolean ascnd = (xx[n - 1] > xx[0]);
		if (jl < 0 || jl > n - 1) {
			jl = 0;
			ju = n - 1;
		} else {
			if (x >= xx[jl] == ascnd) {
				for (;;) {
					ju = jl + inc;
					if (ju >= n - 1) {
						ju = n - 1;
						break;
					} else if (x < xx[ju] == ascnd) {
						break;
					} else {
						jl = ju;
						inc += inc;
					}
				}
			} else {
				ju = jl;
				for (;;) {
					jl = jl - inc;
					if (jl <= 0) {
						jl = 0;
						break;
					} else if (x >= xx[jl] == ascnd) {
						break;
					} else {
						ju = jl;
						inc += inc;
					}
				}
			}
		}
		while (ju - jl > 1) {
			jm = (ju + jl) >> 1;
			if (x >= xx[jm] == ascnd) {
				jl = jm;
			} else {
				ju = jm;
			}
		}
		cor = Math.abs(jl - jsav) > dj ? 0 : 1;
		jsav = jl;
		return Math.max(0, Math.min(n - mm, jl - ((mm - 2) >> 1)));
	}

	public double rawInterpolate(int jlo, double x) {
		return 0.0;
	}

	public double rawInterpolate(int jlo, double x, double[] input) {
		return 0.0;
	}

	public void rawInterpolate(int jlo, double x, double[] input,
			double[] result) {
	}

}


public abstract class DoubleBaseInterpolator extends BaseInterpolator {

	double[] yy;

	public DoubleBaseInterpolator(double[] x, double[] y, int m) {
		super(x, m);
		this.yy = y;
	}

}



public class CubicSplineInterpolator extends DoubleBaseInterpolator {

	double[] y2;

	public CubicSplineInterpolator(double[] xValues, double[] yValues,
			double yp1, double ypn) {
		super(xValues, yValues, 2);
		y2 = new double[xValues.length];
		sety2(xValues, yValues, yp1, ypn);
	}

	public CubicSplineInterpolator(double[] xValues, double[] yValues) {
		super(xValues, yValues, 2);
		y2 = new double[xValues.length];
		sety2(xValues, yValues, 1.e99, 1.e99);
	}

	private void sety2(double[] xv, double[] yv, double yp1, double ypn) {
		int i, k;
		double p, qn, sig, un;
		int n = y2.length;
		double[] u = new double[n - 1];
		if (yp1 > 0.99e99) {
			y2[0] = u[0] = 0.0;
		} else {
			y2[0] = -0.5;
			u[0] = (3.0 / (xv[1] - xv[0]))
					* ((yv[1] - yv[0]) / (xv[1] - xv[0]) - yp1);
		}
		for (i = 1; i < n - 1; i++) {
			sig = (xv[i] - xv[i - 1]) / (xv[i + 1] - xv[i - 1]);
			p = sig * y2[i - 1] + 2.0;
			y2[i] = (sig - 1.0) / p;
			u[i] = (yv[i + 1] - yv[i]) / (xv[i + 1] - xv[i])
					- (yv[i] - yv[i - 1]) / (xv[i] - xv[i - 1]);
			u[i] = (6.0 * u[i] / (xv[i + 1] - xv[i - 1]) - sig * u[i - 1]) / p;
		}
		if (ypn > 0.99e99) {
			qn = un = 0.0;
		} else {
			qn = 0.5;
			un = (3.0 / (xv[n - 1] - xv[n - 2]))
					* (ypn - (yv[n - 1] - yv[n - 2]) / (xv[n - 1] - xv[n - 2]));
		}
		y2[n - 1] = (un - qn * u[n - 2]) / (qn * y2[n - 2] + 1.0);
		for (k = n-2; k >= 0; k--) {
			y2[k] = y2[k] * y2[k + 1] + u[k];
		}
	}

	@Override
	public double rawInterpolate(int jl, double x) {
		int klo = jl, khi = jl + 1;
		double y, h, b, a;
		h = xx[khi] - xx[klo];
		if (h == 0.0) {
			System.out.println(this + " bad input");
			return 0.0;
		}
		a = (xx[khi] - x) / h;
		b = (x - xx[klo]) / h;
		y = a * yy[klo] + b * yy[khi]
				+ ((a * a * a - a) * y2[klo] + (b * b * b - b) * y2[khi])
				* (h * h) / 6.0;
		return y;
	}

	public static void main(String[] args) {
		CubicSplineInterpolator li = new CubicSplineInterpolator(new double[] {
				0.0, 1.2, 3.0, 5.0}, new double[] { 5.0, 2.0, -11.0, 7 });
		for (int i = 0; i < 300; i++) {
			double x = i * 0.01;
			System.out.println("input: " + x + " output: " + li.interpolate(x));
		}
	}

}


The problem with this is that it is slower than the specialized version in my previoius post but it allows a non-even distribution of the “x-values”. The other is just a uniform, but very quick, variant.

Hi there,

Thanks for all your reponses.

Due to simplicity mainly i’m trying to get the cubic spline algorithm that is mentioned here:

http://archive.gamedev.net/reference/articles/article914.asp

I’ve made some progress, but the control points of the curve don’t get plotted correctly. I’ve made a video here.

I’ve updated my sample if anyone wished to take a look (netbeans project)
http://www.fileswap.com/dl/Fjl3EMrz/CubicSplines.rar.html

Ok, now I understand more what you are trying to do.

It looks from the example that it is actually a bezier curve that you are using.
I also think that the 1 second, hardcoded constant, seems rather arbitrary and should instead depend on the update frequency. Although the plotted control points in the video looks OK.
My guess is that it is probably a bug in the curve implementation or the parameters sent to it.

My suggestion is that you debug the curve code by creating example curve segments and plot them. You can also try the GeneralPath class (or Path2D) in java.awt.geom (use the curveTo() to specify all the four points for each segment) and see if your results match.

Here is an example Applet that uses Splines:


import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;

import java.applet.Applet;

public class Spline extends Applet {
	private static final long serialVersionUID = 1L;
	
	Point[] points;	//points to be interpolated
	Point[] control;	//control points
	int numpoints;
	double t;	//time variable
	static final double k = .05; //partition length
	int moveflag;	//point movement

	//this method initializes the applet
	
	public void init() {
		//start off with 6 points
		points = new Point[6];
		control = new Point[6];
		numpoints = 6;
		moveflag = numpoints;
		int increment = (int)((getWidth()-60)/(numpoints-1));
		for(int i=0;i<numpoints;i++) {
			points[i] = new Point((i*increment)+30,(int)(getHeight()/2));
		}


	}
	
	//this method is called by the repaint() method
	public void update(Graphics g) {
		paint(g);
	}
	
	public void paint(Graphics g) {
		//points to be plotted
		double x1,y1,x2,y2;
		//Clear screen and set colors
		setBackground(Color.white);
		g.setColor(Color.white);
		g.fillRect(0,0,getWidth(),getHeight());
		g.setColor(Color.black);

		
		//Change interpolating points into control points
		control[0] = new Point(points[0].x,points[0].y);
		control[numpoints-1] = new Point(points[numpoints-1].x,points[numpoints-1].y);
		
		x1= 1.6077*points[1].x - .26794 * points[0].x - .43062 * points[2].x + .11483 * points[3].x - .028708 * points[4].x + .004785*points[5].x;
		y1= 1.6077*points[1].y - .26794 * points[0].y - .43062 * points[2].y + .11483 * points[3].y - .028708 * points[4].y + .004785*points[5].y;
		control[1] = new Point(x1,y1);
		
		x1= -.43062 * points[1].x + .07177 * points[0].x + 1.7225 * points[2].x - .45933 * points[3].x + .11483 * points[4].x - .019139 * points[3].x;
		y1= -.43062 * points[1].y + .07177 * points[0].y + 1.7225 * points[2].y - .45933 * points[3].y + .11483 * points[4].y - .019139 * points[3].y;
		control[2] = new Point(x1,y1);
		
		x1= .11483 * points[1].x - .019139 * points[0].x - .45933 * points[2].x + 1.7225 * points[3].x - .43062 * points[4].x + .07177 * points[5].x;
		y1= .11483 * points[1].y - .019139 * points[0].y - .45933 * points[2].y + 1.7225 * points[3].y - .43062 * points[4].y + .07177 * points[5].y;
		control[3] = new Point(x1,y1);
		
		x1=- .028708 * points[1].x + .004785 * points[0].x + .114835 * points[2].x - .43062 * points[3].x + 1.6077 * points[4].x - .26794 * points[5].x;
		y1=- .028708 * points[1].y + .004785 * points[0].y + .114835 * points[2].y - .43062 * points[3].y + 1.6077 * points[4].y - .26794 * points[5].y;
		control[4] = new Point(x1,y1);
		
		//Plot points
		for(int i=0;i<numpoints;i++)
			g.fillOval((int)points[i].x-2,(int)points[i].y-2,4,4);

		//draw n bezier curves using Bernstein Polynomials
		x1=points[0].x;
		y1=points[0].y;
		for(int i=1;i<numpoints;i++) {
			for(t=i-1;t<=i;t+=k) {
				double tValue = (t-(i-1));
				x2= points[i-1].x + tValue * (-3*points[i-1].x + 3 * (.6667 * control[i-1].x + .3333 * control[i].x) + tValue * (3 * points[i-1].x - 6 * (.6667 * control[i-1].x + .3333 * control[i].x) + 3 * (.3333 * control[i-1].x + .6667*control[i].x) + (-points[i-1].x + 3 * (.6667 * control[i-1].x + .3333 * control[i].x) - 3 * (.3333 * control[i-1].x + .6667 * control[i].x) + points[i].x) * tValue));
				y2= points[i-1].y + tValue * (-3*points[i-1].y + 3 * (.6667 * control[i-1].y + .3333 * control[i].y) + tValue * (3 * points[i-1].y - 6 * (.6667 * control[i-1].y + .3333 * control[i].y) + 3 * (.3333 * control[i-1].y + .6667*control[i].y) + (-points[i-1].y + 3 * (.6667 * control[i-1].y + .3333 * control[i].y) - 3 * (.3333 * control[i-1].y + .6667 * control[i].y) + points[i].y) * tValue));
				
				g.drawLine((int)x1,(int)y1,(int)x2,(int)y2);
				x1=x2;
				y1=y2;
			}
		}

	}
	

	//Check if user has clicked on point
	public boolean mouseDown(Event evt, int x, int y) {
		Point p = new Point(x,y);
		for(int i=0;i<numpoints;i++) {
			for(int j=-8;j<15;j++) {
				for(int l=-8;l<15;l++) {
					if(p.equals(new Point(points[i].x+j, points[i].y+l))) {
						//set moveflag to the ith point
						moveflag=i;
					}
						
				}
			}
		}
		return true;
	}
	public boolean mouseDrag(Event evt, int x, int y) {
		//check if user is trying to drag an old point
		if(moveflag < numpoints) {
			//move the point and redraw screen
			points[moveflag].move(x,y);
			repaint();
		}
		return true;
	}
		//if user unclicks mouse, reset moveflag
	public boolean mouseUp(Event evt, int x, int y) {
		moveflag = 6;
		return true;
	}
}
class Point {
	double x, y;
	Point(double newX, double newY) {
		x = newX;
		y = newY;
	}
	void move(double moveX, double moveY) {
		x = moveX;
		y = moveY;
	}
	public boolean equals(Point p) {
		if ((int)p.x == (int)x && (int)p.y == (int)y) return true;
		return false;
	}
}

Hi thanks,

Thanks for the comments. I’ve not had much time to look into this but I have made some tweaks based on some suggestions made at game dev.net

This is the current progress of it so far.

With an updated formula it seems to work but the motion is more linear than cubic… Any thoughts?

http://pastebin.com/gtCTxK6n