Mostrando las entradas con la etiqueta Matemáticas. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Matemáticas. Mostrar todas las entradas

jueves, 9 de noviembre de 2017

WebGl BufferGeometry Sprite

Éste pretende ser un motor de animación 3d usando cuadros, basado en THREE.js.

... el ejemplo lo tendré pronto ...

*** Download ***

https://drive.google.com/file/d/1hHsDfnmpmjUOaF87CF3IRwi6A84UaSjt/view?usp=sharing

*** Documentación ***

Cargar textura
```
var textureLoader = new THREE.TextureLoader();
textureLoader.load('photo.png', function (texture){
... código ...
});
```

Iniciar motor
Se le pasa la escena three js
```
var enSp = new WebGl_BufferGeometry_Sprite(scene);
```

Crear grupo o familia de textura
Para aumentar la velocidad del plotéo, se me ocurre que todos los objetos tridimencionales que compartan una textura, serán el mismo objeto THREE.BufferGeometry y manipularé sus vertices para que funjan como objetos independientes.
```
var groupMesh = enSp.addTexture(texture);
```

Agregar un objeto 3d
Se le debe pasar el arreglo con las vertices del objeto, el arreglo del mapa de textura, el arreglo de los indices de los polígonos,  arreglo de vertices agrupadas,  arreglo de cuadros de animación.
```
vtx = [[0,2,0,2,-1,0,-2,-1,0], [0,2.2,0,2,-1,0,-2.2,-1,0]];
uvmap = [0.5,0,1,1,0,1];
index = [0,1,2];
groups = [[{x:0,y:0,z:0},2,1],[..  ..],..  ..];
animation = [0,1,0];
var sprite = enSp.addSprite(groupMesh, vtx, uvmap, index, groups, animation);
```

  • Array con las vertices del objeto. Es un array unidimencional que contiene los componentes xyz de las vertices uno tras otro. Además, para agregar cuadros de animación, es posible pasarle un array de arrays donde cada sub array es un nuevo array unidimencional que contiene los componentes xyz de cada cuadro. Importante. Se respeta el orden de las vertices, esperando que la diferencia de entre ellas sea la animación.
  • Array del mapa de textura. Es un array unidimencional que contiene los componentes uv de cada vértice. Importante. nótese que es por vértice y no por polígono. de modo que si queremos tener dos polígonos vecinos con distinto mapa de textura, se requerirá que cada uno tenga sus vertices particulares. Tratando de evitar el caso anterior porque al aumentar las vertices, se reduce el desempeño.
  • Array de indices de polígonos. Es un array unidimencional que contiene los componentes abc de cada polígono, uno tras de otro.
  • Array de vertices agrupadas. Es un array bidimencional que en sus subarreglos contiene un objeto {x,y,z} en su primera posición que especifica el origen del grupo, y seguido, el indice de las vertices que conforman el grupo. Esto es útil para animar o mover por separado un grupo de un objeto.
  • Array de cuadros de animación. Es un array bidimencional que contiene el orden en que el indice de los cuadros de animación se sucederán (los cuadros son los subarrays del Array de vertices). Por ejemplo, si tengo una animación de maniar en tres cuadros, y deseo que se repitan en loop, usaría 0,1,2,1  ... de modo que al animar se sucederían 012101210 ...

