弯曲路径恒定速度和终点

时间:2016-04-03 23:40:11

标签: java libgdx spline derivative

我试图在给定的时间内以恒定的速度在弯曲的路径上移动。我通过在曲线上的不同点取导数并对它们求平均值来计算曲线行进所需的平均速度。然后我将路径的位置(t)乘以曲线当前位置的平均导数和导数的比率。这种设定恒速的方法效果很好。

当多个控制点(3个或更多)放在同一位置时,我遇到了问题。那么此时的速度(或导数)为0,将平均速度除以速度0显然会导致计算出现问题。

BSpline需要在末端放置三个控制点,以使曲线实际到达终点的起点和终点。如果我只在末端放置1或2个控制点,则路径在第一个控制点之后开始并在最后一个控制点之前结束。对于我的应用程序,重要的是运动到达终点,因为我将多个BSplines链接在一起,这对他们正确排列并且它们之间没有任何时间间隔很重要。

我尝试了一些不同的尝试来修复它,但没有一个成功。

以下是我的示例代码,并且我提供了注释,以指出问题所在。

注意: 我在我的示例中使用了CatmullRomSpline而不是BSpline,因为我在BSpline的派生方法中发现了一个错误,该方法已经修复但不是但是在LibGDX的稳定版本中。

Test.java

public class Test extends Game {
    private Stage stage;
    private MyPath path;

