¿Cómo lograr una velocidad de movimiento uniforme en una curva bezier?

22

Estoy tratando de mover una imagen a lo largo de la curva de Bezier. Así es como lo hago:

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

}

Mi problema es que la imagen no se mueve de manera uniforme. Al principio se mueve lentamente y luego se acelera gradualmente y al final se mueve muy rápido. ¿Qué debo hacer para deshacerme de esta aceleración?

Andrey Chernukha
fuente

Respuestas:

27

Es posible aproximar una solución a este problema para la mayoría de las trayectorias paramétricas. La idea es la siguiente: si hace un zoom lo suficientemente profundo en una curva, no puede distinguir la curva en sí de su tangente en ese punto.

Al hacer esta suposición, no es necesario calcular previamente nada más que dos vectores (tres para curvas de Bezier cúbicas, etc. ).

Entonces, para una curva calculamos su vector tangente en el punto . La norma de este vector es y, por lo tanto, la distancia recorrida durante una duración puede aproximarse como . Se deduce que se recorre una distancia durante un tiempo .METRO(t)reMETROrettreMETROreTΔtreMETROreTΔtLL÷reMETROreT

Aplicación: curva de Bezier cuadrática

Si los puntos de control de la curva de Bezier son , y , la trayectoria se puede expresar como:UNAsido

METRO(t)=(1-t)2UNA+2t(1-t)si+t2do=t2(UNA-2si+do)+t(-2UNA+2si)+UNA

Entonces la derivada es:

reMETROret=t(2UNA-4 4si+2do)+(-2UNA+2si)

Solo necesita almacenar los vectores y alguna parte. Luego, para una determinada , si desea avanzar una longitud , debe:v1=2A4B+2dov2=2UNA+2sitL

t=t+Llminortesolth(tv1+v2)

Curvas de Bezier cúbico

El mismo razonamiento se aplica a una curva con cuatro puntos de control , , y :UNAsidore

METRO(t)=(1-t)3UNA+3t(1-t)2si+3t2(1-t)do+t3re=t3(-UNA+3si-3do+re)+t2(3UNA-6 6si+3do)+t(-3UNA+3si)+UNA

La derivada es:

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

Calculamos previamente los tres vectores:

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

y la fórmula final es:

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

Problemas de precisión

Si está ejecutando a una velocidad de cuadro razonable, (que debe calcularse de acuerdo con la duración del cuadro) será lo suficientemente pequeño para que la aproximación funcione.L

Sin embargo, puede experimentar imprecisiones en casos extremos. Si es demasiado grande, puede hacer el cálculo por partes, por ejemplo, utilizando 10 partes:L

for (int i = 0; i < 10; i++)
    t = t + (L / 10) / length(t * v1 + v2);
sam hocevar
fuente
1
Hola. Estoy leyendo su respuesta, pero no puedo entender qué es L. ¿Qué quiere decir con "que debe calcularse de acuerdo con la duración del marco"?
Michael IV el
¿Es L = longitud del segmento de curva?
Michael IV
L es la longitud de la curva, es decir, la distancia que desea recorrer durante el cuadro actual.
sam hocevar
OK, ya veo ahora. ¿Y cree que esta aproximación es tan buena como la técnica de división de curvas de la respuesta a continuación?
Michael IV
Cuando Les suficientemente pequeño, esta aproximación siempre es más precisa que la respuesta a continuación, sí. También usa menos memoria (porque usa la derivada en lugar de almacenar todos los valores de puntos). Cuando Lcomience a crecer, puede usar la técnica que sugiero al final.
Sam Hocevar
6

Necesita volver a dibujar la curva. La forma más fácil de hacer esto es calcular las longitudes de arco de varios segmentos de la curva y usarlas para determinar de dónde debe tomar muestras. Por ejemplo, tal vez en t = 0.5 (a mitad de camino), debe pasar s = 0.7 a la curva para obtener la posición "a mitad de camino". Necesita hacer una lista de longitudes de arco de varios segmentos de curva para hacer esto.

Probablemente hay mejores formas, pero aquí hay un código C # muy simple que escribí para hacer esto en mi juego. Debería ser fácil portar al objetivo 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);
    }
}

Editar: Vale la pena señalar que esto no le dará la longitud exacta del arco, ya que es imposible obtener la longitud del arco de una curva cúbica. Todo lo que hace es estimar la longitud de los diversos segmentos. Según la longitud de la curva, es posible que deba aumentar la resolución para evitar que cambie de velocidad cuando llegue a un nuevo segmento. Usualmente uso ~ 100, con el que nunca he tenido problemas.

Robert Fraser
fuente
0

Una solución muy ligera es aproximar la velocidad en lugar de aproximar la curva. En realidad, este enfoque es independiente de la función de curva y le permite usar cualquier curva exacta en lugar de usar derivadas o aproximaciones.

Aquí está el código para 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;
}

Aunque la solución es independiente de la función de curva, quería señalarla aquí, ya que también estaba buscando cómo lograr una velocidad constante en una curva de Bezier, y luego se me ocurrió esta solución. Teniendo en cuenta la popularidad de la función, esto puede ser útil aquí.

Guney Ozsan
fuente
-3

No sé nada acerca de cocos2, pero una curva bezier es una especie de ecuación paramétrica, por lo que debería poder obtener sus valores x e y en términos de tiempo.

Jebbles
fuente
44
Agregue un ejemplo + más explicación y esta sería una buena respuesta.
MichaelHouse