martes, 2 de febrero de 2016

Colisión de una línea recta y una curva bezier

Esta función pretende determinar el punto de colisión P entre una curva bezier ABCD descrita entre los puntos A y D y controlada por B y C, y una línea recta con posición L y pendiente M. Ejemplo: https://jsfiddle.net/cincibeles/40s89fwx/


El punto P se describe como el punto de la línea L que comparte espacio con la curva ABCD. De este modo, despejo los componentes x,y de la línea L y de la curva ABCD para despejar el valor T que es un número entre 0 y 1 que sirve como puntero en la curva.

El uso de la curva bezier se describe en un anterior post http://blog.cincibeles.com/2016/01/curva-bizier-y-ruta-de-curvas.html, además he tomado el algoritmo que permite resolver ecuaciones de tercer grado de la página http://www.gyplan.com/es/eqcubic_es.html para poder resolver la igualdad de las formulas bezier y la línea.

despejando la ecuación bezier obtengo..

x = t³ (Ax - 3Bx + 3Cx - Dx) + t² (3Bx - 6Cx + 3Dx) + t (3Cx - 3Dx) + Dx
y = t³ (Ay - 3By + 3Cy - Dy) + t² (3By - 6Cy + 3Dy) + t (3Cy - 3Dy) + Dy

y la ecuación lineal...

x = (y - b) / m + a
y = m (x - a) + b

entonces...

 (y - b) / m + a = t³ (Ax - 3Bx + 3Cx - Dx) + t² (3Bx - 6Cx + 3Dx) + t (3Cx - 3Dx) + Dx
 m (x - a) + b =  t³ (Ay - 3By + 3Cy - Dy) + t² (3By - 6Cy + 3Dy) + t (3Cy - 3Dy) + Dy

despejando la variable t obtendremos el puntero que nos indicará en que sección de la curva está cortando.

En fin. Acá está la función que recibe siete parámetros: un punto de la linea x,y, su pendiente m, y los cuatro puntos de la curva bezier a,b,c,d.

function BizierLineCollide(x,y,m,A,B,C,D){
    var Ax=A[0], Ay=A[1], At, Bx=B[0], By=B[1], Bt, Cx=C[0], Cy=C[1], Ct, Dx=D[0], Dy=D[1], Dt, result=[];
    if(Math.abs(m)<=1){
        At=(Ay-3*By+3*Cy-Dy)-m*(Ax-3*Bx+3*Cx-Dx);
        Bt=3*(By-2*Cy+Dy)-m*3*(Bx-2*Cx+Dx);
        Ct=3*(Cy-Dy)-m*3*(Cx-Dx)
        Dt=Dy-y+m*(x-Dx);
        result= eqCubic(At,Bt,Ct,Dt);
    }else{
        var w=1/m;
        At=(Ax-3*Bx+3*Cx-Dx)-w*(Ay-3*By+3*Cy-Dy);
        Bt=3*(Bx-2*Cx+Dx)-w*3*(By-2*Cy+Dy);
        Ct=3*(Cx-Dx)-w*3*(Cy-Dy);
        Dt=Dx-x+w*(y-Dy);
        result= eqCubic(At,Bt,Ct,Dt);
    }
    var fnB=fnBizier(A,B,C,D), _return=[];
    result.forEach(function(v,k){
        if(!isNaN(v)){
            v=Math.round(v*10000)/10000;
            if(v>=0 && v<1){
                console.log(v);
                _return.push(fnB(v));
            }
        }
    });
    return _return;
}

Devuelve el punto de colisión x,y y t, ademas los componentes de la pendiente de la curva en el mismo punto.
Para funcionar requiere dos funciones: La función que permite deducir una ecuación cubica y la función de la curva bezier .

