 ----------------------------------------------------------------------------
                        
                          TUTORIAL DE PROGRAMACION
                         DE  GRAFICOS  EN  PASCAL
                    
                                
                               
                             
                         
                           
                          
                                 
                                     
                               
                                
                                   
                                     
                      Nmero 12: - Ordenacin de caras -
 ----------------------------------------------------------------------------

 ACERCA DE:

        Los tutoriales de programacin, as como los ejemplos que
        se presentan en ellos, son escritos por Alfonso Alba Cadena
        (FAC) y todos ellos pueden ser encontrados en el archivo
        Hornet en internet:

           http://www.hornet.org/code/tutors/graphics

        El Hornet Archive es el lugar ms seguro para encontrar los
        tutoriales, aunque seguirn estando el servidor de la Facultad
        de Ciencias:

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


        Tambin pueden encontrar otros documentos sobre programacin
        y algunos demos hechos por FAC y Delabu Alama (el primer grupo
        mexicano de demos).

        Para correcciones, dudas, comentarios y sugerencias, dirjanse
        con FAC va e-mail usando alguna de las siguientes direcciones:

             fac@slp1.telmex.net.mx      (preferida)

             shadowfac@hotmail.com       (hotmail (WWW))


        A partir de ahora, el mailing list ya no funcionar mas debido
        a la decadencia de Galia, nuestro servidor, por lo tanto, tendrn
        que conseguir los tutoriales por su propia cuenta.

                                    - o -

        Gracias a todos los que me han escrito por e-mail. Pienso
        seguir con los tutoriales por otro ao, por lo menos, y
        tal vez organizar un concurso de demos por internet. Los
        que estn interesados en participar, escrbanme.

        AVISO: A los que tengan acceso al IRC, les recomiendo ir al
        canal #coders en UnderNET o ircNET. Pueden aprender muchas
        cosas ahi. Tambin estamos tratando de llenar el canal
        #programacion en UnderNET as que anmense y entren ah.


Introduccin:
-------------

     Bueno, la vez pasada vimos cmo aplicar sombreado e iluminacin
     a nuestro cubo tridimensional, sin embargo, un cubo sombreado
     es simplemente un cubo y despus de algn tiempo se vuelve aburrido,
     por lo tanto, antes de ver otros tipos de sombreado o mapeado de
     texturas, primero veremos cmo obtener objetos mas interesantes
     como esferas, toros (toroides o donas) y otros objetos. Para ello,
     necesitarn algn programa de diseo en 3D como 3D Studio o Truspace,
     adems de un programa convertidor que genere una lista de vrtices
     que nosotros podamos leer desde nuestro programa. El programa que
     yo uso se llama 3DTO3D y lo pueden conseguir en las mismas direcciones
     que los tutoriales. El programa es shareware, pero no lo incluyo
     en el tutorial porque el ZIP ocupa casi 300 Kb.

     Cuando estemos trabajando con estos objetos mas complicados se
     nos presentar un problema: debemos dibujar primero las caras que
     estn mas lejos del observador y as hasta llegar a las que estn
     cerca. De otra forma, el objeto no aparecer correctamente. Para
     eso, tendremos que ordenar las caras, usando algn algoritmo de
     ordenacin. Aqu usaremos el Quicksort, pero no voy a explicar el
     algoritmo puesto que no es el objetivo del tutorial.

     Adems, agregaremos algunas funciones extra como el escalado de
     objetos (agrandar o achicar un objeto) y la traslacin, que no es
     mas que mover un objeto con respecto a su posicin actual. Usando
     la traslacin podremos "centrar" un objeto que no est centrado.

     Aparte de eso, nuevamente le he hecho algunos cambios importantes
     a las rutinas de 3D. El principal es que ahora TODAS las funciones
     y procedimientos que reciben como parmetro algna estructura del
     tipo TObjeto3D deben recibir ahora la estructura por medio de un
     apuntador, es decir que ahora reciben un apuntador del tipo
     PTObjeto3D. Esto es porque cuando los objetos se vuelven complicados,
     es mucho mas rpido pasar solamente un apuntador a la estructura
     que TODA la estructura. Y con objetos con mas de 100 caras, es
     imposible pasar estructuras tan grandes a cualquier procedimiento.
     Es por eso que ahora los objetos los vamos a declarar e inicializar
     de la siguiente forma:

           var MiObjeto : PTObjeto3D;
           begin
                MiObjeto := new(PTObjeto3D);
                Objeto3DReinicia(MIObjeto);

                {....... aqu se hace algo interesante......}

                {... y cuando ya no se necesite el objeto 3D...}
                dispose(MiObjeto);
           end;


     No hay que olvidar el uso de new() para reservar la memoria ocupada
     por el objeto, o de otra forma se congelar la mquina.

     Los objetos que vamos a ver llegan a utilizar casi 800 caras. El
     lmite que podemos usar con nuestras rutinas corresponde mas o
     menos a unas 920 caras y 920 vrtices, lo cual genera una estructura
     de casi 64 Kb de memoria (el lmite permitido por Turbo Pascal).



Objetos generados por otros programas:
--------------------------------------

     Ya vimos que es muy fcil generar un cubo en 3D. Sus coordenadas
     son muy fciles de obtener, aunque es un poco tedioso tener que
     utilizar los procedimientos del tipo Objeto3DAgregaVertice().

     Ahora imagnense que queremos generar una esfera. O mejor an,
     un objeto con forma de dona, mejor conocido como "toro". Objetos
     de este tipo no pueden ser generados a mano. Podemos escribir una
     rutina que genere el objeto, ya que son matemticamente fciles
     de describir, pero en ese caso, tendramos que idear una forma de
     generar cada objeto, y algunos no podran ser generados con alguna
     frmula matemtica, por ejemplo, una cara humana.

     Sin embargo, existen muchos programas de diseo en 3D con los cuales
     podemos realizar estas figuras y guardarlas en algn formato de
     archivo conocido. Algunos de estos programas son: 3D Studio, Truspace,
     Lightwave y MoRay. Y cada uno de ellos tiene su propio formato de
     archivo. Es por eso que en vez de aprender varios formatos, nos
     ocuparemos de conocer un solo formato: el formato ASCII del 3D Studio,
     cuyos archivos tienen la extensin .ASC

     Este formato se almacena en un archivo de texto, por lo cual es muy
     fcil de utilizar. Adems, solamente vamos a obtener del archivo
     la informacin de los vrtices y de las caras. He aqu un sencillo
     ejemplo de un archivo .ASC que define un cubo en 3D:

  ----------------------ejemplo de archivo: CUBO.ASC------------------------

