
--------------------------------------------
      Tutorial de programacin grfica
                  Por: FAC
    -------------------------------------
     #9 - Deteccin de caras ocultas.
        - Rellenado de polgonos.
--------------------------------------------


Qu dijeron??? Pensaron que ya no habra Tut #9, verdad?

Pues nel... lo que pasa es que estaba terminando mi nuevo demo,
que de hecho es el primero que va en serio, y aunque no es la
gran cosa en lo referente a efectos, trata de un tema muy especial.

Les sugiero que lo bajen, lo descompriman y lo corran. Lo pueden
bajar de la misma direccin en donde se encuentran los tutoriales,
es decir:

         http://galia.fc.uaslp.mx/~ganzo/prog/tutorial.html

El archivo se llama F_INNA.ZIP y ocupa alrededor de 1.4 megas comprimido,
con lo cual cabe perfectamente en un diskette de alta densidad. Al
descomprimirlo (en el disco duro), ocupar unos 2.8 megas ya que contiene
MUCHAS imgenes, y una de ellas es de 968 * 1200 pxels (1.1 megas).

Para correr el demo satisfactoriamente, necesitan una mquina rpida,
algo as como una Pentium 100 o mejor, tambin necesitan 570 Kb de memoria
convencional libre y unos 3 megas de memoria XMS. Adems, si tienen una
SoundBlaster y quieren oir la msica, necesitan unos 2 megas de EMS.

Ah, y por favor, traten de correr ste y casi cualquier otro demo desde
DOS. NO Windows 95, ni ventanas de DOS, ya que en Windows, los demos son
un 10% ms lentos.

Si despus de todo, no pueden correr el demo, o ste se traba o no se
oye la msica, mndenme un e-mail y yo les dir cmo configurar su
computadora. Solamente dganme si estn usando Windows 95 o no.

Adems, si a alguien de ustedes le interesa saber cmo se realiza alguno
de los efectos que se ven en el demo (tneles, rotozooms, escalado, etc)
solamente tienen que escribir un e-mail y les dar algunos tips. Y si la
demanda es suficiente, podra escribir un tutorial sobre ms efectos.


Bueno, vamos a lo que vinimos...