function eqCubic(a,b,c,d){ //http://www.gyplan.com/es/eqcubic_es.html

    var x1=x2=x3=0, f,g,h,m,k,m2,n,n2,r,rc,theta;
        
    if(a=="" && b==""&& c=="" && d=="") return 'es vacia';
    if(a=="" && b==""&& c=="" ) return d;
    if(a=="" && b=="" ){
        a=c; b=d;
        return -b/a; 
    }
    if(a==0 | a=="") {
        a=b; b=c; c=d;
        var b24ac2a=Math.sqrt(b*b-4*a*c);
        return isNaN(b24ac2a)?[]:[(-b+b24ac2a)/(2*a),(-b-b24ac2a)/(2*a)];
    };

    f = (((3*c)/a) - (((b*b)/(a*a))))/3
    g = ((2*((Math.pow(b,3))/(Math.pow(a,3)))-(9*b*c/(a*a)) + ((27*(d/a)))))/27 // Math.pow(b,3)
    h = (((g*g)/4) + ((f*f*f)/27))
    var dis = h;
    if (h > 0){
        m = (-(g/2)+ (Math.sqrt(h)))
        k=1
        if (m < 0) k=-1; else k=1
        m2 = (Math.pow((m*k),(1/3)))
        m2 = m2*k
        k=1
        n = -(g/2)- (Math.sqrt(h))
        if (n < 0) k=-1; else k=1
        n2 = (Math.pow((n*k),(1/3)))
        n2 = n2*k
        k=1
        x1= (m2 + n2) - b/(3*a);
        x2= ((-1*(m2 + n2)/2 - b/(3*a)) + "  + i * " + (m2 - n2)*Math.pow(3,.5)/2 );
        x3= ((-1*(m2 + n2)/2 - b/(3*a)) + "  - i * " + (m2 - n2)*Math.pow(3,.5)/2 );
        return [x1,x2,x3];
    }
    if (h<=0){
        r = ((Math.sqrt((g*g/4)-h)));
        k=1;
        if (r<0) k=-1;
        rc = Math.pow((r*k),(1/3))*k;
        k=1;
        theta =Math.acos((-g/(2*r)));
        x1= (2*(rc*Math.cos(theta/3))-(b/(3*a)));
        x2a=rc*-1;
        x2b= Math.cos(theta/3);
        x2c= Math.sqrt(3)*(Math.sin(theta/3));
        x2d= (b/3*a)*-1;
        x2=(x2a*(x2b + x2c))-(b/(3*a));
        x3=(x2a*(x2b - x2c))-(b/(3*a));
        return [x1,x2,x3];
    }

}


function fnBizier(pA,cA,cB,pB){
    var B=[
        function(x){ return x*x*x; },
        function(x){ return 3*x*x*(1-x); },
        function(x){ var x1=(1-x); return 3*x*x1*x1; },
        function(x){ var x1=(1-x); return x1*x1*x1; }
    ],
    C=[pA,cA,cB,pB],
    Ax=3*(C[0][0] - 3*C[1][0] + 3*C[2][0] - C[3][0]),
    Bx=2*(3*C[1][0] - 6*C[2][0] + 3*C[3][0]),
    Cx=(3*C[2][0] - 3*C[3][0]),
    Ay=3*(C[0][1] - 3*C[1][1] + 3*C[2][1] - C[3][1]),
    By=2*(3*C[1][1] - 6*C[2][1] + 3*C[3][1]),
    Cy=(3*C[2][1] - 3*C[3][1]),
    getPoint=function(t){
        var B0=B[0](t),
            B1=B[1](t),
            B2=B[2](t),
            B3=B[3](t),
            x = pA[0]*B0 + cA[0]*B1 + cB[0]*B2 + pB[0]*B3,
            y = pA[1]*B0 + cA[1]*B1 + cB[1]*B2 + pB[1]*B3,
            t2=t*t,
            mx= t2*Ax + t*Bx + Cx,

            my= t2*Ay + t*By + Cy;
        return [x,y,t,mx,my];
    };
    return getPoint;
}


Ejemplo: https://jsfiddle.net/cincibeles/40s89fwx/

No hay comentarios.:

Publicar un comentario