Named object: "Cube"
Tri-mesh, Vertices: 8     Faces: 12
Vertex list:
Vertex 0:  X:1.000000     Y:1.000000     Z:1.000000
Vertex 1:  X:-1.000000     Y:1.000000     Z:1.000000
Vertex 2:  X:-1.000000     Y:-1.000000     Z:1.000000
Vertex 3:  X:1.000000     Y:-1.000000     Z:1.000000
Vertex 4:  X:1.000000     Y:1.000000     Z:-1.000000
Vertex 5:  X:-1.000000     Y:1.000000     Z:-1.000000
Vertex 6:  X:-1.000000     Y:-1.000000     Z:-1.000000
Vertex 7:  X:1.000000     Y:-1.000000     Z:-1.000000
Face list:
Face 0:    A:0 B:1 C:2 AB:1 BC:1 CA:1
Smoothing:  1
Face 1:    A:0 B:2 C:3 AB:1 BC:1 CA:1
Smoothing:  1
Face 2:    A:7 B:3 C:2 AB:1 BC:1 CA:1
Smoothing:  1
Face 3:    A:7 B:2 C:6 AB:1 BC:1 CA:1
Smoothing:  1
Face 4:    A:0 B:3 C:7 AB:1 BC:1 CA:1
Smoothing:  1
Face 5:    A:0 B:7 C:4 AB:1 BC:1 CA:1
Smoothing:  1
Face 6:    A:0 B:4 C:5 AB:1 BC:1 CA:1
Smoothing:  1
Face 7:    A:0 B:5 C:1 AB:1 BC:1 CA:1
Smoothing:  1
Face 8:    A:2 B:1 C:5 AB:1 BC:1 CA:1
Smoothing:  1
Face 9:    A:2 B:5 C:6 AB:1 BC:1 CA:1
Smoothing:  1
Face 10:    A:5 B:4 C:7 AB:1 BC:1 CA:1
Smoothing:  1
Face 11:    A:5 B:7 C:6 AB:1 BC:1 CA:1
Smoothing:  1

  ---------------------------fin del ejemplo-------------------------------

     El archivo puede contener alguna informacin adicional como el tipo
     de textura para cada cara o las normales de los vrtices, pero esa
     informacin no nos interesa por ahora y no la vamos a tomar en cuenta.

     Tambin es posible que el archivo contenga mas de un objeto,
     por ejemplo: una figura formada por un cubo, una esfera y una piramide.
     En este caso, lo que haremos ser leer la informacin de todos los
     objetos contenidos en un archivo y manejarlos dentro de nuestro
     programa como un solo objeto. Si quieren, ustedes pueden modificar
     el programa despus para que maneje objetos separados.

     Bueno. Vamos a analizar el formato del archivo. Probablemente, ya
     se dieron cuenta de que no es muy complicado de entender. Lo primero
     que encontramos es una lnea como sta:

                 Named object: "Cube"

     Eso solamente le da un nombre al objeto con fines de referencia
     dentro del programa 3D Studio. A nosotros realmente no nos interesa,
     as que seguimos con la siguiente lnea:

                 Tri-mesh, Vertices: 8         Faces: 12

     Esto nos dice que el objeto se compone de una malla de tringulos
     (tri-mesh). El 3D Studio siempre utiliza tringulos, aunque nuestras
     rutinas 3D pueden trabajar con cualquier polgono convexo.

     Adems, nos da los dos primeros datos importantes que necesitamos
     conocer: el nmero de vrtices y el nmero de caras de nuestro objeto.
     Recuerden que los objetos estn formados por tringulos, es por eso
     que nuestro cubo tiene 12 caras y no 6... cada cuadrado est formado
     por 2 tringulos.

     La siguiente lnea es:

                 Vertex list:

     Lo cual significa que aqu comienza la lista de los vrtices. Cada
     vrtice tiene el siguiente aspecto:

              Vertex 0:  X:1.000000     Y:1.000000     Z:1.000000
              Vertex 1:  X:-1.000000     Y:1.000000     Z:1.000000
              Vertex 2:  X:-1.000000     Y:-1.000000     Z:1.000000
              ...(etc)

     Efectivamente, se nos dan las coordenadas de cada vrtice. En
     3D Studio, el primer vrtice es el cero. En nuestras rutinas, es
     el uno, as que tendremos que hacer una pequea suma para obtener
     los ndices correctos. Adems, podemos ver que el lado de este
     cubo mide 2 unidades, lo cual es muy pequeo comparado con el cubo
     que hemos estado manejando (de lado 100). Esto significa que tendremos
     que agrandar un poco el cubo usando el escalado, pero esto lo veremos
     despus.

     Despus de la informacin de los vrtices, sigue una lnea que dice
     "Face list:", y despus de ella, sigue la informacin de las caras
     en la forma siguiente:

                 Face 0:    A:0 B:1 C:2 AB:1 BC:1 CA:1
                 Smoothing:  1
                 Face 1:    A:0 B:2 C:3 AB:1 BC:1 CA:1
                 Smoothing:  1
                 ...(etc)

     Los tres vrtices de cada cara son conocidos como A, B y C. Por lo
     tanto, lo anterior nos dice que la cara 0 est formada por los
     vrtices 0, 1 y 2. Y la cara 1 est formada por los vrtices 0, 2
     y 3. El resto de la informacin es innecesaria, pero se encuentra
     en el archivo y simplemente hay que ignorarla.