Un sprite Objeto o sprite, tiene funciones que permiten modificar su posición, rotación, etc.

  • remove. Remueve el sprite u objeto del motor.
    ```
    sprite.remove();
    ```
  • pos. Especifica la posición de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo.
    ```
    sprite.pos({x:10, y:1, z:0,1}, false);
    ```
  • ang. Especifica el ángulo de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo. Usa radianes.
    ```
    sprite.ang({x:10, y:1, z:0,1}, false);
    ```
  • scale. Especifica la escala de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo. Por default cada uno valen 1. Además es posible especificar todos de un solo golpe pasándole un número en vez del objeto xyz.
    ```
    sprite.scale({x:2, y:2, z:2}, false);
    sprite.scale(2, false);
    ```
  • scroll. Permite recorrer la textura de un objeto o sprite pasándole la cantidad que se moverá en uv el mapa de las vertices.
    ```
    sprite.scroll(u,v);
    ```
  • anim. Anima un sprite según los siguientes parámetros (solo el cuadro inicial es obligatorio -start-):
    ```
    sprite.anim(start, stopin, back, step, onsuccess,  targetx, targety, targetz, targeteasefn, onstep);
    ```
    • cuadro inicial. cuadro con el que arranca la animación.
    • cuadro final. cuadro donde se detendrá la animación.
    • retroceso. si se especifica, volverá el puntero de animación la cantidad `retroceso` permitiendo con ello hacer un loop.
    • velocidad. es la velocidad del puntero en cambiar de cuadro. Por default es 1, pero puedes hacer que dure el doble pasándolo a 0.5
    • animación success. Es una función que se ejecuta cada que la animación supera el cuadro final, o bien, si se reinicia el loop.
    • target x. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target y. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target z. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target ease. si se especifica un punto de traslado, es la función ease que usara para realizar el traslado. Por default es lineal. 
    • función onstep. Es ta función se ejecuta cada que el puntero de la animación avanza, siendo sus argumentos:
      • tEase. un numero entre 0 y 1 que refleja el avance de la animación de forma ease.
      • t. un numero entre 0 y 1 que refleja el avance de la animación de forma real.
  • setGroup. permite cambiar la posición, ángulo o escala de un grupo de vertices según los siguientes parámetros:
    ```
    sprite.setGroup(group, obj, isAdd, _fn, _success, _step);
    ```
    • group. es un numero que indica el indice del grupo de vertices
    • obj. es un elemento de tipo {x,y,z,ax,ay,az,ex,ey,ez} que especifica las nuevas propiedades del grupo.
    • isAdd. si se especifica, es un booleano que indica si las nuevas propiedades son aditivas.
    • _fn. Si se especifica es la función ease con laque sucederá el cambio de especificaciones.
    • _success. si se especifica, es la función que se ejecuta cuando las nuevas especificaciones sucedan.
    • _step. es la velocidad con la que sucederán las nuevas especificaciones. Por default es uno (de inmediato). Por ejemplo, si se quisiera hacer en dos pasos, usaríamos 0.5
  • clone. permite clonar un sprite existente. A veces resulta útil clonar enemigos o efectos.
    ```
    sprite.clone();
    ```
  • join. permite fusionar un sprite en otro para construir sprites más complicados. Conserva los grupos de vertices sólo recorriendo el indice de grupo. Al fusionarlo podemos indicar la posición, ángulo y escala.
    ```
    sprite.join(obj,pos,ang,scale);
    ```
Eejecutar un efecto especial
Clona un sprite para animarlo y auto destruirlo al concluir la animación. (solo sprite es obligatorio)
```

enSp.fx(sprite,x,y,z,e,flip,endX,endY,endZ,step,startFrame,endFrame,callBackFnOnEnd,easeFn,onStep)
```
    • sprite. sprite recurso donde se hará el clon.
    • x. posición donde aparecerá.
    • y. posición donde aparecerá.
    • z. posición donde aparecerá.
    • e. escala que tendrá.
    • flip. no es nada.
    • endX. posición que alcanzará durante la animación
    • endY. posición que alcanzará durante la animación
    • endZ. posición que alcanzará durante la animación
    • step. velovidad con la que el puntero del cuadro avanza. Por default es 1.
    • startFrame. cuadro inicial de la animación
    • endFrame. cuadro final de la animación.
    • callBackFnOnEnd. funcion que se ejecuta justo al terminar la animación.
    • easeFn. funcion ease que se usa para suceder la traslación
    • onStep. funcion que se ejecuta a cada paso de la traslación (tEase, t)

Refrescar cuadro
Es importante refrescar el cuadro constantemente para que todos las acciones tomen efecto.
```
enSp.update()
```

jueves, 16 de febrero de 2017

Deducción de un circulo partiendo de 3 puntos conocidos

Si tenemos tres puntos, podemos suponer que son parte de una circunferencia.


La formula es sencilla. Teniendo los puntos A,B,C, trazo la línea que pasa por AB y la roto 90 grados (la rotación tiene centro a la mitad de ambos puntos) sea (AB)'. Pues bien, créanlo o no, esta línea pasa por el centro de la circunferencia. Si hago lo mismo con los puntos BC para obterner (BC)', entonces, el punto de colisión entre (AB)' y (BC)' será el centro de la circunferencia.


Acá pongo las funciones de javascript para hacerlo

