I’m trying to achieve constant speed on a path using the LibGDX CatmullRomSpline and I’m having problems getting it to work. I’ve tried researching on this topic a lot including reading the LibGDX wiki, but their explanation for achieving constant speed doesn’t really make sense and I wasn’t able to get their method to work. https://github.com/libgdx/libgdx/wiki/Path-interface-&-Splines In my case, the derivative values are very large (in the hundreds) so when dividing a number between 0-1 by the derivative the result is very small and the movement is very slow and still not constant. So I’m not sure exactly how their example works.
In my example I have a couple visual aids coinciding with the speed of the ball, the bar at the bottom of the screen increases in length as the speed increases and the color also changes from white to red as the speed increases.
In the act() method of MyPath.java I have two sections commented out starting with [1] and [2]. The first one is normal with the variable speed through the path and the second one is my failed attempt at getting the LibGDX wiki constant speed to work. So just un-comment these lines to switch between the two versions.
My idea for constant speed involves figuring out the speed based on the total length of the path (using the approxLength(1000) method on the spline), then using the derivative function to determine the actual speed at a given instant, and adjusting the percentage value sent into the spline to compensate for the speed changes in order to make the speed constant. However, I don’t quite understand what the derivative function actually represents. I posted a question about the derivative function earlier, but based a comment I received I figured it might be easier to ask about achieving constant speed instead. Here is my previous question on the derivative function: LibGDX CatmullRomSpline Derivative Meaning?
Any ideas on how to achieve constant speed in my example (or explaining what the derivative function for the CatmullRomSpline actually represents so I could better understand how to use it) would be greatly appreciated.
For anyone who'd like to run the program, here are the two image files I created for my example (add these to the root of the assets folder): http://dropshots.com/Tekker/date/2015-09-19
Here is my example code:
DesktopLauncher.java: (changed desktop window width and height to 1000)
public class DesktopLauncher {
public static void main (String[] arg) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.width = 1000;
config.height = 1000;
new LwjglApplication(new TEST(), config);
}
}
TEST.java:
public class TEST extends Game {
Stage stage;
MyPath path;
@Override
public void create () {
stage = new Stage();
stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
Gdx.input.setInputProcessor(stage);
path = new MyPath(1000, 1000);
stage.addActor(path);
}
@Override
public void render () {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
@Override
public void dispose(){
stage.dispose();
super.dispose();
}
}
MyPath.java:
public class MyPath extends WidgetGroup {
Image start, end, path, bar1, horizontal;
float time, percent, dVal, pathLength, dMax=1000, cycle=6, maxPercent, deltaCycle;
CatmullRomSpline<Vector2> catmull;
Vector2 result = new Vector2();
Vector2 previousResult = new Vector2(50,150);
Vector2 derivative = new Vector2();
Vector2 previousDerivative = new Vector2();
Vector2[] points = {
new Vector2(50,150), new Vector2(50,150),
new Vector2(400,800), new Vector2(600,150), new Vector2(700,400),
new Vector2(860,150), new Vector2(860,150)
};
boolean print = true;
public MyPath(int width, int height){
this.setSize(width, height);
catmull = new CatmullRomSpline<Vector2>(points, false);
createPath();
createBar();
pathLength = catmull.approxLength(1000);
}
@Override
public void act(float delta){
// [1] VARIABLE SPEED
//time += delta;
//percent = (time / cycle) % 1;
// [2] CONSTANT SPEED FAIL!
//catmull.derivativeAt(previousDerivative, percent);
//time += delta;
//percent = ((time / cycle) / previousDerivative.len() ) % 1;
catmull.valueAt(result, percent);
path.setPosition(result.x, this.getHeight() - result.y);
updateSpeedVisuals();
debugPrint();
previousResult.set(result);
}
private void createPath(){
start = new Image(new Texture("dot.png"));
start.setColor(Color.GRAY);
start.setPosition(50, this.getHeight() - 150);
this.addActor(start);
end = new Image(new Texture("dot.png"));
end.setColor(Color.GRAY);
end.setPosition(860, this.getHeight() - 150);
this.addActor(end);
path = new Image(new Texture("dot.png"));
path.setColor(Color.WHITE);
this.addActor(path);
}
private void createBar(){
Texture texture = new Texture("ninepatch.png");
int crop = (int)(texture.getWidth()/2)-1;
NinePatch patch9 = new NinePatch(texture, crop, crop, crop, crop);
bar1 = new Image(patch9);
bar1.setColor(Color.GRAY);
bar1.setPosition(5, this.getHeight()-900);
this.addActor(bar1);
}
private void updateSpeedVisuals(){
catmull.derivativeAt(derivative, percent);
dVal = derivative.len() / dMax;
path.setColor(1f, 1f-dVal, 1f-dVal, 1f);
bar1.setWidth(derivative.len());
bar1.setColor(1f, 1f-dVal, 1f-dVal, 1f);
}
private void debugPrint(){
maxPercent = (percent > maxPercent) ? percent : maxPercent;
if (maxPercent > percent){
print = false;
}
if (print){
String debugPrint = "";
debugPrint = debugPrint + "pathLength=" + pathLength + "\t";
debugPrint = debugPrint + "derivative=" + derivative.len() + "\t";
System.out.println(debugPrint);
}
}
}
Since the derivative is the rate of change of the spline position it is indeed the 'speed', and when the spline is bending away from the underlying data points it has to 'speed up' to make the calculated spline reach the next data point in time, you must divide out this speed to perceive a visual constant speed.
You aren't getting a constant speed because you are still incrementing your time variable by delta instead of delta divided by the rate of change (derivative). You should be adding a variable amount to the percent variable each frame, instead you were modifying everything by the derivative of a single point along the Catmull-Rom spline.
Instead of:
catmull.derivativeAt(previousDerivative, percent);
time += delta;
percent = ((time / cycle) / previousDerivative.len() ) % 1;
You should:
catmull.derivativeAt(previousDerivative, percent);
percent += derivativeAverage / cycle * delta / previousDerivative.len();
percent %= 1;
you should use the average derivative divided by cycle now since you can't use cycle alone as a percent per second variable anymore.
Iterating over the spline to find the average value of the derivativeAverage:
int samples = 100; //the higher the more accurate, however slower
float derivativeAverage = 0;
Vector2 out = new Vector2();
for (float i=0;i<1;i+=1f/samples) {
catmull.derivativeAt(out, i);
derivativeAverage += out.len();
}
derivativeAverage /= samples;