Un lector de archivos .ASC:
---------------------------

     Lo que vamos a hacer es un procedimiento que lea un archivo .ASC,
     que obtenga la informacin del objeto y que la almacene en una
     estructura de tipo TObjeto3D.

     Para ello, vamos a codificar algunas funciones auxiliares que
     en realizarn la mayor parte del trabajo.

     La primera de ellas compara el inicio de una cadena con otra
     cadena. Si la segunda cadena est contenida al inicio de la
     primera, entonces la funcin devolvera un valor verdadero,
     de lo contrario, devolver falso. La funcin es muy sencilla:

          function ComparaInicio(s, pre : string) : boolean;
          var i : integer;
          begin
               ComparaInicio := true;
               { Si s es menor que pre, entonces devuelve falso }
               if Length(s) < Length(pre) then
               begin
                    ComparaInicio := false;
                    exit;
               end;
               { Comprueba caracter por caracter }
               for i := 1 to Length(pre) do
                   if s[i] <> pre[i] then
                   begin
                        ComparaInicio := false;
                        exit;
                   end;
          end;


     Esta funcin nos servira para buscar palabras como "Vertex" y
     "Face" al principio de cada lnea y de esa forma encontraremos
     la informacin que buscamos.

     La segunda funcin auxiliar toma una cadena y elimina todos los
     caracteres desde el principio hasta que encuentra dos puntos (:).
     As obtendremos la informacin que est justo despus de cadenas
     como 'Vertex 0:  X: 1.00000     Y: 1.00000       Z: 1.00000' :


          function EliminaHastaDosPuntos(var s : string) : boolean;
          var i : integer;
              ss : string;
          begin
               EliminaHastaDosPuntos := false;
               ss := '';
               i := 1;
               { Busca los dos puntos }
               while (i < Length(s)) and (s[i] <> ':') do inc(i);
               { Si no los encontr, devuelve true }
               if i = Length(s) then
               begin
                    EliminaHastaDosPuntos := true;
                    exit;
               end;
               { Si los encontr, entonces obtiene el resto de la cadena }
               { pero elimina los espacios no deseados }
               inc(i);
               while s[i] = ' ' do inc(i);
               { copia el resto de la cadena en ss }
               while (i <= Length(s)) do
               begin
                    ss := ss + s[i];
                    inc(i);
               end;
               { sustituye la cadena original por la cadena nueva }
               s := ss;
          end;


     Y la ltima funcin auxiliar tambin es muy sencilla. Simplemente
     obtiene el valor numrico que se encuentra al principio de una
     cadena. Por ejemplo, si la cadena es '1.00000    Z: 1.00000', la
     funcin devolver el valor 1.0000 sin tomar en cuenta el resto de
     la cadena:


          function ObtenerValor(var s : string) : double;
          var sub : string;
              i : integer;
              valor : double;
          begin
               sub := '';
               i := 1;
               { Obtiene una subcadena hasta que encuentra un espacio }
               while (i <= Length(s)) and (s[i] <> ' ') do
               begin
                    sub := sub + s[i];
                    inc(i);
               end;
               { Obtiene el valor numrico de la subcadena }
               val(sub, valor, i);
               ObtenerValor := valor;
          end;


     Las funciones anteriores son simple manejo de cadenas y no deben
     presentar ningn problema. Teniendo a la mano estas funciones,
     podemos empezar a leer el archivo .ASC. Obviamente, primero hay
     que abrir el archivo e inicializar la estructura TObjeto3D en la
     que vamos a almacenar la figura:

               Objeto3DReinicia(obj);
               assign(ascii, archivo);
               reset(ascii);


     Ahora, lo primero es encontrar la lnea que comienza con "Tri-mesh"
     y obtener de esa lnea el nmero de vrtices y de caras:

          temp := '';
          while not ComparaInicio(temp, 'Tri-mesh') do readln(ascii, temp);

     Lo anterior busca la lnea "Tri-mesh" y la almacena en la cadena temp.
     Ahora recorremos esa cadena hasta que encontremos unos dos puntos (:)
     y obtenemos el nmero de vrtices. Luego hacemos lo mismo para obtener
     el nmero de caras:

          EliminaHastaDosPuntos(temp);
          NumVertices := trunc(ObtenerValor(temp));
          EliminaHastaDosPuntos(temp);
          NumCaras := trunc(ObtenerValor(temp));

     Como pueden ver, las funciones que definimos nos ayudan mucho.
     Ahora que tenemos el numero de vertices, podemos empezar a leer
     sus coordenadas. Primero, buscamos la lnea que dice "Vertex list"
     y nos la saltamos:

          temp := '';
          while not ComparaInicio(temp, 'Vertex list') do readln(ascii, temp);


     Luego, hacemos un ciclo para leer la informacin de cada vrtice.
     Dentro de ese ciclo, buscamos lneas que empiecen con "Vertex",
     y de esa lnea extraemos la informacin de las coordenadas. Recuerden
     que esas lneas tienen la siguiente apariencia:

        "Vertex 0:      X: 1.00000     Y: 1.00000      Z: 1.00000"

          temp := '';
          while not ComparaInicio(temp, 'Vertex') do readln(ascii, temp);
          EliminaHastaDosPuntos(temp);
          EliminaHastaDosPuntos(temp);
          x := ObtenerValor(temp);
          EliminaHastaDosPuntos(temp);
          y := ObtenerValor(temp);
          EliminaHastaDosPuntos(temp);
          z := ObtenerValor(temp);

     Ya que tenemos las coordenadas del vrtice, lo agregamos al objeto:

          Objeto3DAgregaVertice(obj, x, y, z);

     Y repetimos ese proceso un nmero de veces igual a NumVertices.

     Ahora continuamos con la informacin de las caras. Lo primero es
     encontrar la lnea que dice "Face list":

          temp := '';
          while not ComparaInicio(temp, 'Face list') do readln(ascii, temp);


     Y luego, repetir para cada cara lo siguiente: Buscamos las lneas
     que comiencen con "Face" y extraemos los ndices de los tres vrtices.
     Cada lnea "Face" tiene la siguiente forma:

          "Face 0:    A:0 B:1 C:2 AB:1 BC:1 CA:1"

          temp := '';
          while not ComparaInicio(temp, 'Face') do readln(ascii, temp);
          EliminaHastaDosPuntos(temp);
          EliminaHastaDosPuntos(temp);
          a := trunc(ObtenerValor(temp)) + 1;
          EliminaHastaDosPuntos(temp);
          b := trunc(ObtenerValor(temp)) + 1;
          EliminaHastaDosPuntos(temp);
          c := trunc(ObtenerValor(temp)) + 1;

     Recuerden que en 3D Studio los ndices de los vrtices comienzan en
     cero y no en uno, por eso es que le sumamos uno a cada ndice.

     Ahora, podemos agregar los vertices a una cara y agregar la cara
     al objeto:

          CaraAgregaVertice(cara, a);
          CaraAgregaVertice(cara, b);
          CaraAgregaVertice(cara, c);
          Objeto3DAgregaCara(obj, cara);


     Una vez que hicimos esto para todas las caras, podemos cerrar el
     archivo y calcular las normales del objeto:

          close(ascii);
          Objeto3DCalculaNormales(obj);


     Y mas o menos as es como se lee un archivo ASCII de 3D Studio.
     El procedimiento completo se encuentra en la unidad OBJETO3D.PAS,
     y adems, ese procedimiento puede trabajar con archivos que
     contengan varios objetos. Tambin se comprueba si no se ha llegado
     al final del archivo, lo cual producira algunos errores.

     Bueno, esto es todo lo que hay que saber por ahora sobre los
     archivos .ASC. La mayora de los programas de diseo 3D pueden
     exportar sus objetos a este tipo de archivo, y de no ser posible,
     les aconsejo que consigan el programa 3DTO3D, el cual convierte
     entre varios formatos tridimensionales.

     Ahora vamos a ver cmo "acomodar" un objeto despus de que lo
     hemos ledo de un archivo.....