Eliminacin de partes ocultas:
------------------------------

     Ok. Supongo que todos recuerdan lo bsico de lgebra vectorial, verdad?
     Por lo menos, necesitan MUY bien cmo obtener el producto punto y
     el producto cruz entre dos vectores y saber qu es lo que significa
     cada uno de ellos. Si no lo recuerdan o nunca lo han visto, pueden
     escribirme un e-mail y les dar una BREVE explicacin.

     Qu es la eliminacin de partes ocultas?

     Bueno, si recuerdan en el ejemplo del tutorial anterior, algunas veces
     el objeto tridimensional pareca como si se deformara. Yo les dije
     que era un efecto visual y que se poda resolver con uno o dos
     parpadeos ;)

     Lo que en realidad sucede es que no existe ninguna ilusin de
     profundidad, y eso es exactamente lo que queremos lograr cuando
     dibujamos algo en 3D, verdad? La razn de la falta de profundidad
     es principalmente que hasta ahora nuestros objetos solamente estn
     formados por "alambres", es decir que solamente dibujamos las
     aristas... eso lo resolveremos ms tarde, pero supongo que todos
     notaron tambin que a la hora de dibujar un objeto, lo que hacemos
     es dibujar TODAS sus caras, incluso las que no deberan ser visibles.

     Eso est muy mal, principalmente, porque perdemos mucho tiempo
     dibujando caras que al final sern tapadas por otras caras que
     estn ms adelante, y en segundo lugar porque el poco efecto de
     profundidad que nos da un modelo de alambre, se pierde completamente.

     Pero cmo le hacemos para NO dibujar ESAS caras?

     Bueno, para empezar, Cules son ESAS caras?, es decir que necesitamos
     una manera de saber si una cara est de frente al observador o no.

     Eso es mucho ms fcil de lo que parece, (aunque yo pas DIAS tratando
     de deducir la respuesta). Lo que debemos hacer es calcular la NORMAL
     de cada cara y ella nos dir hacia dnde "apunta" la cara.

     Qu diablos es la "normal"?

     Para aquellos que no recuerdan. Una "normal" a un plano es un vector
     perpendicular a ese plano y adems unitario, es decir que su magnitud
     vale 1. Cuando hablamos de las caras de un objeto 3D, la normal a
     una cara es un vector unitario perpendicular al plano de la cara
     que adems apunta HACIA AFUERA del objeto.

     Ahora, imagnense nuestro cubo con una normal en cada cara. Es fcil
     ver (como dira Pantoja) que las caras que son visibles para el
     observador, tienen su normal apuntando HACIA el observador. Ya que
     el observador lo hemos situado en la parte positiva del eje Z,
     las normales de las caras visibles tienen su coordenada Z MAYOR que 0,
     segn nuestro sistema de coordenadas (ver TUT8.TXT).

     Esto significa que las caras ocultas tienen una normal con Z negativa,
     por lo tanto, cuando dibujemos un objeto 3D, simplemente checamos la
     coordenada Z de cada cara, y si es positiva, la dibujamos.

     Y cmo se calcula la normal?

     Fcil. Solamente hay que seguir 3 pasos:

            1.- Obtener 2 vectores que estn SOBRE el plano de la cara.
            2.- Obtener un vector perpendicular a la cara (con producto cruz).
            3.- Normalizar el vector perpendicular.

     Bueno, vamos paso por paso:

     1.- Obtener 2 vectores que estn sobre el plano de la cara.

         - Estos vectores son los correspondientes a dos de las aristas
           (o lados) de la cara. Llammosles A y B:

                              v1    a61       v6
                              /---------------\
                            /a12            a56 \
                       v2 /                       \
                         |                          \ v5
                         |a23                        |
                         |           a34          a45|
                         -----------------------------
                       v3                             v4

           Las 'v' representan los vrtices. Las 'a' representan aristas.
           (por ejemplo: 'a12' es la arista que va de 'v1' a 'v2', ok?).

           RECUERDEN: Es MUY importante que los vrtices se encuentren
                      en un orden INVERSO a la direccin de las manecillas
                      del reloj.

           Bueno, como tenemos las coordenadas de los vrtices, es muy fcil
           obtener los vectores correspondientes a las aristas con una
           simple resta, es decir que:  a12 = v2 - v1,  a23 = v3 - v2,  etc...

           Necesitamos 2 vectores sobre el plano y cada cara tiene por lo
           menos 3 aristas, as que ya estuvo. Usando v1, v2 y v3, obtenemos
           un vector que va de v2 a v1 (a21) y otro que va de v2 a v3 (a23).
           Recuerden que les llamaremos A y B:

                     A = a21 = v1 - v2
                     B = a23 = v3 - v2

           Entonces tenemos algo como esto:

                              v1
                           A  /---------------\
                            /                   \
                       v2 /                       \
                         |                          \
                        B|                           |
                         |                           |
                         -----------------------------
                        v3

           Donde A es un vector que va de v2 a v1, y B es otro vector
           que va de v2 a v3.


     2.- Obtener un vector perpendicular a la cara.

         - Esto es una papa. Simplemente recuerden que el producto
           cruz entre dos vectores nos da otro vector perpendicular
           al plano formado por los dos vectores.

           Por lo tanto, nuestro vector perpendicular (P) queda como:

               P := B x A

           El producto cruz NO es conmutativo. No es lo mismo B x A
           que A x B. Si usan la fabulosa regla de la mano derecha,
           notarn que el vector P apunta hacia el observador, es decir,
           hacia ustedes. Si rotramos nuestro polgono de forma que los
           vrtices quedaran en el orden inverso, obtendramos que P
           apunta hacia dentro del monitor... a menos que hayan impreso
           esto en papel... :)

           Para los que odiaron el curso de Clculo Vectorial, aqu
           tienen la forma de calcular el producto cruz:

                              | i   j   k  |
              P  =  B x A  =  | Bx  By  Bz |
                              | Ax  Ay  Az |


     3.- Normalizar el vector perpendicular.

         - Otra papa. Simplemente dividimos P entre su magnitud y obtendremos
           finalmente el vector normal (N).

                  P
           N  =  ---         |P| = sqrt(Px * Px + Py * Py + Pz * Pz)
                 |P|


     Si no han olvidado el tutorial anterior, recordarn que utilizamos
     nmeros de punto fijo en casi todas las operaciones y que incluso
     definimos funciones para multiplicar y dividir nmeros de punto fijo,
     pero cmo demonios obtenemos la raz cuadrada? Bueno, afortunadamente
     me encontr por ah una funcin que obtiene la raz cuadrada de
     un nmero de punto fijo, codificada por David Boeren. La funcin
     est completamente en ensamblador, por lo que es muy rpida y debido
     a que es material "freeware", la pueden utilizar sin ningn problema
     en sus programas.


     Para la eliminacin de caras ocultas, solamente necesitamos saber
     si la componente Z de la normal (Nz) es positiva o negativa, pero
     las normales tambin tienen muchos otros usos, como veremos ms
     adelante.

     Y tenemos que hacer TODO esto para CADA cara del objeto?

     - S. Pero solamente una vez. Es decir que no tenemos que calcular
       las normales cada vez que vayamos a dibujar el objeto, sino que
       las calculamos una vez al principio del programa, las almacenamos
       junto con la informacin de cada cara y cuando rotemos el objeto,
       tambin rotamos las normales de cada cara.

     En resumen, para eliminar partes ocultas hacemos lo siguiente:

        1.- Despus de generar el objeto, calculamos las normales.
        2.- Al dibujar un objeto, solamente dibujamos las caras cuyas
            normales tengan componente Z mayor o igual a cero.
        3.- Al rotar un objeto, rotamos tanto los vrtices del objeto
            como las normales de sus caras.


     NOTA: - Por ahora, esto es suficiente para dibujar los objetos
             correctamente debido a que son objetos sencillos, pero
             con objetos ms complejos, ser necesario ordenar las
             caras de atrs hacia adelante. (Eso se ver en el siguiente
             tutorial).

     El programa de ejemplo (BASE3D.PAS) es exactamente el mismo que
     el del tutorial anterior, pero se le ha aadido la eliminacin
     de caras ocultas. Los nuevos procedimientos y datos son:

        - En el tipo TCara se ha agregado el siguiente elemento:

                normal : TVertice;

        - Se ha agregado el procedimiento VerticeNormaliza, el cual
          normaliza un vector... (a poco???)

        - La funcin CaraVisible devuelve verdadero si la
          componente Z de la normal es mayor o igual que cero,
          de lo contrario, devuelve falso.

        - El procedimiento Objeto3DRota se ha modificado para
          rotar tanto los vrtices como las normales del objeto.

        - Se ha aadido el procedimiento Objeto3DCalculaNormales.
          qu creen que hace?


     Bueno, por ahora eso es todo lo que haremos para eliminar las
     partes ocultas. Ahora pasaremos a otro tema...