var linesColition=function(x1,y1,m1,x2,y2,m2){
var mm=m1-m2,x,y;
if(m1==m2) return false;
if(isNaN(m1*0)){
x=x1;
y=m2*(x-x2)+y2;
}else
if(isNaN(m2*0)){
x=x2;
y=m1*(x-x1)+y1;
}else{
x=( m1*x1 -m2*x2 +y2 -y1 )/mm;
y=( m2*y1 -m1*y2 +m1*m2*(x2 -x1) )/-mm;
}
return [x,y];
};

var getCircleByThreePoints=function(A,B,C){
var x1=(A[0]+B[0])/2, y1=(A[1]+B[1])/2, m1=(A[0]-B[0])/(B[1]-A[1]),
x2=(B[0]+C[0])/2, y2=(B[1]+C[1])/2, m2=(B[0]-C[0])/(C[1]-B[1]),
p=linesColition(x1,y1,m1,x2,y2,m2), w=p[0]-A[0], h=p[1]-A[1],
r=Math.sqrt(w*w+h*h);
return p? p.concat(r): false;
}

Y se usa algo así, pasandole los puntos A,B,C. Devuelve un arreglo [x,y,radio]:

circle=getCircleByThreePoints( [-10,-5], [0,-10], [2,-7] );


viernes, 3 de junio de 2016

Curva Cuadrática

Ahora comparto la curva cuadrática.
https://jsfiddle.net/cincibeles/z0yq6ndr/

La función:

var QuadraticCurve =function(A,B,C){
var alphaX=A[0]-2*B[0]+C[0], betaX=2*(B[0]-A[0]), gamaX=A[0],
alphaY=A[1]-2*B[1]+C[1], betaY=2*(B[1]-A[1]), gamaY=A[1];
this.getPoint=function(t){
t=t || 0;
var t2=t*t, t_2=2*t,
        mx= t_2*alphaX + betaX,
        my= t_2*alphaY + betaY;
return [t2*alphaX +t*betaX +gamaX,
t2*alphaY +t*betaY +gamaY,
t, mx, my];
};

}

Nos devuelve un array [x, y, time, mx, my], donde x,y son la posición del punto en un tiempo t, y mx,my son los componentes de su pendiente (m = my/mx)

Y como plus, la función del Path..

var cuadraticPath=function(){
var args=[], i=0; while(arguments[i] instanceof Array) args.push(arguments[i++]);
var curves=[];
for(i=0; i<args.length; i+=2)
if(args[i]!=undefined && args[i+1]!=undefined && args[i+2]!=undefined){
curves.push(new QuadraticCurve(args[i],args[i+1],args[i+2]));
}
this.getPoint=function(time){
var i=time*curves.length, ii=~~i, jj=i%1;
if(!curves[ii]) return false;
return curves[ii].getPoint(jj); 
}
}

se usa

c=new cuadraticPath([1,1],[2,4],[4,1],[3,-1],[1,1]);
for(var i=0; i<=1; i+=0.1) console.log(c.getPoint(i));





jueves, 4 de febrero de 2016

El punto está dentro de un poligono bezier y una pista bezier

Las funciones de hoy pretenden identificar si un punto en el espacio bidimensional se encuentra dentro del área que encierra una figura formada por curvas bezier .
El ejemplo: https://jsfiddle.net/cincibeles/0bcag61a/9/
La siguiente función devuelve true o fase según sea el caso. Los parámetros que requiere son: un punto descrito por un array [x,y] y una sucesión de puntos que describan la figura, combinando curvas con líneas [[x1,y1],[x2,y2,cA2,cB2],[x3,y3]]. Donde se puede ver que una curva bezier se describe a través de tres cordenadas (origen A, controlador A y controlador B) usando como extremo final el siguiente punto en el arreglo.  Nota:requiere de funciones descritas abajo.

    var isDotInPolygonB=function (point, vs) {
        if(vs instanceof Array){      
            var x = point[0], y = point[1];
            var xi, yi, xj, yj, bx,by, cx,cy, r;
            var inside = false;
            for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
                xi = vs[i][0]; yi = vs[i][1]; xj = vs[j][0]; yj = vs[j][1];
                if(vs[j][2]!=undefined && vs[j][3]!=undefined && vs[j][4]!=undefined && vs[j][5]!=undefined){
                    bx=vs[j][2]; by=vs[j][3]; cx=vs[j][4]; cy=vs[j][5];
                    r=BizierLineCollide(x,y,0,[xj,yj],[bx,by],[cx,cy],[xi,yi]);
                    r.forEach(function(v){ if(x<v[0]) inside = !inside; });
                }else
                    if ( ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) ) inside = !inside;
            }
            return inside;
        } return false;
    };

