ベジェ曲線で均一な移動速度を実現する方法は?


22

ベジェ曲線に沿って画像を移動しようとしています。これは私がそれを行う方法です:

- (void)startFly
{    
 [self runAction:[CCSequence actions:
             [CCBezierBy actionWithDuration:timeFlying bezier:[self getPathWithDirection:currentDirection]],
             [CCCallFuncN actionWithTarget:self selector:@selector(endFly)],
             nil]];

}

私の問題は、画像が均一に移動しないことです。最初はゆっくりと動き、その後徐々に加速し、最後には本当に速く動きます。この加速を取り除くにはどうすればよいですか?

回答:


27

ほとんどのパラメトリック軌道について、この問題の解を近似することが可能です。アイデアは次のとおりです。カーブを十分にズームすると、そのポイントでの接線からカーブ自体を見分けることはできません。

この仮定を行うことにより、事前計算何も(立方ベジェ曲線のために3つ以上のベクトルへの必要はありませんなど)。

したがって、曲線、点でその接線ベクトルを計算します。このベクトルのノルムはであるため、期間移動距離はとして近似できます。したがって、距離は期間ます。MtdMdttdMdTtdMdTtLL÷dMdT

アプリケーション:二次ベジェ曲線

ベジェ曲線の制御点が、および場合、軌跡は次のように表現できます。ABC

Mt=1t2A+2t1tB+t2C=t2A2B+C+t2A+2B+A

したがって、導関数は次のとおりです。

dMdt=t2A4B+2C+2A+2B

ベクトルおよびどこかに保存するだけです。次に、与えられたについて、長さ前進したい場合、次のようにします。v1=2A4B+2Cv2=2A+2BtL

t=t+Llengthtv1+v2

立方ベジェ曲線

同じ推論が、4つの制御点、、、およびを持つ曲線にも適用されます。ABCD

