Come ottenere una velocità di movimento uniforme su una curva più bezier?


22

Sto cercando di spostare un'immagine lungo la curva di Bezier. Ecco come lo faccio:

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

}

Il mio problema è che l'immagine non si muove in modo uniforme. All'inizio si muove lentamente e poi accelera gradualmente e alla fine si muove molto velocemente. Cosa devo fare per sbarazzarmi di questa accelerazione?

Risposte:


27

È possibile approssimare una soluzione a questo problema per la maggior parte delle traiettorie parametriche. L'idea è la seguente: se ingrandisci abbastanza in profondità su una curva, non puoi distinguere la curva stessa dalla sua tangente in quel punto.

Facendo questo presupposto, non è necessario pre-calcolare nulla di più di due vettori (tre per curve cubiche di Bezier, ecc .).

Quindi per una curva M(t) calcoliamo il suo vettore tangente dMdttdMdTΔtdMdTΔtLL÷dMdT

Applicazione: curva quadratica di Bezier

Se i punti di controllo della curva di Bezier sono , e , la traiettoria può essere espressa come:UNBC

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

Quindi il derivato è:

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

Devi solo memorizzare vettori e da qualche parte. Quindi, per una data , se vuoi avanzare di una lunghezza , fai:v1=2UN-4B+2Cv2=-2UN+2BtL

t=t+Llength(tv1+v2)

Curve cubiche di Bezier

Lo stesso ragionamento si applica a una curva con quattro punti di controllo , , e :UNBCD

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

Il derivato è:

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

Precalcoliamo i tre vettori:

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

e la formula finale è:

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

Problemi di precisione

Se si esegue a un frame rate ragionevole, (che dovrebbe essere calcolato in base alla durata del frame) sarà sufficientemente piccolo per consentire l'approssimazione.L

Tuttavia, potresti riscontrare inesattezze in casi estremi. Se è troppo grande, è possibile eseguire il calcolo a tratti, ad esempio utilizzando 10 parti:L

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

1
Ciao. Sto leggendo la tua risposta, ma non riesco a capire cosa sia L. Cosa intendi con "che dovrebbe essere calcolato in base alla durata del frame"?
Michele IV,

L = lunghezza del segmento curva?
Michele IV,

L è la lunghezza della curva, ovvero la distanza che si desidera percorrere durante il fotogramma corrente.
sam hocevar,

OK, vedo ora. E pensi che questa approssimazione sia buona quanto la tecnica di divisione delle curve dalla risposta qui sotto?
Michele IV,

Quando Lè sufficientemente piccola, questa approssimazione è in realtà sempre più accurata della risposta seguente, sì. Utilizza anche meno memoria (perché utilizza la derivata anziché memorizzare tutti i valori dei punti). Quando Linizia a crescere, puoi usare la tecnica che suggerisco alla fine.
sam hocevar,

6

Devi ricomparare la curva. Il modo più semplice per farlo è calcolare la lunghezza dell'arco di diversi segmenti della curva e usarli per capire da dove si dovrebbe campionare. Ad esempio, forse at = 0,5 (a metà strada), dovresti passare s = 0,7 alla curva per ottenere la posizione "a metà strada". Per fare ciò è necessario memorizzare un elenco di lunghezze d'arco di vari segmenti di curva.

Probabilmente ci sono modi migliori, ma ecco un codice C # molto semplice che ho scritto per farlo nel mio gioco. Dovrebbe essere facile eseguire il porting all'obiettivo 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);
    }
}

Modifica: vale la pena notare che questo non ti darà la lunghezza esatta dell'arco, poiché è impossibile ottenere la lunghezza dell'arco di una curva cubica. Tutto ciò che fa è stimare la lunghezza dei vari segmenti. A seconda della lunghezza della curva, potrebbe essere necessario aumentare la risoluzione per evitare che cambi velocità quando raggiunge un nuovo segmento. Di solito uso ~ 100, con cui non ho mai avuto problemi.


0

Una soluzione molto leggera è quella di approssimare la velocità piuttosto che approssimare la curva. In realtà questo approccio è indipendente dalla funzione curva e consente di utilizzare qualsiasi curva esatta anziché utilizzare derivate o approssimazioni.

Ecco il codice per 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;
}

Sebbene la soluzione sia indipendente dalla funzione curva, volevo annotarla qui mentre cercavo anche come ottenere una velocità costante su una curva di Bezier, e poi ho trovato questa soluzione. Considerando la popolarità della funzione, questo può essere utile qui.


-3

Non so nulla di cocos2, ma una curva di Bezier è una specie di equazione parametrica, quindi dovresti essere in grado di ottenere i tuoi valori xey in termini di tempo.


4
Aggiungi un esempio + ulteriori spiegazioni e questa sarebbe una buona risposta.
MichaelHouse
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.