Bezier eğrisinde düzgün hareket hızı nasıl elde edilir?


22

Bezier eğrisi boyunca bir görüntüyü taşımaya çalışıyorum. Bu nasıl yaparım:

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

}

Benim sorunum görüntünün düzgün hareket etmemesi. Başlangıçta yavaş hareket ediyor ve sonra yavaş yavaş hızlanıyor ve sonunda çok hızlı hareket ediyor. Bu ivmeden kurtulmak için ne yapmalıyım?

Yanıtlar:


27

Pek çok parametrik yörünge için bu soruna bir çözüm bulmak mümkündür. Buradaki fikir şudur: Bir eğri üzerinde yeterince derin zoom yaparsanız, eğrinin kendisini o noktadaki teğetinden söyleyemezsiniz.

Bu varsayımı yaparak, precompute şey (kübik Bezier eğrileri için üç, ikiden fazla vektör gerek yoktur vb ).

Bu yüzden M(t) eğrisi için teğet vektörünü d M hesaplıyoruz.dMdttdMdTΔtdMdTΔtLL÷dMdT

Uygulama: ikinci dereceden Bezier eğrisi

Bezier eğrisinin kontrol noktaları , ve ise, yörünge şöyle ifade edilebilir:ABC

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

Yani türev:

dMdt=t(2bir-4B+2C)+(-2bir+2B)

Yalnızca bir yerde ve vektörlerini saklamanız gerekir . Sonra, belirli bir , uzunluğunu ilerletmek istiyorsanız , şunları yapın:v1=2bir-4B+2Cv2=-2bir+2BtL

t=t+Llength(tv1+v2)

Kübik Bezier eğrileri

Aynı akıl yürütme dört , , ve kontrol noktalarına sahip bir eğri için de geçerlidir :birBCD

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

Türev:

dMdt=t2(-3bir+9B-9C+3D)+t(6bir-12B+6C)+(-3bir+3B)

Üç vektörü önceden hesaplıyoruz:

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

ve son formül:

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

Doğruluk sorunları

Makul bir kare hızında çalışıyorsanız, (kare süresine göre hesaplanmalıdır), yaklaşık çalışmanın çalışması için yeterince küçük olacaktır.L

Ancak, aşırı durumlarda yanlışlıklar yaşayabilirsiniz. Eğer çok büyük, sen 10 parçalar kullanarak, örneğin hesaplama parçalı yapabilirsiniz:L

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

1
Merhaba. Cevabınızı okuyorum, ancak L'nin ne olduğunu anlayamıyorum. "Çerçeve süresine göre hesaplanması gereken" derken neyi kastediyorsunuz?
Michael IV

L = eğri segment uzunluğu mu?
Michael IV,

L eğri uzunluğu, yani geçerli kare boyunca seyahat etmek istediğiniz mesafe.
sam hocevar

Tamam, şimdi anlıyorum. Ve bu yaklaşımın aşağıdaki cevaptan eğri yarma tekniği kadar iyi olduğunu mu düşünüyorsunuz?
Michael IV,

Ne zaman Lyeterince küçük, bu yaklaşım, evet her zaman gerçekte aşağıda cevabını daha doğru. Ayrıca daha az bellek kullanır (çünkü tüm nokta değerlerini saklamak yerine türevi kullanır). Ne zaman Lbüyümeye başlar, ne sonunda önermek tekniği kullanabilirsiniz.
sam hocevar

6

Eğriyi yeniden örneklemeniz gerekir. Bunu yapmanın en kolay yolu, eğrinin birkaç bölümünün yay uzunluklarını hesaplamak ve bunları nereden örneklemeniz gerektiğini bulmak için kullanmaktır. Örneğin, belki de t = 0.5'te (yarı yolda), "yarıda" konumunu almak için s = 0.7 değerini eğriye geçirmelisiniz. Bunu yapmak için çeşitli eğri kesimlerinin yay uzunluklarının bir listesini kaydetmeniz gerekir.

Muhtemelen daha iyi yollar var, ama işte bunu oyunda yapmak için yazdığım bazı çok basit C # kodları. Amaç C'ye taşınması kolay olmalı:

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);
    }
}

Düzenleme: Kübik bir eğrinin yay uzunluğunu elde etmek imkansız olduğundan, bunun size tam yay uzunluğunu vermeyeceğini belirtmeye değer. Tüm bunlar, çeşitli bölümlerin uzunluğunu tahmin etmektir. Eğrinin uzunluğuna bağlı olarak, yeni bir segmente ulaştığında hızları değiştirmesini önlemek için çözünürlüğü artırmanız gerekebilir. Genelde ~ 100 kullanıyorum, ki bununla hiç problem yaşamadım.


0

Çok hafif bir çözüm, eğriye yaklaşmak yerine hıza yaklaşmaktır. Aslında bu yaklaşım eğri fonksiyonundan bağımsızdır ve türev veya yaklaşım kullanmak yerine herhangi bir tam eğri kullanmanıza olanak tanır.

İşte C # Unity 3D kodunu:

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;
}

Çözüm, eğri işlevinden bağımsız olmasına rağmen, burada bir Bezier eğrisi üzerinde sabit bir hıza nasıl ulaşacağımı ararken aynı zamanda not etmek istedim ve sonra bu çözümü buldum. Fonksiyonun popülaritesini göz önünde bulundurursak, bu burada yardımcı olabilir.


-3

Cocos2 hakkında hiçbir şey bilmiyorum, ancak bir sınır eğrisi bir tür parametrik denklemdir, bu nedenle x ve y değerlerinizi zaman açısından elde edebilmelisiniz.


4
Bir örnek + daha fazla açıklama ekleyin ve bu iyi bir cevap olacaktır.
MichaelHouse
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.