4

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);
        }
    }
}
Community
  • 1
  • 1
Tekkerue
  • 1,497
  • 1
  • 11
  • 17

1 Answers1

6

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;
  • Thanks Johnathon! That made the speed constant. I have one more question if you don't mind, my cycle variable is the time (in seconds) it takes to complete the entire path and being able to set the completion time is also important to my project. Is there anyway to achieve constant speed while being able to set the amount of time it takes to complete the path? Thanks again! – Tekkerue Sep 19 '15 at 20:36
  • I believe that you could use something like `approxLength(100) / cycle * delta / previousDerivative.len()`. Only thing is I'm not sure how this will interact with the derivative, it may not produce the expected result. Let me know what happens, I have no Java environment to test with on this machine. – Johnathon Havens Sep 19 '15 at 20:48
  • Another alternative could be to iterate over the whole spline and find the average derivative value, I'll edit the answer to include something along those lines. – Johnathon Havens Sep 19 '15 at 21:02
  • I tried using "percent += (pathLength / cycle * delta / previousDerivative.len()) % 1;" and the ball moved very fast through the path, got to the end, and then moved downward off of the screen. It only went through the pattern once and never reset. – Tekkerue Sep 19 '15 at 21:07
  • the modulus 1 is no longer needed in that line, you need to run that as a separate line `percent %= 1;` I edited the answer to reflect – Johnathon Havens Sep 19 '15 at 21:13
  • Moving the %1 down to the next line allows the path to reset. The speed appears to be constant, but it is very fast. Instead of taking 6 seconds to complete the path (according to the cycle variable) it took 1.1820759 seconds (added up the delta times over one pass through the path). – Tekkerue Sep 19 '15 at 21:24
  • Also, how would I use the derivativeAverage value in the equation you provided? Thanks again for all your help. – Tekkerue Sep 19 '15 at 21:27
  • Edited the derivative average, the vectors would cancel out before. Adding the lengths and finding the average of that is better. The derivativeAverage would replace the pathLength – Johnathon Havens Sep 19 '15 at 21:29
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90131/discussion-between-johnathon-havens-and-tekkerue). – Johnathon Havens Sep 19 '15 at 21:31