    @Override
    public void create () {
        Gdx.graphics.setDisplayMode(1000, 1000, false);
        stage = new Stage();
        stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
        Gdx.input.setInputProcessor(stage);
        path = new MyPath(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        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(){
        path.dispose();
        stage.dispose();
        super.dispose();
    }
}

MyPath.java

public class MyPath extends WidgetGroup implements Disposable {
    private Path<Vector2> path;
    private Vector2 result=new Vector2(), derivative=new Vector2();
    private float time, t, tPrev, dt, tConst, tConstPrev, derivativeAverage;
    private Array<Texture> textures = new Array<Texture>(Texture.class);
    private Array<Image> points = new Array<Image>(Image.class);
    private Image dot;

    private final float CYCLE = 4;  // path cycle time (in seconds)

    private Vector2[] pointsData = {
            new Vector2(100, 100),
            new Vector2(100, 100),
//          new Vector2(100, 100),  // << UN-COMMENT TO PRODUCE BUG

            new Vector2(350, 800),
            new Vector2(550, 200),
            new Vector2(650, 400),
            new Vector2(900, 100),
            new Vector2(900, 100)
    };

    public MyPath(int width, int height){
        this.setSize(width, height);        
        path = new CatmullRomSpline<Vector2>(pointsData, false);
        // create and add images
        createImages();
        for (int i=0; i<points.size; i++){
            points.items[i].setPosition(pointsData[i].x - points.items[i].getWidth()/2, pointsData[i].y - points.items[i].getHeight()/2);
            addActor(points.items[i]);
        }
        addActor(dot);

        // calculate derivative average
        derivativeAverage();
    }

    @Override
    public void act(float delta){
        result = getValue(delta);
        dot.setPosition(result.x - dot.getWidth()/2, result.y - dot.getHeight()/2);     
    }
    private Vector2 getValue(float delta){
        // set t in the range [0,1] for path
        time += delta;
        if (time > CYCLE){
            time = tPrev = dt = tConst = tConstPrev = 0;
        }
        t = time / CYCLE;
        dt = t - tPrev;
        tPrev = t;

        // constant speed (tConst)
        path.derivativeAt(derivative, tConstPrev);
        tConst += dt * (derivativeAverage / derivative.len());  // << ERROR when derivative.len() is 0
        tConstPrev = tConst;

        path.valueAt(result, tConst);

        return result;
    }

    private void derivativeAverage(){
        float segmentCount = 20000;
        derivativeAverage = 0;      
        for (float i=0; i<=1; i+=1.0/segmentCount) {    
            path.derivativeAt(result, i);
            derivativeAverage += result.len();          
        }
        derivativeAverage /= segmentCount;
        if (derivativeAverage==0){ throw new GdxRuntimeException("ERROR: derivative average is zero"); }
    }

    private void createImages(){
        dot = getImage(Color.GREEN, true);
        for (int i=0; i<pointsData.length; i++){
            points.add(getImage(Color.WHITE, false));
        }
    }
    private Image getImage(Color color, boolean fillCircle){
        Pixmap pixmap = new Pixmap(50, 50, Pixmap.Format.RGBA8888);
        pixmap.setColor(color);
        if (fillCircle){
            pixmap.fillCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
        } else {
            pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
        }
        textures.add(new Texture(pixmap));
        pixmap.dispose();
        return new Image(textures.peek());
    }
    @Override
    public void dispose(){
        while (textures.size > 0){
            textures.pop().dispose();
        }
    }
}

=============================================== ====================

修改

=============================================== ====================

这是我最近尝试增加t直到点移动。

此方法偶尔会对某些帧(通过零导数平滑移动)起作用。但有时候,当奇迹触及零导数或延伸到曲线末端以外的方向移动一个不同的方向或完全消失时(因为位置设置为负值),点会在曲线的开始处开始奇怪的事情。 / p>

所以看起来这个方法非常接近,因为它偶尔会对某些帧起作用,但它会在其他帧上出现故障并做一些奇怪的事情。

Vector2 lastPoint = new Vector2();
float minSpeed = 1;
float minDerivative = 1;
float temp;

...

private Vector2 getValue(float delta){      
    // set t in the range [0,1] for path
    time += delta;
    if (time > CYCLE){
        time = tPrev = dt = tConst = tConstPrev = 0;
    }
    t = time / CYCLE;

    // CONSTANT SPEED
    dt = t - tPrev;
    path.derivativeAt(derivative, tConstPrev);
    temp = dt * (derivativeAverage / derivative.len());
    path.valueAt(result, tConst + temp);

    //**************************************
    //  FIX FOR ZERO SPEED
    //  increase t in loop until speed > 0
    //**************************************
    while (result.dst(lastPoint)<minSpeed || derivative.len()<minDerivative){
        // set t in the range [0,1] for path
        time += delta;
        if (time > CYCLE){
            time = tPrev = dt = tConst = tConstPrev = 0;
            lastPoint.set(0,0);         
        }
        t = time / CYCLE;

        // CONSTANT SPEED
        dt = t - tPrev;
        // new derivative
        path.valueAt(derivative, t);
        derivative.sub(lastPoint);

        temp = dt * (speedAverage / derivative.len());
        path.valueAt(result, tConst + temp);
    }

    tConst += temp;

    lastPoint.set(result);
    tPrev = t;
    tConstPrev = tConst;

    return result;
}

在计算平均速度以保持零衍生物不影响它时,我也做了类似的事情。我也尝试使用注释掉的部分和#34; addedSegmentCount&#34;在计算平均值时变量,但实际上由于某种原因引起了更多的故障......尽管从理论上讲这似乎是&#34;正确的&#34;计算平均值的方法,因为如果距离太小,某些段不会被添加。

private void pathLength_SpeedAverage(){
    float segmentCount = 20000;
//  float addedSegmentCount = 0;
    pathLength = 0;

    path.valueAt(lastPoint, 0);
    for (float i=0; i<=1; i+=1.0/segmentCount){
        path.valueAt(result, i);
        if (result.dst(lastPoint) >= minSpeed){
            path.derivativeAt(result, i);
            if (result.len() >= minDerivative){
                pathLength += result.len();

                lastPoint.set(result);
//              ++addedSegmentCount;
            }
        }
    }
    speedAverage = pathLength / segmentCount;
//  speedAverage = pathLength / addedSegmentCount;
    lastPoint.set(0,0);
}

1 个答案:

答案 0 :(得分:1)

如果控制点可能重合,则无法完全避免零一阶导数。所以,我建议不要使用一阶导数。您的目的是以恒定速度遍历路径,这相当于沿着具有相等弧长的路径的采样点。解决这个问题的理论方法涉及用数值计算弧长的微积分,但我们可以采用近似方法,如下所示:

假设您想以N个步骤遍历路径,
1)样本M在参数域中均匀地沿着路径指向(即,t = 0.0,0.1,0.2,0.3 ......),其中M优选地大于N.将这些点表示为P0,P1,P2, ....
2)计算P0P1,P1P2,P2P3,....之间的距离 3)编译从参数t(i)映射到累积弦长| P0P1 | + | P1P2 | + ..... + | P(i-1)P(i)|的查找表。最后,您还将获得路径的总长度,表示为L.
4)现在,对于k L / N(其中k = 0到N)的每个值,您可以通过线性插值其中k的两个参数值来计算查找表中的相应t值。 em> L / N落在了上面。