Algunas transformaciones tiles:
--------------------------------

     Traslacin:

     La traslacin solamente significa mover el objeto a una posicin,
     relativa a su posicin actual. Nosotros tenemos el procedimiento
     Objeto3DMueve(), pero ste procedimiento NO traslada el objeto,
     sino que solamente cambia la posicin del origen dentro de nuestro
     espacio tridimensional, sin embargo, el objeto no cambia de posicin
     con respecto a ste origen.

     La traslacin s mueve al objeto con respecto a su origen. Esto
     produce efectos muy distintos a lo que hace Objeto3DMueve().

     Por ejemplo, digamos que tenemos un cubo cuyo centro es el origen
     y ese origen se localiza en el punto (0, 0, 0), es decir, en el
     centro del espacio 3D. Cuando el cubo comienza a rotar, ste rotar
     sobre s mismo, tal y como lo hemos visto antes. Ahora, supongamos
     que hacemos lo siguiente:

                 Objeto3DMueve(cubo, 100, 0, 0);

     Entonces, el origen del cubo se desplazar hacia la derecha, y por
     lo tanto, todo el cubo se mover, ya que sus coordenadas son relativas
     al origen. Eso significa que el origen sigue estando en el centro del
     cubo y por lo tanto, seguir rotando sobre s mismo.

     Ahora digamos que dejamos el origen en su posicin original (0, 0, 0),
     y en vez de mover el origen, le aadimos 100 a la coordenada X de
     todos los vrtices del cubo, es decir que lo TRASLADAMOS 100 unidades
     hacia la derecha.

     Lo que pasa aqu es que el origen del cubo NO se mueve, sin embargo,
     el cubo s se ha movido con respecto a ese origen. Ahora, cuando
     hagamos una rotacin alrededor del eje Y, el cubo no girar sobre s
     mismo, sino que parecer orbitar alrededor del origen.

     Bueno, solamente traten de imaginrselo. Ahora, es muy fcil realizar
     una traslacin, simplemente se le suma un vector de traslacin a
     todos los vrtices del objeto:

           with objeto do
                for i := 1 to NVertices do
                begin
                     Vertice[i].x := Vertice[i].x + tx;
                     Vertice[i].y := Vertice[i].y + ty;
                     Vertice[i].z := Vertice[i].z + tz;
                end;


     La traslacin la podemos usar para muchas cosas, por ejemplo, hacer
     que un objeto "orbite" alrededor de algo. O para "centrar" un objeto
     con respecto a su origen. O para crear una escena tridimensional donde
     varios objetos tienen el mismo origen, pero estn en distintas
     posiciones.


     Escalado:

     El escalado es otra de las transformaciones bsicas que se le pueden
     aplicar a los objetos tridimensionales. Se usa bsicamente para
     hacer un objeto mas grande o mas pequeo, pero se puede usar tambin
     para deformar un objeto, escalndolo sobre uno de sus ejes pero no
     sobre los otros.

     Para realizar el escalado, simplemente multiplicamos cada componente
     de cada vrtice por un escalar. Si este escalar es distinto para cada
     componente (x, y, z), entonces obtendremos una deformacin del objeto:

           with objeto do
                for i := 1 to NVertices do
                begin
                     Vertice[i].x := Vertice[i].x * sx;
                     Vertice[i].y := Vertice[i].y * sy;
                     Vertice[i].z := Vertice[i].z * sz;
                end;

     Si alguno de los factores sx, sy, sz es igual a 1, entonces el objeto
     no se deformar sobre ese eje. Obviamente, los factores nunca deben
     ser igual a cero, ya que esto destruira la informacin de los vrtices,
     sin embargo, se puede usar esto para obtener una proyeccin paralela
     del objeto sobre algno de los planos XY, XZ o YZ.

     Los factores mayores que 1 hacen al objeto mas grande, los menores que
     1 lo hacen mas pequeo, y los factores negativos producen una "imagen
     de espejo" del objeto.


     Bueno, ahora vamos a ver en qu podemos aplicar estas transformaciones
     nuevas. Digamos que acaban de disear una nave espacial 3D con
     su programa favorito y lo quieren usar para un juego. Lo guardan en
     formato ASC y luego lo cargan desde el juego, y entonces..........
     WOOPS!  La nave est completamente fuera del centro de la pantalla.
     Por alguna razn, en el programa que utilizaron no estaba centrada
     con respecto a su origen a la hora de guardar el archivo. Y lo peor
     de todo es que a la hora de aplicar una rotacin a la nave, est
     da vueltas por toda la pantalla, en lugar de girar sobre s misma.

     Lo que hay que hacer es centrar la nave con respecto al origen. Para
     ello, tenemos que realizar una traslacin, pero hacia dnde hay que
     trasladarla?

     Bueno, primero tenemos que calcular las coordenadas del centro actual
     de la nave. Esto se hace de la misma forma que calculbamos el centro
     de una cara al aplicar sombreado por distancia, es decir, calculamos
     el promedio de todos los vrtices del objeto:

                 with objeto do
                 begin
                      for i := 1 to NVertices do
                      begin
                           tx := tx + Vertice[i].x;
                           ty := ty + Vertice[i].y;
                           tz := tz + Vertice[i].z;
                      end;
                      tx := tx / NVertices;
                      ty := ty / NVertices;
                      tz := tz / NVertices;
                 end;


     Bueno, entonces actualmente el centro de la nave est en (tx, ty, tz),
     y lo que queremos es que est en el origen, es decir, en (0, 0, 0).

     Ahora, si al vector (tx, ty, tz) le sumamos el vector (-tx, -ty, -tz),
     entonces obtendremos lo que queremos: (0, 0, 0), por lo tanto, lo que
     hay que hacer es trasladar todo el objeto (-tx, -ty, -tz) unidades.

             Objeto3DTraslada(obj, -tx, -ty, -tz);


     Y de esa forma, nuestro objeto estar centrado! Si queremos mover el
     centro a alguna posicin mas especfica, simplemente, volvemos a
     trasladarlo a esa posicin.


     El segundo problema que se nos presenta al importar archivos .ASC,
     es que algunas veces, los objetos quedan demasiado grandes o demasiado
     pequeos, y entonces tenemos que realizar un escalado para que se
     ajusten a lo que queremos. Si el objeto es muy grande, tendremos que
     escalarlo por un factor menor que 1, y si es muy pequeo, el factor
     tendr que ser mayor que 1. El problema es cmo determinar ese factor.

     Bueno, para eso, he desarrollado otro procedimiento que escala un
     objeto de tal manera que la distancia de algn vrtice al origen
     nunca llegue a ser mayor que algn valor proporcionado por nosotros,
     adems, los objetos que sean muy pequeos, los agranda todo lo que
     se pueda, sin pasarse de este valor. El procedimiento es muy sencillo,
     lo primero que se hace es calcular la mxima distancia desde algn
     vrtice hasta el origen:

             max := 0;
             with obj^ do
                  for i := 1 to NVertices do
                  begin
                       dist := sqrt(Vertice[i].x * Vertice[i].x +
                                    Vertice[i].y * Vertice[i].y +
                                    Vertice[i].z * Vertice[i].z);
                       if dist > max then max := dist;
                  end;

     Ahora, un poco de matemticas:

     - dist es la distancia desde algn vrtice hasta el origen
     - max es la distancia del vrtice mas alejado (que acabamos de calcular)
     - distmax es el valor mximo que vamos a permitir para esas distancias

     Obviamente, lo que queremos es que la magnitud de los vrtices (dist)
     nunca sea mayor que distmax. Y estamos seguros de que esa magnitud
     actualmente nunca es mayor que max, por lo tanto, si dividimos
     dist / max, obtendremos un valor menor o igual a uno. Si ese valor
     lo multiplicamos por distmax, obtendremos un valor no mayor que
     distmax. Por lo tanto, la magnitud del nuevo vrtice ser:

         dist'  :=  dist / max * distmax  :=  dist * (distmax / max)

     pero dist es igual a sqrt(x * x + y * y + z * z), por lo tanto:

         dist'  :=  sqrt(x * x + y * y + z * z) * (distmax / max)

     y metiendo (distmax / max) dentro de la raz, obtenemos:

         dist'  :=  sqrt( sqr(x * (distmax / max)) +
                          sqr(y * (distmax / max)) +
                          sqr(z * (distmax / max));

     De aqu obtenemos las siguientes relaciones:

             x'  :=  x * (distmax / max);
             y'  :=  y * (distmax / max);
             z'  :=  z * (distmax / max);

     Bueno, y qu demonios significa todo esto?

     Significa que si nosotros escalamos todos los vrtices del objeto
     por un factor igual a (distmax / max), entonces el objeto encajar
     perfectamente en una esfera de radio igual a distmax. Ni mas grande,
     ni mas pequeo. Por lo tanto, despus de calcular el valor de max,
     lo nico que hay que hacer es:

              escala := distmax / max;
              Objeto3DEscala(obj, escala, escala, escala);

     Y de esa forma, podemos hacer que el objeto encaje en una esfera
     de radio determinado, sin importar qu tan grande o que tan chico
     era antes. Y esto lo hace el procedimiento Objeto3DEncaja(), el
     cual pueden encontrar en OBJETO3D.PAS.

     Ahora, cmo hacer que CUALQUIER objeto quepa perfectamente en la
     pantalla?

     Primero, despus de cargar el objeto, hay que centrarlo, luego
     hay que hacer que encaje en una esfera de mas o menos 180 unidades
     de radio, y eso es todo. Son simplemente tres lneas:

               Objeto3DLeeASC('miobjeto.asc', objeto, false);
               Objeto3DCentra(objeto);
               Objeto3DEncaja(objeto, 180);
               Objeto3DMueve(objeto, 0, 0, 0);

     La ltima lnea simplemente se asegura que el origen del objeto
     est en el centro de la pantalla, puesto que an no hemos implementado
     ninguna operacin de recorte o clipping, y si el objeto se sale
     de la pantalla, la mquina probablemente se congele.


