Bagaimana cara mencapai kecepatan gerakan yang seragam pada kurva bezier?

22

Saya mencoba untuk memindahkan gambar di sepanjang kurva Bezier. Beginilah cara saya melakukannya:

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

}

Masalah saya adalah gambar bergerak tidak seragam. Pada awalnya itu bergerak perlahan dan kemudian berakselerasi secara bertahap dan pada akhirnya bergerak sangat cepat. Apa yang harus saya lakukan untuk menghilangkan akselerasi ini?

Andrey Chernukha
sumber

Jawaban:

27

Dimungkinkan untuk memperkirakan solusi untuk masalah ini untuk sebagian besar lintasan parametrik. Idenya adalah sebagai berikut: jika Anda memperbesar cukup dalam pada kurva, Anda tidak bisa membedakan kurva itu sendiri dari garis singgung pada titik itu.

Dengan membuat asumsi ini, tidak perlu untuk melakukan perhitungan lebih dari dua vektor (tiga untuk kurva Bezier kubik, dll .).

Jadi untuk kurva kita menghitung vektor singgung pada titik . Norma dari vektor ini adalah dan karenanya jarak yang ditempuh untuk durasi dapat diperkirakan sebagai . Oleh karena itu jarak ditempuh untuk durasi .M.(t)dM.dttdM.dTΔtdM.dTΔtL.L.÷dM.dT

Aplikasi: kurva Bezier kuadratik

Jika titik kontrol dari kurva Bezier adalah , dan , lintasan dapat dinyatakan sebagai:SEBUAHBC

M.(t)=(1-t)2SEBUAH+2t(1-t)B+t2C=t2(SEBUAH-2B+C)+t(-2SEBUAH+2B)+SEBUAH

Jadi turunannya adalah:

dM.dt=t(2SEBUAH-4B+2C)+(-2SEBUAH+2B)

Anda hanya perlu menyimpan vektor dan suatu tempat. Kemudian, untuk diberikan , jika Anda ingin memajukan panjang , Anda lakukan:v1=2SEBUAH-4B+2Cv2=-2SEBUAH+2BtL.

t=t+L.length(tv1+v2)

Kurva Bezier Kubik

Alasan yang sama berlaku untuk kurva dengan empat titik kontrol , , dan :SEBUAHBCD

M(t)=(1-t)3SEBUAH+3t(1-t)2B+3t2(1-t)C+t3D=t3(-SEBUAH+3B-3C+D)+t2(3SEBUAH-6B+3C)+t(-3SEBUAH+3B)+SEBUAH

Derivatifnya adalah:

dMdt=t2(3A+9B9C+3D)+t(6A12B+6C)+(-3SEBUAH+3B)

Kami precompute tiga vektor:

v1=-3SEBUAH+9B-9C+3Dv2=6SEBUAH-12B+6Cv3=-3SEBUAH+3B

dan formula terakhirnya adalah:

t=t+L.length(t2v1+tv2+v3)

Masalah akurasi

Jika Anda menjalankan framerate yang masuk akal, (yang harus dihitung sesuai dengan durasi frame) akan cukup kecil agar aproksimasi bekerja.L.

Namun, Anda mungkin mengalami ketidakakuratan dalam kasus-kasus ekstrem. Jika terlalu besar, Anda dapat melakukan perhitungan sedikit demi sedikit, misalnya menggunakan 10 bagian:L.

for (int i = 0; i < 10; i++)
    t = t + (L / 10) / length(t * v1 + v2);
sam hocevar
sumber
1
Hai. Saya membaca jawaban Anda, tetapi saya tidak bisa mengerti apa itu L. Apa yang Anda maksud dengan "yang harus dihitung sesuai dengan durasi bingkai"?
Michael IV
Apakah L = panjang segmen kurva?
Michael IV
L adalah panjang kurva, yaitu jarak yang ingin Anda tempuh selama bingkai saat ini.
sam hocevar
Oke, saya mengerti sekarang. Dan Anda pikir perkiraan ini sebagus teknik kurva pemisahan dari jawaban di bawah ini?
Michael IV
Ketika Lcukup kecil, perkiraan ini sebenarnya selalu lebih akurat daripada jawaban di bawah ini, ya. Ini juga menggunakan lebih sedikit memori (karena menggunakan turunan alih-alih menyimpan semua nilai titik). Saat Lmulai tumbuh, Anda bisa menggunakan teknik yang saya sarankan di bagian akhir.
sam hocevar
6

Anda perlu mengubah kurva kembali. Cara termudah untuk melakukan ini adalah menghitung panjang busur beberapa segmen kurva dan menggunakannya untuk mencari tahu di mana Anda harus mengambil sampel. Misalnya, mungkin pada t = 0,5 (setengah jalan), Anda harus melewati s = 0,7 ke kurva untuk mendapatkan posisi "setengah". Anda perlu menyimpan daftar panjang busur berbagai segmen kurva untuk melakukan ini.

Mungkin ada cara yang lebih baik, tetapi inilah beberapa kode C # yang sangat sederhana yang saya tulis untuk melakukan ini di permainan saya. Seharusnya mudah untuk port ke 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);
    }
}

Sunting: Perlu dicatat bahwa ini tidak akan memberi Anda panjang busur yang tepat, karena tidak mungkin untuk mendapatkan panjang busur dari kurva kubik. Semua ini dilakukan adalah memperkirakan panjang berbagai segmen. Tergantung pada berapa lama kurva itu, Anda mungkin perlu meningkatkan resolusi untuk mencegahnya mengubah kecepatan ketika mencapai segmen baru. Saya biasanya menggunakan ~ 100, yang saya tidak pernah punya masalah dengannya.

Robert Fraser
sumber
0

Solusi yang sangat ringan adalah dengan memperkirakan kecepatan daripada mendekati kurva. Sebenarnya pendekatan ini tidak tergantung pada fungsi kurva dan memungkinkan Anda untuk menggunakan kurva yang tepat daripada menggunakan turunan atau perkiraan.

Berikut adalah kode untuk 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;
}

Meskipun solusinya tidak tergantung pada fungsi kurva, saya ingin mencatatnya di sini karena saya juga mencari cara untuk mencapai kecepatan konstan pada kurva Bezier, dan kemudian saya menemukan solusi ini. Mempertimbangkan popularitas fungsi ini, ini mungkin bermanfaat di sini.

Guney Ozsan
sumber
-3

Saya tidak tahu apa-apa tentang cocos2, tetapi kurva bezier adalah semacam persamaan parametrik, jadi Anda harus bisa mendapatkan nilai x dan y Anda dari segi waktu.

Kerikil
sumber
4
Tambahkan contoh + lebih banyak penjelasan dan ini akan menjadi jawaban yang bagus.
MichaelHouse