Rellenado de polgonos:
-----------------------

     Hmmmm... (3 meses despus)... bueno, ya son vacaciones y por fin
     tengo tiempo de terminar el tut #9 y #10 y si hay chance, el #11.

     El modelo de alambre (o malla) del objeto permite darnos una idea
     de la forma del objeto, sin embargo, no es muy atractivo, por lo
     tanto, el siguiente adelanto ser rellenar las caras del objeto de
     algn color, dando una apariencia realmente slida.

     Cmo se hace esto?

     Bueno, existen muchos mtodos de rellenado de polgonos, pero aqu
     veremos nicamente uno de ellos, al que yo llamo: "El mtodo de
     rastreo con lmites precalculados".

     Este mtodo es muy sencillo de entender y puede ser "expandido"
     despus para aadir sombreado Gouraud, Phong y mapeo de texturas,
     adems de que es MUY fcil aplicarlo a polgonos CONVEXOS con
     cualquier nmero de lados. Otros algoritmos son (mucho) ms rpidos
     pero solamente se aplican a tringulos.

     El mtodo es el siguiente:

        Digamos que tenemos el siguiente polgono:

                         (x1, y1)
                            /\
                          /    \
              (x2, y2)  /        \  (x4, y4)
                        \        /
                          \    /
                            \/
                         (x3, y3)

              (Recuerden que los vrtices estn en sentido inverso
               a las manecillas del reloj)

        Lo que haremos primero ser averiguar cules son las coordenadas
        Y mnima y mxima (del polgono). Para este caso:

                 Ymin = y1                      Ymax = y3

        Ahora tenemos que averiguar cules son las coordenadas X
        mnima y mxima (o izquierda y derecha) para cada lnea horizontal
        desde Ymin hasta Ymax. Una vez que tenemos esas coordenadas
        almacenadas en dos arreglos (Xmin, Xmax), lo nico que tenemos
        que hacer es dibujar una lnea horizontal desde Xmin hasta Xmax
        para cada lnea de rastreo desde Ymin hasta Ymax.

        Suena complicado? Espero que no, porque en realidad es muy fcil:

              for y := Ymin to Ymax do
              begin
                   HLine(Xmin[y], Xmax[y], y, color, VGA);
              end;

        Ven? Ahora el nico problema es calcular los arreglos Xmin y Xmax.

        La forma de hacerlo es la siguiente:

           - Para cada LADO del polgono calculamos las coordenadas de
             cada punto en la lnea (usando la ecuacin de la recta).

           - Comparamos la coordenada X con el valor de Xmin y Xmax
             correspondientes a la coordenada Y del punto.

           - Si la coordenada X es menor que Xmin, entonces modificamos Xmin.

           - Si la coordenada X es mayor que Xmax, entonces modificamos Xmax.

        El cdigo es mas o menos como sigue:


           procedure ScanSide(x1, y1, x2, y2 : integer);
           { Escanea el lado que va de (x1, y1) a (x2, y2) }
           var y, ydiv : integer;
               x, xinc : real;
           begin
                if y1 > y2 then
                begin
                     temp := y1; y1 := y2; y2 := temp;
                     temp := x1; x1 := x2; x2 := temp;
                end;
                { el cdigo anterior se asegura de que (x1, y1) sea el }
                { punto SUPERIOR y (x2, y2) sea el punto INFERIOR      }

                ydiv := y2 - y1;
                if ydiv = 0 then exit;     { la lnea es horizontal }
                xinc := (x2 - x1) / ydiv;  { obtenemos la pendiente }
                x := x1;                   { valor inicial de x }
                for y := y1 to y2 do
                begin
                     if x < Xmin[y] then Xmin[y] := round(x);
                     if x > Xmax[y] then Xmax[y] := round(x);
                     x := x + xinc;
                end;
           end;


        Claro que en la realidad esto se hace usando nmeros de punto fijo,
        para mayor velocidad. Pero aquellos que tengan un Pentium, (no AMD
        ni Cyrix), pueden usar nmeros reales si usan el coprocesador.
        Esto se hace utilizando el tipo double en lugar de real.


        Luego llamamos a este procedimiento para cada lado del polgono
        y tendremos la informacin necesaria para dibujarlo.

        Debemos inicializar primero los arreglos Xmin y Xmax, llenando
        Xmin con nmeros muy GRANDES y Xmax con nmeros muy PEQUEOS.

             for i := 0 to 199 do
             begin
                  Xmin[i] := 32000;
                  Xmax[i] := -32000;
             end;

        Y eso es todo. El procedimiento completo para rellenar una cara
        de un objeto se encuentra en el programa BASE3D_2.PAS, en el
        procedimiento CaraDibuja().

        Obviamente, tenemos que saber DE QUE COLOR se va a dibujar cada
        cara, por lo que se le ha aadido un campo de datos ms a la
        estructura TCara. Ese campo es el color de la cara y es de tipo byte.

        Ahora todo lo que hay que hacer es un procedimiento que dibuje
        un objeto entero, es decir, que dibuje TODAS las caras VISIBLES
        del objeto. Ese procedimiento es el siguiente:

            procedure Objeto3DDibujaPlano(obj : TObjeto3D; where : word);
            var i : integer;
            begin
                 Objeto3DCalcula2D(obj);    { calcula la proyeccin a 2D }
                 for i := 1 to obj.NCaras do
                 if CaraVisible(obj.cara[i])
                    then CaraDibuja(obj, obj.cara[i], where);
            end;


        El procedimiento solamente calcula las proyecciones a 2D de sus
        vrtices y hace un ciclo para dibujar todas sus caras. Fcil, eh?