Además, presento la misma función aplicada en donas creadas por dos formas bezier una dentro de otra. Por ejemplo un punto dentro de una pista delimitada por curvas bezier. 

En este caso los parametros son el punto [x,y] y dos arreglos de puntos, cada arreglo con la sucesión de puntos necesaria para formar una forma.   Nota:requiere de funciones descritas abajo.

vvar isDotInRoadB=function (point, vsOut, vsIn) {
    if((vsOut instanceof Array) && (vsIn instanceof Array)){
        var x = point[0], y = point[1], colionOut=[], colionIn=[], cntOut=0, cntIn=0;
        var xi, yi, xj, yj, xx, bx,by, cx,cy, r;;
        var inside = false;
        for (var i = 0, j = vsOut.length - 1; i < vsOut.length; j = i++) {
            xi = vsOut[i][0]; yi = vsOut[i][1]; xj = vsOut[j][0]; yj = vsOut[j][1];
            if(vsOut[j][2]!=undefined && vsOut[j][3]!=undefined && vsOut[j][4]!=undefined && vsOut[j][5]!=undefined){
                bx=vsOut[j][2]; by=vsOut[j][3]; cx=vsOut[j][4]; cy=vsOut[j][5];
                r=BizierLineCollide(x,y,0,[xj,yj],[bx,by],[cx,cy],[xi,yi]);
                r.forEach(function(v){
                    if(x<v[0]){ inside = !inside; }
                    else{ colionOut.push([j,i,v[0],v]); }
                    cntOut++;
                });
            }else
            if ( (yi > y) != (yj > y) ){
                xx = (xj - xi) * (y - yi) / (yj - yi) + xi;
                if (x < xx){
                    inside = !inside;
                }else colionOut.push([j,i,xx]);
                cntOut++;
            }
        }
        for (var i = 0, j = vsIn.length - 1; i < vsIn.length; j = i++) {
            xi = vsIn[i][0]; yi = vsIn[i][1]; xj = vsIn[j][0]; yj = vsIn[j][1];
            if(vsIn[j][2]!=undefined && vsIn[j][3]!=undefined && vsIn[j][4]!=undefined && vsIn[j][5]!=undefined){
                bx=vsIn[j][2]; by=vsIn[j][3]; cx=vsIn[j][4]; cy=vsIn[j][5];
                r=BizierLineCollide(x,y,0,[xj,yj],[bx,by],[cx,cy],[xi,yi]);
                r.forEach(function(v){
                    if(x<v[0]){ inside = !inside; }
                    else{ colionIn.push([j,i,v[0],v]); }
                    cntIn++;
                });
            }else
            if ( (yi > y) != (yj > y) ){
                xx = (xj - xi) * (y - yi) / (yj - yi) + xi;
                if (x < xx){
                    inside = !inside;
                }else colionIn.push([j,i,xx]);
                cntIn++;
            }
        }
        if(!inside && colionOut.length>0 && colionIn.length>0 && cntOut-colionOut.length + cntIn-colionIn.length>0){
            var a=colionOut.sort(sortByValue2).pop(), b=colionIn.sort(sortByValue2).pop();
            if(a.length>3){
                var ai=[a[3][0],a[3][1]], aj=[a[3][0]+a[3][3],a[3][1]+a[3][4]];
            }else{
                var ai=[vsOut[a[0]][0],vsOut[a[0]][1]], aj=[vsOut[a[1]][0],vsOut[a[1]][1]];
            }
            if(b.length>3){
                var bi=[b[3][0],b[3][1]], bj=[b[3][0]+b[3][3],b[3][1]+b[3][4]];
            }else{
                var bi=[vsIn[b[0]][0],vsIn[b[0]][1]], bj=[vsIn[b[1]][0],vsIn[b[1]][1]];
            }
            if(triangleNormal(point,ai,aj)*triangleNormal(point,bi,bj)<0) inside=true;
        }
        return inside;
    } return false;
};


