Java 曲线路径恒定速度和端点

Java 曲线路径恒定速度和端点,java,libgdx,spline,derivative,Java,Libgdx,Spline,Derivative,我试图在给定的时间内以恒定的速度在一条弯曲的路径上移动。我通过取曲线上各个点的导数并求平均值,计算了在曲线上行驶所需的平均速度。然后我将路径的位置(t)乘以平均导数和曲线当前位置的导数之比。这种设定恒定速度的方法非常有效 当多个控制点(3个或更多)放在同一位置时,就会出现我遇到的问题。此时的速度(或导数)为0,将平均速度除以0显然会导致计算中出现问题 BSpline要求在端点处放置三个控制点,以便曲线实际到达端点处的起点和终点。如果我只在末端放置1或2个控制点,则路径从第一个控制点之后开始,在最

我试图在给定的时间内以恒定的速度在一条弯曲的路径上移动。我通过取曲线上各个点的导数并求平均值,计算了在曲线上行驶所需的平均速度。然后我将路径的位置(t)乘以平均导数和曲线当前位置的导数之比。这种设定恒定速度的方法非常有效

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

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

我尝试过几种不同的方法来修复它,但都没有成功

这是我的示例代码,我已经添加了一些注释来指出问题所在

注意:我在示例中使用CatmullRomspeline而不是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();
    }
}
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();
        }
    }
}
MyPath.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();
    }
}
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();
        }
    }
}
公共类MyPath扩展WidgetGroup{
专用路径;
私有向量2结果=新向量2(),导数=新向量2();
专用浮点时间,t,tPrev,dt,tConst,tConstPrev,派生度;
私有数组纹理=新数组(Texture.class);
私有数组点=新数组(Image.class);
私有图像点;
专用最终浮点循环=4;//路径循环时间(秒)
专用向量2[]点数据={
新矢量2(100100),
新矢量2(100100),

//新矢量2(100100),//如果控制点可以重合,则无法完全避免零一阶导数。因此,我建议根本不要使用一阶导数。你的目的是以恒定速度穿过路径,这相当于沿等长弧路径的采样点。解决这一问题的理论方法涉及计算我们需要数值计算弧长,但我们可以采用近似方法,如下所示:

假设您希望以N个步骤遍历路径,
1) 在参数域(即t=0.0、0.1、0.2、0.3等)中沿路径均匀采样M个点,其中M最好大于N。将这些点表示为P0、P1、P2等。
2) 计算P0P1、P1P2、P2P3……之间的距离。
3) 编译一个从参数t(i)映射到累积弦长| P0P1 |+| P1P2 |+…+| P(i-1)P(i)|的查找表。最后,还将获得路径的总长度,表示为L。

4) 现在,对于kL/N的每个值(其中k=0到N),您可以通过线性插值kL/N所在的两个参数值,从查找表中计算相应的t值。

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

假设您希望以N个步骤遍历路径,
1) 在参数域(即t=0.0、0.1、0.2、0.3等)中沿路径均匀采样M个点,其中M最好大于N。将这些点表示为P0、P1、P2等。
2) 计算P0P1、P1P2、P2P3……之间的距离。
3) 编译一个从参数t(i)映射到累积弦长| P0P1 |+| P1P2 |+…+| P(i-1)P(i)|的查找表。最后,还将获得路径的总长度,表示为L。

4) 现在,对于kL/N的每个值(其中k=0到N),可以通过线性插值kL/N所在的两个参数值,从查找表中计算相应的t值。

B样条曲线不需要在端点处添加多个控制点,以便曲线到达起点和终点,除非使用非固定结序列,如[0,0.1,0.2,…,0.9,1.0]。如果使用固定结序列,如[0,0,0,0.1,0.2,…,0.9,1,1,1],则曲线将在第一个和最后一个控制点开始和结束。如上所述,如果切换到Catmull Rom样条曲线,是否仍存在相同的问题(遇到零导数)?是的,我有相同的零如果我在同一位置设置3个(或更多)控制点,CatmullRom会出现(或接近零)导数问题。关于BSpline,我查看了LibGDX源代码,似乎在valueAt()或derivativeAt()中没有使用结方法。我通过清除结数组并为其指定我自己的值来测试它,路径保持不变。那么我该如何设置不同的结序列呢?谢谢。如果你复制连续点,你会得到零导数。但是不管你使用哪种插值,这种情况都会发生。我不熟悉LibGD我只是从B样条理论的角度进行了评论。对不起。你知道有没有一种方法可以通过多个控制点平稳地跨过这些区域,回到导数不再为零(或接近于零)的路径上?我尝试了一些不同的方法