El programa de ejemplo BASE3D_2.PAS:
------------------------------------

     El ejemplo muestra a nuestro famoso cubo girando y movindose a lo
     largo del eje Z.

     Chequen muy bien la parte del modelo de alambre (al principio), para
     que NOTEN la eliminacin de caras ocultas. Luego presionen alguna
     tecla y vean cmo el rellenado de polgonos aade un poco ms de
     "realidad" a nuestros objetos 3D.

     El cdigo solamente sirve para mostrar lo que viene en los tutoriales.
     POR FAVOR, no basen sus programas 100% en este cdigo. Es MUY lento
     y est muy mal estructurado.

     Lo mejor sera usar programacin orientada a objetos y un poco de
     ensamblador, pero prefiero que todos entiendan las bases y ustedes
     mismos busquen la manera de optimizar todo esto.

     Para el movimiento del cubo en el eje Z, se utiliza un pequeo truco.
     Primero se hace una tabla de 256 elementos (de 0 a 255):

             var TablaZ : array[0..255] of integer;

     Luego se llena la tabla con las diferentes posiciones del objeto:

           for i := 0 to 255 do
               TablaZ[i] := round(300 * sin(2 * Pi * i / 256));

     Como pueden ver, esto produce exactamente un periodo de la funcin
     seno con una amplitud de 600 (de pico a pico).

     Si no entendieron lo anterior, entonces vyanse a estudiar matemticas.

     Adems tenemos una variable (tablapos) que es de tipo BYTE. Es MUY
     importante que sea de tipo byte, ya que la tabla tiene 256 elementos.

     Lo que se hace es usar tablapos como ndice en TablaZ, y luego
     incrementar tablapos. Cuando tablapos llega a 255, al incrementarlo
     nos da CERO (es un byte!), y entonces regresamos al principio de la
     tabla y empezamos un nuevo periodo. Esto produce un movimiento
     perfectamente contnuo y precalculado, y lo nico que hay que hacer
     es incrementar un solo byte (tablapos).

     Vindolo en forma de cdigo:

              while not keypressed do
              begin
                   Objeto3DMueve(Cubo, 0, 0, TablaZ[tablapos]);
                   .....
                   .....
                   inc(tablapos);
              end;


     lo anterior mas o menos equivale a lo siguiente:

              while not keypressed do
              begin
                   Objeto3DMueve(Cubo, 0, 0, round(300*sin(2*Pi*ang/360)));
                   .....
                   .....
                   inc(ang);
                   if ang >= 360 then ang := 0;
              end;


     Obviamente, la primera versin es ms rpida, aunque consume un poco
     de memoria. La otra ventaja es que si queremos modificar todo el
     movimiento, solamente tenemos que modificar la tabla (TablaZ), y
     no cada lnea donde se encuentre Objeto3DMueve.


