Wie erreicht man eine gleichmäßige Bewegungsgeschwindigkeit auf einer Bézierkurve?

22

Ich versuche, ein Bild entlang der Bezier-Kurve zu verschieben. So mache ich es:

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

}

Mein Problem ist, dass sich das Bild nicht gleichmäßig bewegt. Am Anfang bewegt es sich langsam und dann beschleunigt es sich allmählich und am Ende bewegt es sich sehr schnell. Was soll ich tun, um diese Beschleunigung loszuwerden?

Andrey Chernukha
quelle

Antworten:

27

Es ist möglich, eine Lösung für dieses Problem für die meisten parametrischen Trajektorien zu finden. Die Idee ist die folgende: Wenn Sie eine Kurve stark genug zoomen, können Sie die Kurve selbst nicht an der Tangente an diesem Punkt erkennen.

Wenn Sie diese Annahme treffen, müssen Sie nicht mehr als zwei Vektoren vorberechnen (drei für kubische Bézier-Kurven, usw. ).

Also berechnen wir für eine Kurve ihren Tangentenvektor am Punkt . Die Norm dieses Vektors ist und daher kann die für eine Dauer als . Daraus folgt, dass eine Strecke für eine DauerM(t)dMdttdMdTΔtdMdTΔtLL÷dMdT .

Anwendung: quadratische Bezierkurve

Wenn die Kontrollpunkte der Bezier-Kurve , undABC , kann die Flugbahn folgendermaßen ausgedrückt werden:

M(t)=(1t)2A+2t(1t)B+t2C=t2(A2B+C)+t(2A+2B)+A

Die Ableitung lautet also:

dMdt=t(2A4B+2C)+(2A+2B)

Sie müssen nur die Vektoren und irgendwo . Dann, für ein gegebenes , wenn Sie eine Länge vorrücken möchten , tun Sie:v1=2EIN-4B+2Cv2=-2EIN+2BtL

t=t+LlenGth(tv1+v2)

Kubische Bezierkurven

Die gleiche Überlegung gilt für eine Kurve mit vier Kontrollpunkten , , und :EINBCD

M(t)=(1t)3A+3t(1t)2B+3t2(1t)C+t3D=t3(A+3B3C+D)+t2(3A6B+3C)+t(3A+3B)+A

Die Ableitung ist:

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

Wir berechnen die drei Vektoren vor:

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

und die endgültige Formel lautet:

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

Genauigkeitsprobleme

Wenn Sie mit einer vernünftigen Framerate laufen,L (das entsprechend der Bilddauer berechnet werden sollte) ausreichend klein, damit die Approximation funktioniert.

In extremen Fällen können jedoch Ungenauigkeiten auftreten. Wenn zu groß ist, können Sie die Berechnung stückweise ausführen, beispielsweise mit 10 Teilen:L

for (int i = 0; i < 10; i++)
    t = t + (L / 10) / length(t * v1 + v2);
sam hocevar
quelle
1
Hallo. Ich lese deine Antwort, kann aber nicht verstehen, was L ist. Was meinst du mit "die sollte nach der Rahmendauer berechnet werden"?
Michael IV
Ist L = Kurvensegmentlänge?
Michael IV
L ist die Kurvenlänge, dh die Entfernung, die Sie während des aktuellen Frames zurücklegen möchten.
Sam Hocevar
OK, ich verstehe jetzt. Und Sie denken, diese Annäherung ist so gut wie die Kurventeilungstechnik aus der Antwort unten?
Michael IV
Wenn Lausreichend klein, ist diese Annäherung tatsächlich immer genauer als die Antwort unten, ja. Außerdem wird weniger Speicher verwendet (da die Ableitung verwendet wird, anstatt alle Punktwerte zu speichern). Wenn es Lanfängt zu wachsen, kannst du die Technik verwenden, die ich am Ende vorschlage.
sam hocevar
6

Sie müssen die Kurve neu parametrisieren. Der einfachste Weg, dies zu tun, besteht darin, die Bogenlängen mehrerer Kurvensegmente zu berechnen und diese zu verwenden, um herauszufinden, wo Sie abtasten sollten. Zum Beispiel sollten Sie bei t = 0,5 (auf halber Strecke) s = 0,7 an die Kurve übergeben, um die Position "auf halber Strecke" zu erhalten. Dazu müssen Sie eine Liste der Bogenlängen verschiedener Kurvensegmente speichern.

Es gibt wahrscheinlich bessere Möglichkeiten, aber hier ist ein sehr einfacher C # -Code, den ich geschrieben habe, um dies in meinem Spiel zu tun. Es sollte einfach sein, auf Ziel C zu portieren:

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

Bearbeiten: Es ist erwähnenswert, dass dies nicht die exakte Bogenlänge ergibt, da es unmöglich ist, die Bogenlänge einer kubischen Kurve zu erhalten. Dies ist lediglich eine Schätzung der Länge der verschiedenen Segmente. Abhängig von der Länge der Kurve müssen Sie möglicherweise die Auflösung erhöhen, um zu verhindern, dass sich die Geschwindigkeit ändert, wenn ein neues Segment erreicht wird. Normalerweise benutze ich ~ 100, womit ich noch nie ein Problem hatte.

Robert Fraser
quelle
0

Eine sehr leichte Lösung besteht darin, die Geschwindigkeit zu approximieren, anstatt die Kurve zu approximieren. Tatsächlich ist dieser Ansatz unabhängig von der Kurvenfunktion und ermöglicht es Ihnen, eine beliebige exakte Kurve zu verwenden, anstatt Ableitungen oder Näherungen zu verwenden.

Hier ist der Code für 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;
}

Obwohl die Lösung unabhängig von der Kurvenfunktion ist, wollte ich sie hier notieren, da ich auch nach Möglichkeiten gesucht habe, eine konstante Geschwindigkeit auf einer Bézier-Kurve zu erzielen, und dann habe ich diese Lösung gefunden. In Anbetracht der Popularität der Funktion kann dies hier hilfreich sein.

Guney Ozsan
quelle
-3

Ich weiß nichts über cocos2, aber eine Bezierkurve ist eine Art parametrische Gleichung, sodass Sie in der Lage sein sollten, Ihre x- und y-Werte in Bezug auf die Zeit zu erhalten.

Jebbles
quelle
4
Fügen Sie ein Beispiel + weitere Erläuterungen hinzu, und dies wäre eine gute Antwort.
MichaelHouse