Ambas funciones ( isDotInPolygonB y isDotInRoadB ) requieren de funciones de apoyo.

            // encuentra la normal de un triangulo
            var triangleNormal=function(p,a,b){
                var i=a[0]-p[0]; var j=a[1]-p[1]; var k=b[0]-p[0]; var l=b[1]-p[1];
                return i*l-j*k;
            }
            var reverseSortByValue2=function(a,b){ return a[2]>b[2]?1:(a[2]<b[2]?-1:0); }


// devuelve el punto de cruce entre una línea y una curva bizier.
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){
                _return.push(fnB(v));
            }
        }
    });
    return _return;
}

// resuelve hasta una ecuación cubica a través de sus factores.
// fue tomada de http://www.gyplan.com/es/eqcubic_es.html
function eqCubic(a,b,c,d){
    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];
    }

}

// devuelve una función que describe una curva bizier, esta función regresa cada punto a través del parámetro t
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;
}

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/

viernes, 29 de enero de 2016

Curva bezier y ruta de curvas.


Una curva bezier es aquella que se construye a través de cuatro puntos, usando los puntos primero y último como orillas de la curva y los pontos intermedios como controladores de la misma.

Primero presento el constructor de la curva bezier que luego nos devolverá la posición de un punto dentro de la curva según un tiempo que le pasemos (es decir, un tiempo que va de 0 a 1 que sirva como puntero). La he tomado de la siguiente página: http://www.13thparallel.org/archive/bezier-curves/

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


y se usa por ejemplo. Le paso cuatro coordenadas: punto A, controlador A, controlador B, punto B.

fnGetPoint=fnBizier([0,0],[1,2],[3,1],[3,0]);
for(i=0; i<1; i+=0.1) console.log(fnGetPoint(i));

Una vez teniendo esto es fácil crear un caminito, una ruta construida con segmentos bezier.
Enseguida muestro un constructor que une varias curvas y devuelve la posición del punto según un tiempo dado. Donde, por ejemplo, el segmento 1 está entre el tiempo 0 y 1, el 2 entre el 1 y el 2, el n, entre el n-1 y el n;

function bizierPath(){
    var pointsPath=[], i,j,k;
    for(i=0; i<arguments.length; i++)
        if(arguments[i] instanceof Array) pointsPath.push(arguments[i]);
    if(pointsPath.length>3 && (pointsPath.length-4)%3!=0 ) return false;
    var eqBizier=[];
    for(i=0, j=~~(pointsPath.length/3); i<j; i++)
        eqBizier.push(fnBizier(
            pointsPath[k=i*3],
            pointsPath[k+1],
            pointsPath[k+2],
            pointsPath[k+3]));
    this.getPoints=function(time){
        i=~~time, j=time-i;
        if(time<0 || ~~i>=~~(pointsPath.length/3)) return false;
        return eqBizier[i](j);
    }
}

y se usa por ejemplo. Le paso ocho coordenadas, correspondientes a dos curvas, cuatro por curva.

b=new bizierPath([0,0],[1,2],[2,-1],[3,0],[5,2],[6,1],[6,0]);
for(var i=0;i<=2;i+=0.1) console.log(b.getPoints(i)); 

Puedo pasárle cantas curvas necesite, en este caso use dos pero podrían ser 12 parámetros para 3 curvas o 28 parámetros para 7 curvas.

Ecuación de la parabola a partir de dos puntos y la altura

La idea es que teniendo la distancia horizontal (w) y vertical (h) entre los puntos e imaginando una altura sobresaliente (s) de la parábola, entonces podemos encontrar la ecuación de la misma.
Ejemplo: https://jsfiddle.net/cincibeles/nk4LL43x/

La siguiente función nos devuelve la distancia del los puntos al eje central vertical de la parábola (a y b), además del factor constante que modifica la altura (c).

// w,h son la distancia entre los puntos, s es la altura extra de la parabola
x2byWHS=function(w,h,s){
    d=h/s+1;
    // calcula la distancia al centro
    a = w*d/(d+Math.sqrt(d));
    b = w-a;
    // calcula la costante de la parabola, también funciona c=h/(a*a+b*b)
    c = m/(b*b);
    return [a,b,c];
}

siendo entonces, las ecuaciones de la parabola:

C(x-a)(x+a)=0      es decir     Cx²-Ca²=0    ó
C(x-b)(x+b)=h     también     Cx²-Cb²=h

Ambas describen la misma parábola.

Ejemplo: https://jsfiddle.net/cincibeles/nk4LL43x/