M(t)=(1t)3A+3t(1t)2B+3t2(1tC+t3D=t3A+3B3C+D+t23A6B+3C+t3A+3B+A

導関数は次のとおりです。

dMdt=t23A+9B9C+3D+t6A12B+6C+3A+3B

3つのベクトルを事前計算します。

v1=3A+9B9C+3Dv2=6A12B+6Cv3=3A+3B

最終的な式は次のとおりです。

t=t+Llength(t2v1+tv2+v3)

精度の問題

妥当なフレームレートで実行している場合、(フレーム期間に応じて計算する必要があります)は近似が機能するために十分に小さくなります。L

ただし、極端な場合は不正確になる場合があります。が大きすぎる場合、たとえば10個のパーツを使用して、区分的に計算を実行できます。L

for (int i = 0; i < 10; i++)
    t = t + (L / 10) / length(t * v1 + v2);

1
こんにちは。私はあなたの答えを読んでいますが、私はLが何であるか理解できません。「フレーム期間に従って計算されるべき」とはどういう意味ですか?
マイケルIV

L =曲線セグメントの長さですか?
マイケルIV

Lは曲線の長さ、つまり現在のフレーム中に移動したい距離です。
サムホセバル

わかりました そして、この近似は、以下の答えから曲線分割技術と同じくらい良いと思いますか?
マイケルIV

Lが十分に小さい場合、この近似は実際、以下の答えよりも常に正確です。はい。また、メモリの使用量も少なくなります(すべてのポイント値を保存する代わりに導関数を使用するため)。L成長し始めたら、最後に提案する手法を使用できます。
サムホセバー

6

曲線を再パラメータ化する必要があります。これを行う最も簡単な方法は、曲線のいくつかのセグメントの弧の長さを計算し、これらを使用してどこからサンプリングするかを把握することです。たとえば、おそらくt = 0.5(中間)で、s = 0.7を曲線に渡して「中間」位置を取得する必要があります。これを行うには、さまざまな曲線セグメントの弧の長さのリストを保存する必要があります。

おそらくもっと良い方法はありますが、これをゲームでこれを行うために書いた非常にシンプルなC#コードをいくつか示します。Objective Cに簡単に移植できるはずです。

public sealed class CurveMap<TCurve> where TCurve : struct, ICurve
{
    private readonly float[] _arcLengths;
    private readonly float _ratio;
    public float length { get; private set; }
    public TCurve curve { get; private set; }
    public bool isSet { get { return !length.isNaN(); } }
    public int resolution { get { return _arcLengths.Length; } }

    public CurveMap(int resolution)
    {
        _arcLengths = new float[resolution];
        _ratio = 1f / resolution;
        length = float.NaN;
    }

    public void set(TCurve c)
    {
        curve = c;
        Vector2 o = c.sample(0);
        float ox = o.X;
        float oy = o.Y;
        float clen = 0;
        int nSamples = _arcLengths.Length;
        for(int i = 0; i < nSamples; i++)
        {
            float t = (i + 1) * _ratio;
            Vector2 p = c.sample(t);
            float dx = ox - p.X;
            float dy = oy - p.Y;
            clen += (dx * dx + dy * dy).sqrt();
            _arcLengths[i] = clen;
            ox = p.X;
            oy = p.Y;
        }
        length = clen;
    }

    public Vector2 sample(float u)
    {
        if(u <= 0) return curve.sample(0);
        if(u >= 1) return curve.sample(1);

        int index = 0;
        int low = 0;
        int high = resolution - 1;
        float target = u * length;
        float found = float.NaN;

        // Binary search to find largest value <= target
        while(low < high)
        {
            index = (low + high) / 2;
            found = _arcLengths[index];
            if (found < target)
                low = index + 1;
            else
                high = index;
        }

        // If the value we found is greater than the target value, retreat
        if (found > target)
            index--;

        if(index < 0) return curve.sample(0);
        if(index >= resolution - 1) return curve.sample(1);

        // Linear interpolation for index
        float min = _arcLengths[index];
        float max = _arcLengths[index + 1];
        Debug.Assert(min <= target && max >= target);
        float interp = (target - min) / (max - min);
        Debug.Assert(interp >= 0 && interp <= 1);
        return curve.sample((index + interp + 1) * _ratio);
    }
}

編集: 3次曲線のアーク長を取得することは不可能であるため、これでは正確なアーク長が得られないことに注意してください。これはすべて、さまざまなセグメントの長さを推定することです。曲線の長さによっては、新しいセグメントに到達したときに速度が変化しないように解像度を上げる必要がある場合があります。私は通常〜100を使用しますが、これには問題はありません。


0

非常に軽量なソリューションは、曲線を近似するのではなく、速度を近似することです。実際、このアプローチは曲線関数に依存せず、導関数または近似を使用する代わりに、正確な曲線を使用できます。

C#Unity 3Dのコードは次のとおりです。

public float speed; // target linear speed

// determine an initial value by checking where speedFactor converges
float speedFactor = speed / 10; 

float targetStepSize = speed / 60f; // divide by fixedUpdate frame rate
float lastStepSize;

void Update ()
{   
    // Take a note of your previous position.
    Vector3 previousPosition = transform.position;

    // Advance on the curve to the next t;
    transform.position = BezierOrOtherCurveFunction(p0, p1, ..., t);

    // Measure your movement length
    lastStepSize = Vector3.Magnitude(transform.position - previousPosition);

    // Accelerate or decelerate according to your latest step size.
    if (lastStepSize < targetStepSize) 
    {
        speedFactor *= 1.1f;
    }
    else
    {
        speedFactor *= 0.9f;
    }

    t += speedFactor * Time.deltaTime;
}

解決策は曲線関数に依存しませんが、ベジェ曲線で一定の速度を達成する方法も探していたので、ここで注意したかったので、この解決策を考え出しました。関数の人気を考慮すると、これはここで役立ちます。


-3

cocos2については何も知りませんが、ベジェ曲線は一種のパラメトリック方程式なので、時間の観点からxとyの値を取得できるはずです。


4
例と詳細な説明を追加してください。これは良い答えです。
マイケルハウス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.