MUESTRA.EXE:
------------

     El programa de ejemplo MUESTRA.EXE solamente se encuentra en forma
     compilada. En realidad, es exactamente el mismo que BASE3D_5.PAS,
     pero sin el ordenamiento de polgonos, que an no hemos visto.

     Para usar el programa, hay que pasarle el nombre de algn archivo
     .ASC en la lnea de comandos, incluyendo la extensin. Por ejemplo,
     para mostrar el objeto CUBO.ASC, hay que escribir:

                  muestra cubo.asc

     Obviamente, nadie quiere volver a ver un cubo, as que intenten algo
     como:

                  muestra thing.asc

     O alguno de los otros archivos que se incluyen con el tutorial.

     Como pueden ver, no importa qu objeto carguen, ste siempre cabr
     perfectamente en la pantalla y estar siempre centrado.


     Ahora, intenten lo siguiente:

                  muestra tutwire.asc

     TUTWIRE.ASC es un objeto compuesto por dos toroides delgados. Ahora
     opriman 's' para darle sombreado plano y observen que cuando el
     objeto gira se ve de alguna forma extrao: en ocasiones un toro
     parece ser mas grande que otro, pero los dos son del mismo tamao,
     y no es posible decidir cul de los dos est enfrente y cul detrs.

     Lo mismo sucede con el objeto SOFTEN.ASC, el cual consta de dos
     esferas: cuando una esfera parece que va a ocultarse detrs de la
     otra, eso no sucede, sino que se sigue viendo delante de ella.

     La razn de esto es muy simple. Llammosle a cada uno de los dos
     toroides A y B. En el archivo ASCII, esta figura estaba como dos
     objetos separados. Primero lemos la informacin del toro A y luego
     la del B. Por lo tanto, en el arreglo que contiene la informacin
     de las caras, los polgonos del toro A estn antes que los del B,
     y por lo tanto, a la hora de dibujar la figura, el toro A siempre
     se dibujar antes que el toro B. An cuando el toro A sea el que
     est mas cerca del observador. Esto obviamente produce problemas
     de visualizacin. La solucin es pintar las caras de atrs hacia
     adelante, es decir, empezando con las que son mas lejanas al
     observador.


Ordenacin de polgonos:
------------------------

     El observador est en la parte positiva del eje Z. Por lo tanto,
     para dibujar las caras de atras hacia adelante, primero hay que
     ordenarlas con respecto a las coordenadas Z de sus vrtices. Las
     que tengan coordenadas Z menores sern dibujadas primero, y as
     hasta las caras cuyos vrtices tienen coordenadas Z mayores.

     Eso significa que a partir de las coordenadas Z de los vrtices
     de una cara, nosotros debemos obtener un cierto valor de "profundidad"
     de esa cara. La forma mas sencilla y rpida de hacer esto es
     sumar todas las coordenadas Z. El valor obtenido lo almacenaremos
     en una variable perteneciente a la estructura TCara. Esta variable
     yo la he llamado simplemente Z y es de tipo double. Como ahora el
     tipo TCara tiene un elemento mas, esto hace que tengamos que reducir
     el numero mximo de vrtices y caras de un objeto a mas o menos 870,
     para que el tipo TObjeto3D no sobrepase el lmite de 64 Kb.

     Bueno, como ya dije, al valor de profundidad Z de cada cara le
     vamos a asignar la suma de las coordenadas Z de sus vrtices.
     Esto lo hacemos con un sencillo procedimiento:

          procedure CaraCalculaZ(var cara : TCara; obj : PTObjeto3D);
          var i : integer;
          begin
               with cara do
               begin
                    Z := 0;
                    for i := 1 to NVertices do
                        Z := Z + obj^.Vertice[Vertice[i]].z;
               end;
          end;


     Ahora, lo que hay que hacer es ordenar las caras con respecto a
     su valor de profundidad Z, de menor a mayor.

     Para eso, vamos a hacer uso de dos herramientas: apuntadores y
     el algoritmo QuickSort. El QuickSort es uno de los algoritmos de
     ordenacin mas rpidos que hay, pero me llevara algn tiempo
     explicarlo, as que si quieren saber cmo funciona, busquen un
     libro sobre estructuras de datos.

     Los apuntadores los vamos a usar por una sencilla razn: una
     estructura de tipo TCara ocupa alrededor de 48 bytes, mientras
     que un apuntador necesita slamente 8 bytes, por lo tanto, es
     mucho mas rpido ordenar un arreglo de apuntadores que uno de
     estructuras TCara.

     Por lo tanto, vamos a definir algunos tipos extra que nos van
     a ayudar a hacer el ordenamiento:

              type PTCara = ^TCara;
                   TCaraArray = array[1..MaxCaras] of PTCara;
                   PTCaraArray = ^TCaraArray;


     El tipo PTCara simplemente es un apuntador a una estructura TCara,
     el tipo TCaraArray es un arreglo de apuntadores a caras y el tipo
     PTCaraArray es un apuntador a un arreglo TCaraArray, es decir,
     un apuntador a un arreglo de apuntadores. Si esto les parece confuso,
     esperen a ver lo que vamos a hacer con todos estos tipos ;)

     A la hora de dibujar (renderizar) un objeto, vamos a seguir los
     siguientes pasos:

               1) Calcular la proyeccin 2D de cada vrtice del objeto

               2) Generar un arreglo de tipo TCaraArray que contenga
                  apuntadores a todas las caras que son visibles

               3) Calcular la profundidad Z de cada cara en el arreglo

               4) Ordenar el arreglo segn su profundidad, usando QuickSort

               5) Dibujar todas las caras del arreglo en orden.


     El primer paso ya sabemos cmo hacerlo. El segundo es muy sencillo:

               var a : TCaraArray;
                   i, j : integer;
               ...
               j := 1;
               for i := 1 to objeto^.NCaras do
                   if CaraVisible(objeto^.cara[i], objeto) then
                   begin
                        a[j] := addr(objeto^.cara[i]);
                        inc(j);
                   end;

     Si una cara es visible, entonces se agrega su direccin al arreglo a[]
     y se incrementa la posicin dentro de este arreglo. Al final, tendremos
     que el nmero total de caras visibles es igual a j - 1.

     El tercer paso lo podemos combinar junto con el segundo de la siguiente
     manera:

               var a : TCaraArray;
                   i, j : integer;
               ...
               j := 1;
               for i := 1 to objeto^.NCaras do
                   if CaraVisible(objeto^.cara[i], objeto) then
                   begin
                        a[j] := addr(objeto^.cara[i]);
                        inc(j);
                        CaraCalculaZ(objeto^.cara[i], objeto);
                   end;


     El cuarto paso simplemente hace uso del procedimiento QuickSort
     definido en OBJETO3D.PAS. Este procedimiento toma como parmetros
     un apuntador a un arreglo TCaraArray, y los ndices de el primer
     y ltimo elementos a ordenar. En nuestro caso, siempre ordenaremos
     desde el primer elemento hasta el elemento j - 1:

              QuickSort(addr(a), 1, j - 1);


     Y eso termina con el cuarto paso. Solo hay que tener en cuenta una
     cosa MUY importante: El algoritmo QuickSort es un algoritmo recursivo,
     es decir que el procedimiento se llama a s mismo, llenando la pila
     cada vez que se auto-llama. Si el espacio en la pila es muy reducido,
     se producir un desbordamiento de pila y el programa terminar
     abruptamente. Para prevenir eso, debemos incrementar el espacio
     en la pila con la siguiente lnea (con todo y llaves):

           {$M 65520, 0, 655360}

     Lo cual incrementa la pila (stack) a 65520 bytes, suficiente para
     objetos muy grandes.

     Esa lnea se debe inclur AL PRINCIPIO de cada programa que utilice
     la unidad OBJETO3D.PAS.


     El quinto paso es muy sencillo, una vez que el arreglo ya est
     ordenado. Simplemente hay que hacer un ciclo desde 1 hasta j - 1
     y dibujar todas las caras en el arreglo a[]:

            for i := 1 to (j - 1) do CaraDibuja(obj, a[i]^, where);

     Obviamente, si vamos a aplicar sombreado, hay que calcular primero
     el color de la cara, pero esa parte la pueden revisar dentro del
     procedimiento Objeto3DDibujaSombreadoPlano() ....... phew!


     Y eso es todo lo que hay que hacer para implementar el ordenamiento
     de caras. Ahora s podemos desplegar objetos mas complejos sin
     ningn problema, aunque el ordenamiento disminuye un poco la velocidad
     del programa. Por cierto, cuando se dibuja nicamente la malla del
     objeto, no es necesario ordenar las caras.

     El programa BASE3D_5.EXE funciona exactamente igual que el programa
     MUESTRA.EXE, pero haciendo uso de la ordenacin de polgonos. Pueden
     comprobar las diferencias entre los dos mostrando objetos como
     TUTWIRE.ASC, SOFTEN.ASC o PINATA.ASC y aplicando sombreado plano.


Mejoras en la velocidad:
------------------------

     La velocidad de nuestro motor 3D no es algo de lo que podemos
     estar muy orgullosos. En realidad, es un motor muy lento,
     principalmente debido a que hasta ahora he tratado de evitar
     los apuntadores y el ensamblador.

     He aqu algunas mediciones de la velocidad de varios motores 3D
     probados en mi mquina: una AMD K6 a 166 Mhz con tarjeta de video
     S3 Virge/DX.

     Las pruebas las hice con el objeto PINATA.ASC, el cual tiene 134
     vrtices y 264 caras. La prueba equivale a lo que hace el programa
     BASE3D_5.EXE aplicando sombreado plano y obteniendo la velocidad
     en cuadros por segundo. Adems, en ninguna prueba se utiliz el
     VRetrace(), ya que se pierde algo de tiempo en esperar el retrazo
     vertical. Los resultados fueron aproximadamente los siguientes:

     Con este motor (base3d_5.exe):         60 cuadros por segundo

     Con el motor Vektor para TurboPascal:  100 cuadros por segundo

     Con el motor Vektor para TMT Pascal:   95 cuadros por segundo

     Con el motor Vektor para Watcom C++:   140 cuadros por segundo
     *El mismo motor en alta resolucin:     40 cuadros por segundo


     * Los motores para TMT Pascal y Watcom C++ incluyen clipping.

     * El TMT Pascal usa instrucciones de 32 bits y por eso es
       hasta un 30% mas rpido que el Turbo Pascal.

     * La versin para Watcom C++ usa un algoritmo de ordenacin
       diferente, llamado Radix Sort, el cual es por lo menos un
       200% mas rpido que el QuickSort en este tipo de aplicaciones.

     * El motor en Watcom C++ soporta tambin alta resolucin de 640 * 480.
       El nmero de pxels en esta resolucin es casi 4.8 veces mayor que
       en el modo 13h, es por eso que la velocidad solo llega a 40 cps.


     Qu se puede hacer para mejorar la velocidad?

     Bueno, lo primero es usar apuntadores, los cuales aceleran mucho
     a la hora de pasar parmetros a las funciones. Adems, usando
     apuntadores podemos conservar mucha memoria, debido a que la
     asignacin de memoria se hace dinmicamente y el tamao de una
     estructura de apuntadores puede variar dentro del programa. Esto
     tambin permite generar objetos que requieran mas de 64 Kb de
     memoria, ya que la estructura principal contendr apuntadores a los
     vrtices y a las caras, no toda la informacin sobre ellos.

     Usando apuntadores ya no sera necesario el uso de referencias
     complejas como:

               objeto.Vertice[objeto.Cara[i].Vertice[j]].x

     Con apuntadores, el hacer referencia a algn vertice de alguna
     cara es algo tan simple como:  Cara^.Vertice[i]^.x

     Esto, adems de hacer el programa mas fcil de entender, tambin
     lo hace mas rpido, sin embargo, se necesita entender perfectamente
     el funcionamiento de los apuntadores y tener algo de prctica con
     ellos, as que esa parte se las dejo a ustedes.

     Otra forma de acelerar el programa es implementando algunas funciones
     en ensamblador, por ejemplo, la rotacin y proyeccin a 2D de los
     vrtices y el renderizado de los polgonos. El usar ensamblador
     requiere tambin de cierto grado de conocimiento y de mucha prctica.

     Y finalmente, siempre es buena idea tratar de conseguir el mejor
     compilador que se pueda. Como pueden ver, el motor hecho para
     Watcom C++ es al menos un 40% mas rpido que los dems. Esto es
     en parte al manejo de punteros del lenguaje C, y en parte a las
     excelentes opciones de optimizacin del compilador de Watcom.

     Una vez ms, les recomiendo que prueben el TMT Pascal, el cual es
     hasta un 30% mas rpido que el Turbo Pascal en algunas cosas, adems
     de que no tiene tantas limitaciones con respecto a la memoria.
     Existe una versin de la unidad MODE13.PAS para TMT Pascal, con la
     cual la mayora de los programas de los tutoriales funcionan sin
     grandes cambios.

     Vamos a hacer algo: Estoy pensando en empezar a escribir los tutoriales
     para el TMT Pascal. Desde ahora, pueden enviarme un e-mail diciendo
     que cambiemos o que no cambiemos al TMT Pascal. Obviamente, la
     mayora decide. Y si resulta que quieren cambiar a TMT, entonces
     habr un tutorial dedicado a cmo usar este compilador y una
     explicacin del funcionamiento de la unidad MODE13.PAS en TMT Pascal.


