Curvas de Bézier

Se denomina curvas de Bézier a un sistema que se desarrolló hacia los años 1960 para el trazado de dibujos técnicos, en el diseño aeronáutico y en el de automóviles. Su denominación es en honor a Pierre Bézier, quien ideó un método de descripción matemática de las curvas que se comenzó a utilizar con éxito en los programas de CAD.

 

Dado un número finito de puntos mayor que uno, la curva de Bezier es el conjunto de puntos resultante de interpolar linealmente otros puntos.

El conjunto de puntos que traza la curva es definido por la interpolación lineal de dos puntos que pueden a su vez estar interpolados entre otros dos.

Según el número de puntos iniciales podemos determinar el orden de la curva.

Curvas lineales:

Dados los puntos P0 y P1, una curva lineal de Bézier es una línea recta entre los dos puntos. La curva viene dada por la expresión:

 

 

Esto equivale a la fórmula de la interpolación lineal entre dos puntos en código C#:

public struct Vector3
{
   public float x;
   public float y;
   public float z;

   public Vector3(float x, float y, float z)
   {
      this.x = x;
      this.y = y;
      this.z = z;
   }

   public static Vector3 Lerp(Vector3 start, Vector3 end, float t)
   {
      if (t > 1F) t = 1F;
      else if (t < 0F) t = 0F;

      float x = (1 - t) * start.x + t * end.x;
      float y = (1 - t) * start.y + t * end.y;
      float z = (1 - t) * start.z + t * end.z;

      return new Vector3(x, y, z);
   }
}

public static class Main 
{ 
   public static void main() 
   { 
      Vector3 start = new Vector3(0F, 1F, -1F);
      Vector3 end = new Vector3(2F, 0F, 0F);
      float t = 0.25f;  
      Vector3 point = Vector3.Lerp(start, end, t);
   } 
}

Curvas cuadráticas:

Una curva cuadrática de Bézier es el camino trazado por la función B(t), dados los puntos: P0P1, y P2

 

 

De donde viene esta fórmula? Vamos a descomponerlo:

 

 

Vamos a sacar las ecuaciones entre los puntos P0 hasta P1 y P1 hasta P2:

\dpi{120} \small Q_0 = (1-t)P_0 + t P_1

\dpi{120} \small Q_1 = (1-t) P_1 + tP_2

 

Y ahora calculamos la interpolación entre los puntos Q0 y Q1:

\dpi{120} \small B = (1-t)Q_0 + tQ_1

 

Y sustituímos Q0 y Q1 por sus ecuaciones:

\dpi{120} \small B = (1-t)((1-t)P_0 + tP_1)+t((1-t)P_1 + tP_2)

 

Y despejamos:

\dpi{120} \small B = (1-t)(1-t)P_0 + (1-t)tP_1 + t(1-t)P_1 + t^2P_2

\dpi{120} \small \mathbf{B = (1-2)^2P_0 + 2t(1-t)P_1 + t^2P_2}

 

De tal forma que para tres puntos podríamos resolverlo de dos formas:

public static class Main
{
   public static Vector3 BezierWithLerp(Vector3 p0, Vector3 p1, Vector3 p2, float t)
   {
      Vector3 q0 = Vector3.Lerp(p0, p1, t);
      Vector3 q1 = Vector3.Lerp(p1, p2, t);
      
      return Vector3.Lerp(q0, q1, t);
   }

   public static Vector3 BezierWithMath(Vector3 p0, Vector3 p1, Vector3 p2, float t)
   {
      if (t > 1F) t = 1F;
      else if (t < 0F) t = 0F;

      float d = 1 - t;
      float i = d * d;
      float j = 2 * t * d;
      float k = t * t;

      float x = p0.x * i + p1.x * j + p2.x * k;
      float y = p0.y * i + p1.y * j + p2.y * k;
      float z = p0.z * i + p1.z * j + p2.z * k;

      return new Vector3(x, y, z);
   }
}

 

Curvas cúbicas:

Cuatro puntos del plano o del espacio tridimensional, P0P1P2 y P3 definen una curva cúbica de Bézier. La curva comienza en el punto P0 y se dirige hacia P1 y llega a P3 viniendo de la dirección del punto P2. Usualmente, no pasará ni por P1 ni por P2. Estos puntos solo están ahí para proporcionar información direccional. La distancia entre P0 y P1 determina “qué longitud” tiene la curva cuando se mueve hacia la dirección de P2 antes de dirigirse hacia P3.

 

 

De donde viene esta fórmula? Vamos a descomponerlo:

 

 

Vamos a sacar las ecuaciones para todos los puntos intermedios:

\dpi{120} \small Q_0 = (1-t)P_0 + tP_1

\dpi{120} \small Q_1 = (1-t)P_1 + tP_2

\dpi{120} \small Q_2 = (1-t)P_2 + tP_3

 

Y ahora las ecuaciones entre los puntos intermedios de los anteriores:

\dpi{120} \small R_0 = (1-t)Q_0 + tQ_1

\dpi{120} \small R_1 = (1-t)Q_1 + tQ_2

 

Y finalmente el punto que queremos conocer:

\dpi{120} \small B = (1-t)R_0 + tR_1

 

Y ahora sustituímos todo lo anterior:

\dpi{120} \small R_0 = (1-t)((1-t)P_0 + tP_1) + t((1-t)P_1 + tP_2)

\dpi{120} \small R_0 = (1-t)^2P_0 + 2(1-t)tP_1 + t^2P_2

 

\dpi{120} \small R_1 = (1-t)((1-t)P_1 + tP_2) + t((1-t)P_2 + tP_3)

\dpi{120} \small R_1 = (1-t)^2P_1 + 2(1-t)tP_2 + t^2P_3

 