CONCLUSIONES:
-------------

     Bueno, hemos dado otro paso dentro de la tercera dimensin, pero
     todava quedan muchas otras cosas que ver.

     Para el siguiente tutorial (#10), haremos una pausa en lo referente
     a 3D, y debido a la demanda popular, veremos cmo dibujar TEXTO
     en el modo 13h. Veremos cmo generar y usar diferentes tipos de letra
     y construiremos una unidad especial para desplegar texto.

     Despus de eso, en el tutorial #11, empezaremos a ver el sombreado
     de polgonos y fuentes de luz. Despus podremos seguir con ordenacin
     de caras, sombreado Gouraud, Phong y mapeo de texturas.

     Y tal vez, por ah salga otro tutorial de efectos, ahora con efectos
     como tneles, distorsin de bitmaps, estela de movimiento, etc...

     Y una vez ms les pido QUE ME MANDEN SUS PROGRAMAS. Si no me mandan
     sus programas, yo voy a pensar que todos ustedes estn muertos y
     nadie ms lee los tutoriales, y por lo tanto, voy a dejar de escribirlos!

     Y para que no digan que no se puede, ahora tengo 3 direcciones
     de e-mail, as que anmense:

                fac@slp1.telmex.net.mx          (preferida)
                shadowfac@hotmail.com           (hotmail)
                ganzo@galia.fc.uaslp.mx         (la vieja y buena galia)

     Bueno, claro que si ests leyendo esto en el ao 2001, probablemente
     ninguna de las direcciones sirve, pero no importa... buscame por ah...



ANUNCIOS CLASIFICADOS:
----------------------

     Nuevo ao, nueva seccin en el tutorial... :)

     Bueno, solamente les quiero informar a TODOS que se aproxima la
     famossima SEMANA DE CIENCIAS, la cual se llevar prximamente
     a cabo en la FACULTAD DE CIENCIAS de la UASLP, del 16 al 2? de Marzo
     de 1998 (mas o menos).

     An falta ms de mes y medio para ese evento, pero aqu les tengo
     un adelanto de algunos eventos que les podran interesar:


        - SUPER CONCURSO DE PROGRAMACION:

                El concurso es interno, as que solamente los alumnos
                de Ciencias pueden entrar. Habr varias SUPERcategoras,
                tanto para principiantes como para supertraumados.

                Algunas de las categoras que se piensan implantar son:

                   - Concurso de Juegos (haz el mejor juego que puedas)
                   - Concurso rpido (el primero que termine, gana)
                   - Concurso de "haz lo que puedas en 30 minutos"

                NADA de problemas ultramatemticos ni megainteligentes.
                Solamente CREATIVIDAD.


        - SEGUNDA EXPOSICION MULTIMEDIA: DEMOS, MUSICA, GRAFICAS, ETC...

                Presentada por Delabu Alama, el nico grupo de demos
                mexicano (hasta ahora).

                Despus del xito de la primera exposicin y el demo
                del Ninja, se har est segunda presentacin con ms
                demos, ms msica y ms grficas.

                Tambin se acepta CUALQUIER aportacin, ya sea de cdigo,
                msica o grficos hechos en computadora. Todas las
                aportaciones sern presentadas en la expo junto con los
                programas que resulten del CONCURSO DE PROGRAMACION.


Eso es todo.... Rima de la semana? Inventen una y mndenmela!

                                                                        FAC.