FINAL:
------

     Una vez ms, el tutorial ha sobrepasado las 1000 lneas, as que
     creo que lo nico que voy a decir es: chenle ganas.

     Probablemente este tutorial estuvo un poco complejo, incluyendo
     el cdigo de ejemplo, pero es muy importante que se entienda todo
     esto si quieren ver mas cosas sobre 3D. Creo que haremos una pausa
     por ahora en la tercera dimensin y regresaremos un poco a la
     segunda para ver algunos efectos como antialiasing, motion blur
     y transparencia. Son tres cosas muy sencillas, pero que mejoran
     en gran medida *otros* efectos.


La (nica) rima de (la semana?) este tutorial:
----------------------------------------------

    "So you bitchy? Well, be dat way.
     Oh, you an asshole? Well, be dat way.
     As a matter of fact
     if that's how ya gonna act
     then stay the fuck away
     if you gon be dat way"

                                        Drex Aqwar - "Be Dat Way"


Mas Anuncios: Ummm..... esto se est convirtiendo en una revista electrnica.

  -------------------------------------------------------------------------
  |                                                           |
  |                                                          |
  |                         |
  |                  |
  |                          |
  |                      |
  |                  |
  |                   |
  |                    |
  |                                                                       |
  |        presenta (no muy) orgullosamente sus nuevas producciones       |
  |                                                                       |
  |   STUPIFEKKYPLOOPY        BARBIE MEETS EAZY-E         OFF THE ROOF    |
  |    (f_loopy.zip)            (dlb-brez.zip)           (dlb_otr.zip)    |
  |                                                                       |
  |                                                                       |
  |   Disponibles en formato ZIP en los siguientes lugares:               |
  |                                                                       |
  |          - http://www.hornet.org/demos                                |
  |          - http://galia.fc.uaslp.mx/~ganzo/prog/demos.html            |
  |                                                                       |
  |   fac:   fac@slp1.telmex.net.mx   |  mr. e:   mre@galia.fc.uaslp.mx   |
  |          shadowfac@hotmail.com    |                                   |
  -------------------------------------------------------------------------
          ----------------------------------------------------------
          |                                                        |
          |    Tienes algo que decir o anunciar al resto de la     |
          |               comunidad programadora?                  |
          |                                                        |
          |             Para eso est este espacio!                |
          |                                                        |
          |     Enva tus anuncios a  fac@slp1.telmex.net.mx       |
          |   con el ttulo "Tutorial - Anuncio" y ser puesto     |
          |                 en el prximo tutorial                 |
          |                                                        |
          |    * Limitado a dos o tres anuncios por tutorial *     |
          |                                                        |
          ----------------------------------------------------------
  -------------------------------------------------------------------------
  |                                                                       |
  |       Quieres aprender los ltimos trucos sobre programacin?         |
  |                                                                       |
  |  - Grficos - Windows - Linux - C++ - VESA 2.0 - Direct X - Sonido -  |
  |                                                                       |
  |           El nico lugar es en los canales #coders en IRC             |
  |                                                                       |
  |                     UnderNET:  us.undernet.org                        |
  |                       ircNET:  irc.stealth.net                        |
  |                                                                       |
  |         #coders en UnderNET son mas orientados a los juegos           |
  |         #coders en  ircNET  son mas orientados a la "scene"           |
  |                                                                       |
  |          Tambin en UnderNET: el nuevo canal #programacin            |
  |                                                                       |
  |       Consigue tu programa de IRC y date una vuelta en #coders        |
  |                                                                       |
  -------------------------------------------------------------------------