\dpi{120} \small B = (1-t)((1-t)^2P_0+2(1-t)tP_1+t^2P_2) + t((1-t)^2P_1+2(1-t)tP_2+t^2P_3)

\dpi{120} \small B = (1-t)^3P_0 + 3(1-t)^2tP_1+3(1-t)t^2P_2+t^3P_3

 

Reordenamos y obtenemos la fórmula original:

\dpi{120} \small \mathbf{B =P_0(1-t)^3+3P_1t(1-t)^2+3P_2t^2(1-t)+P_3t^3}

 

De tal forma que para cuatro puntos podríamos resolverlo de dos formas:

public static class Main
{
   public static Vector3 BezierWithLerp(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
   {
      Vector3 q0 = Vector3.Lerp(p0, p1, t);
      Vector3 q1 = Vector3.Lerp(p1, p2, t);
      Vector3 q2 = Vector3.Lerp(p2, p3, t);    

      Vector3 r0 = Vector3.Lerp(q0, q1, t);
      Vector3 r1 = Vector3.Lerp(q1, q2, t);
      
      return Vector3.Lerp(r0, r1, t);
   }

   public static Vector3 BezierWithMath(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
   {
      if (t > 1F) t = 1F;
      else if (t < 0F) t = 0F;

      float d = 1 - t;
      float td3 = 3 * t * d;

      float i = d * d * d;
      float j = td3 * d;
      float k = td3 * t;
      float l = t * t * t;

      float x = p0.x * i + p1.x * j + p2.x * k + p3.x * l;
      float y = p0.y * i + p1.y * j + p2.y * k + p3.y * l;
      float z = p0.z * i + p1.z * j + p2.z * k + p3.z * l;

      return new Vector3(x, y, z);
   }
}

 

Generalización:

\dpi{200} \tiny B(t) = \sum_{i=0}^{n} \binom{n}{i}P_i (1-t)^n^-^i t^i = P_0 (1-t)^n + \binom{n}{1}P_1 (1-t)^n^-^1 t + \cdots + P_n t^n, t \in [0, 1].

 

 

Vamos a verlo para 3 puntos:

\dpi{120} \small B(t) = \binom{2}{0}P_0(1-t)^2^-^0t^0+\binom{2}{1}P_1(1-t)^2^-^1t^1+\binom{2}{2}P_2(1-t)^2^-^2t^2

\dpi{120} \small B(t) = \binom{2}{0}P_0(1-t)^2 + \binom{2}{1}P_1(1-t)t+\binom{2}{2}P_2t^2

\dpi{120} \small B(t) = \frac{2!}{0!(2-0)!}P_0(1-t)^2 + \frac{2!}{1!(2-1)!}P_1(1-t)t + \frac{2!}{2!(2-2)!}P_2t^2

 

\dpi{120} \small \mathbf{B(t) = P_0(1-t)^2 + 2P_1(1-t)t + P_2t^2}

 

Vamos a verlo para 4 puntos:

\dpi{150} \tiny B(t) = \binom{3}{0}P_0(1-t)^3^-^0t^0 + \binom{3}{1}P_1(1-t)^3^-^1t^1 + \binom{3}{2}P_2(1-t)^3^-^2t^2 + \binom{3}{3}P_3(1-t)^3^-^3t^3

\dpi{120} \small B(t) = \binom{3}{0}P_0(1-t)^3 + \binom{3}{1}P_1(1-t)^2t + \binom{3}{2}P_2(1-t)t^2 + \binom{3}{3}P_3t^3

\dpi{150} \tiny B(t) = \frac{3!}{0!(3-0)!}P_0(1-t)^3 + \frac{3!}{1!(3-1)!}P_1(1-t)^2t + \frac{3!}{2!(3-2)!}P_2(1-t)t^2 + \frac{3!}{3!(3-3)!}P_3t^3

 

\dpi{120} \small \mathbf{B(t) = P_0(1-t)^3 + 3P_1(1-t)^2t + 3P_2(1-t)t^2 + P_3t^3}

 

De forma general podríamos resolverlo de dos formas:

public static Vector3 BezierLerp(Vector3[] points, float t)
{
    int n = points.Length;

    switch(n)
    {
        case 0: return Vector3.zero;

        case 1: return points[0];

        case 2: return Vector3.Lerp(points[0], points[1], t);

        default:

            Vector3[] subPoints = new Vector3[n - 1];

            for (int i = 0; i < subPoints.Length; i++)
                subPoints[i] = Vector3.Lerp(points[i], points[i + 1], t);

            return BezierLerp(subPoints, t);
    }
}
public static Vector3 BezierMath(Vector3[] points, float t)
{
    int n = points.Length - 1;

    float d = 1F - t;

    float x = 0F;
    float y = 0F;
    float z = 0F;

    for (int i = 0; i <= n; i++)
    {
        int binomial = Binomial(n, i);
        Vector3 point = points[i];
        float di = Pow(d, n - i);
        float ti = Pow(t, i);

        float p = binomial * di * ti;

        x += point.x * p;
        y += point.y * p;
        z += point.z * p;
    }

    return new Vector3(x, y, z);
}

public static int Binomial(int n, int r)
{
    if (r > n) return 0;
    if (r == n) return 1;

    if (r > n - r)
        r = n - r;

    int binomial = 1;
    
    for(int i = 0; i <= b; i++, n--)
        binomial = (binomial * n) / i;
   
    return binomial;        
}

public static float Pow(float f, int p)
{
    float value = 1F;

    if (p > 0)
    {
        for (int i = 0; i < p; i++)
            value *= f;
    }
    else if (p < 0)
    {
        for (int i = 0; i < -p; i++)
            value /= f;
    }

    return value;
}

 

 

 

Relacionado


  • Splines de Bezier
  • Easing

 

Enlaces externos


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *