2011/03/09

Trigonometría aplicada

Hace unos días me he dado cuenta de lo oxidado que tengo el tema de la trigonometría y más cuando desde siempre he estado muy interesado en la computación gráfica, raytracers, técnicas de representación gráfica, etc... incluso me gustaba la asignatura de álgebra en la facultad y la hasta la usaba!. Es el momento de tirar de wikipedia y refrescar los conocimientos...

En fin, no está de más un repaso rápido al A-B-C de trigonometría y su aplicación inmediata, ya que el 90% de los casos se resuelven con el 1% del conocimiento.

Lo que voy a poner es el resultado, los que estén interesados en el desarrollo de la fórmula, que le pregunten a un matemático.

Partimos de un objeto coordenada
// **** Coord Object ****
function Coord(x, y) {
    this.x = x;
    this.y = y;
}
Empezando por lo más básico, seno y coseno...

* Pitágoras y los tiángulos. A partir de un origen o, una distancia d y un ángulo a respecto a la horizontal necesito saber la coordenada (x,y) para colocar algo allí.
var c = new Coord(o.x + d * Math.cos(a), o.y + d * Math.sin(a));
* Ángulo entre 2 objetos. Teniendo 2 coordenadas m y o, necesito saber el ángulo que hacen entre sí siendo m la coordenada del ratón por ejemplo y o la coordenada del centro del objeto con el que quiero comparar m.
var a = Math.atan2(m.y - o.y, m.x - o.x);
* Distancia entre 2 puntos c1 y c2. Ni que decir tiene, que ambos deben estar en las mismas unidades.
var d = Math.sqrt(Math.pow((c2.x - c1.x), 2) + Math.pow((c2.y - c1.y), 2));
* Traslaciones. Necesito mover un punto c1 en una cantidad indicada por el vector o siendo el resultado c2. Ya se que es una chorradita, pero esta es la manera general de realizar transformaciones afines. La matriz de la traslación sería.

| 1   0  o.x | | c1.x |   | c1.x + o.x |
| 0   1  o.y | | c1.y | = | c1.y + o.y |
| 0   0   1  | |   1  |   |      1     |

Con todo ésto, para trasladar, solo habría que hacer
var c2 = new Coord(c1.x + o.x, c1.y + o.y);
* Transformaciones. Escalados, deformaciones, proyecciones, transformaciones en espejo, etc...  Aquí es donde tiene sentido la "chorradita" de la traslación, ya que ésta es un caso particular de transformación. Todas ellas se pueden combinar simplemente poniendo el valor correcto en la posición de la matriz de antes. Para más detalles, la wikipedia.

* Rotaciones. Otro caso especial de transformación. Para realizarlas, necesitamos definir una matriz de rotación (suponemos 2 dimensiones aunque para 3, es muy parecido). Si se quiere rotar un punto c1 un ángulo a alrededor de otro punto origen o (estamos dando una traslación como implícita, ojo), la matriz sería la siguiente, dando como resultado c2. Ojo, hay que pre-trasladar el punto de interés c1 al origen de la referencia (punto o) antes de empezar ya que o será nuestro (0, 0) para la rotación. Luego se hace la traslación contraria.
| Math.cos(a)  -Math.sin(a) | | c1.x - o.x | = | c2.x |
| Math.sin(a)   Math.cos(a) | | c1.y - o.y |   | c2.y |
Por lo tanto, expresándolo todo junto programáticamete
var c2 = new Coord(0, 0); // Voy a separarlo en 2 líneas por claridad
c2.x = o.x + (Math.cos(a) * (c1.x - o.x)) - (Math.sin(a) * (c1.y - o.y));
c2.y = o.y + (Math.sin(a) * (c1.x - o.x)) + (Math.cos(a) * (c1.y - o.y));
Para 3 dimensiones, os lo dejo como deberes, pero es más de lo mismo.

* Trazado de líneas (a.k.a. trasladar un objeto desde el punto a al punto b). Solo una palabra... Bresenham. Es un algoritmo óptimo únicamente con aritmética entera. Pseudocódigo del algoritmo plagiado vilmente desde la wikipedia:
function line(x0, x1, y0, y1)
     boolean steep := abs(y1 - y0) > abs(x1 - x0)
     if steep then swap(x0, y0), swap(x1, y1)
     if x0 > x1 then swap(x0, x1), swap(y0, y1)
     int deltax := abs(x1 - x0)
     int deltay := abs(y1 - y0)
     int error := deltax / 2
     int ystep
     int y := y0

     int inc
     if x0 < x1 then inc := 1 else inc := -1
     if y0 < y1 then ystep := 1 else ystep := -1
     for x from x0 to x1 with increment inc
         if steep then plot(y,x) else plot(x,y)
         error := error - deltay
         if error < 0 then
             y := y + ystep
             error := error + deltax
Para los vagos (como yo), ya escrito en javascript:
function calcStraightLine (c1, c2) {
    var coords = new Array();
    var x1 = c1.x; var y1 = c1.y; var x2 = c2.x; var y2 = c2.y;
    // Calcula las diferencias t el chequeo de error
    var dx = Math.abs(x2 - x1);
    var dy = Math.abs(y2 - y1);
    var sx = (x1 < x2) ? 1 : -1;
    var sy = (y1 < y2) ? 1 : -1;
    var err = dx - dy;
    // Fija la coordenara origen
    coords.push(new Coordinates(y1, x1));
    // Bucle principal
    while (!((x1 == x2) && (y1 == y2))) {
        var e2 = err << 1;
        if (e2 > -dy) {
            err -= dy;
            x1 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y1 += sy;
        }
        // Guarda la coordenada calculada
        coords.push(new Coordinates(y1, x1));
    }
    return coords;
}
* Trazado de circunferencias. Para dibujarlo se hace por simetría en los 8 octantes usando el algoritmo del punto medio.

Funcion PuntoMedio (int xc, yc, float R)
    Pintar Pixel (0, R)
    Calcular p0 = 5/4 – R // si R es entero, p0 = 1-r
    Para cada xk
        si pk < 0
            Pintar Pixel (xk+1, yk)
            pk+1 = pk + 2xk + 3
        si pk > 0
            Pintar Pixel (xk+1, yk-1)
            pk+1 = pk + 2xk – 2yk+ 5
    Determinar por simetría los puntos de los otros 7 octantes
    Pintar Pixel (x+xc, y+yc)
Hasta aquí hemos llegado.

1 comentario: