
--------------------------------------------
      Tutorial de programacin grfica
                  Por: FAC
     -----------------------------------
           #6 - Animacin simple
--------------------------------------------


Bueno, pues aqu estoy otra vez calentando el teclado (y el asiento)
para poner en sus manos (y sus monitores) el sexto ejemplar de la
(no muy) conocida serie FAC.

Quiero repetir (algo que no he dicho desde el #1) que estos tutoriales
estn basados (pero NO copiados) en los de Granth "Denthor" Smith, pero
en espaol, con mejores explicaciones y un poco de experiencia personal.

Lo que se ve aqu no es nada nuevo, pero me he dado cuenta de que en
cierta ciudad (SLP) de cierto pas, es muy difcil encontrar informacin
sobre programacin de grficos en VGA y muchas otras cosas, por lo que
es necesario recurrir al Internet, que es el lugar de donde yo he sacado
la mayor parte de la informacin presentada aqu.

Supongo que la mayora de ustedes (como yo), no tienen ganas ($$$) de
comprar un libro nuevo cada vez que quieren aprender algo. Y aunque
compraran el libro, solo leeran la parte que les interesa.

Y todo esto, a qu viene al caso?

Bueno, pues resulta que he visto la gran falta de informacim, y he
decidido poner una pgina en Internet en la que podrn encontrar
muchos documentos sobre programacin, por ejemplo:

       - Grficos en 3D               - Efectos especiales
       - Sombreado de polgonos       - Optimizacin de programas
       - Programacin de sonido       - Interrupciones del DOS y BIOS
       - Tutoriales de Ensamblador    - Manejo de memoria (XMS, EMS, Flat)
       - Tutoriales de VGA            - Cdigo fuente


Espero que aprovechen esa informacin y que le den crdito a los que la
hayan escrito. El dar crdito, significa que en alguna parte de su
programa aparece una pantalla o ventana con los nombres de las personas
que contribuyeron a realizar el programa, incluyendolos a ustedes.

Adems, si alguna vez aprenden o descubren algn efecto interesante o
alguna forma de acelerar el sombreado Phong o cualquier cosa, no sera
mala idea que escribieran un pequeo documento y lo pusieran en internet,
o pueden mandrmelo a m para yo publicarlo en internet.

Las direcciones en donde pueden obtener y publicar informacin son:

                ftp://ftp.cdrom.com/pub/demos/code

        ftp://teeri.oulu.fi/pub/msdos/programming/00Main.html


Y, pues como siempre, si tienen alguna duda, sugerencia, idea, o algo
que los dems quieran saber, escrbanme a:

              Alfonso Alba Cadena
    e-mail:   ganzo@galia.fc.uaslp.mx
    telfono: 13-34-71


Manden tambin sus programas. Incluso los de "tarea". Si no pueden hacer
que un programa funcione, mndenlo tambin con una explicacin de qu
es lo que debera hacer y qu es lo que est haciendo.

HEY, no vayan a manderme sus tareas de las clases de programacin! :)


Hmmmm... creo que me exced del lmite de rollo permitido en un tutorial.
Lo que pasa es que la animacin es algo tan fcil que no tardar mucho
en explicarla. De hecho, ustedes ya deben tener las bases necesarias
para hacer animaciones, no creen?

Bueno, pero antes, una *** NOTA IMPORTANTE ***

La unidad MODE_13.PAS ha sufrido un accidente. Se le han hecho algunos
implantes en sus partes esenciales y hemos tenido que cambiarle el nombre.

Es hora de ser valientes y temerarios y principalmente rpidos. La nueva
y mejorada unidad MODE13.PAS (ahora sin el subrayado) contiene los mismos
(y algunos ms) procedimientos que la unidad anterior, pero algunos han
sido reescritos en ensamblador.

Si no quieren saber nada de asm (ensamblador), pero necesitan mayor
velocidad, entonces solamente usen los procedimientos nuevos, que funcionan
igual que los anteriores.

SI quieren aprender a programar grficos en ensamblador, primero comsigan
un BUEN libro de ensamblador (hay muchos malos) y luego revisen las
rutinas en MODE13.PAS. Algunas de ellas estn comentadas y la mayora
se pueden acelerar an ms.

Y a los que de plano les da miedo el asm, pueden seguir usando la
unidad MODE_13.PAS, que est 95% en Pascal. PERO a partir de este
tutorial, en los ejemplos se usarn las nuevas rutinas.

Hay que tener en cuenta que algunas de las rutinas usan cdigo de
32 bits, por lo tanto NECESITAN un 386, adems de algunas tranzas
de programacin, ya que Turbo Pascal 7.0 no permite instrucciones
ms all del 286.

Bueno, anmense, porque lo que sigue es muy fcil...


ANIMACION:
----------

     Hasta donde yo s, existen dos tipos de animacin: Animacin por
cuadros y animacin morfolgica.

     La primera de ellas, (animacin por cuadros), consiste simplemente
en dibujar un objeto en diferentes posiciones en cada cuadro, pero no
muy diferentes. Cuando se tienen las imgenes inicial y final y varias
intermedias, simplemente se dibujan en la pantalla en una secuencia
predeterminada para dar la ilusin de movimiento. Es algo parecido a
dibujar algo en las esquinas de las hojas de un libro y pasarlas rpidamente.

     La animacin morfolgica es un poco mas complicada. En este tipo de
animacin no disponemos de cuadros predibujados con las secuencias de
movimiento, sino que el objeto es generado mediante complejas frmulas
matemticas como rotaciones y traslaciones vectoriales. La ventaja aqu
es que para realizar nuevos movimientos no es necesario dibujar cada
cuadro, sin embargo, se necesita una gran capacidad de procesamiento
para generar los cuadros en tiempo real.

     Para diferenciar entre un tipo de animacin y otro, les tengo un
ejemplo. Qu tienen en comn las siguientes palabras?

         - Street Fighter
         - Mortal Kombat
         - Killer Instinct
         - Tekken
         - Virtua Fighter

     As es, todos son juegos de peleas. Pero desde un punto de vista de
programador grfico, esos juegos se pueden clasificar en dos grupos:

     Street Fighter, Mortal Kombat y Killer Instinct usan animacin por
cuadros. Es decir, que cada movimiento que hace un personaje ha sido
previamente dibujado en una secuencia de cuadros.

     Tekken y Virtua Fighter funcionan de otra manera. En ellos, cada
personaje est formado por una serie de vectores, los cuales forman
polgonos, (por eso es que se ven como se ven). A cada polgono se le
asigna un color o una textura y se le aplica un sombreado, pero eso
se hace MIENTRAS ustedes estn jugando. Un peleador de stos puede estar
formado por miles de polgonos, y en cada movimiento, algunos de esos
polgonos son rotados y trasladados en un espacio tridimensional,
obtenindose as el movimiento correspondiente.

     En este tutorial solamente vamos a ver la animacin por cuadros.
El otro tipo de animacin requiere algunas bases de 3D y MUCHAS
matemticas para poder generar animaciones realistas.


ANIMACION POR CUADROS:
----------------------

Antes, quiero hacer la distincin entre un "sprite" y un "cuadro".

Un "cuadro" simplemente es uno de tantos "dibujos" que pueden componer
una secuencia de animacin. Si ustedes quisieran hacer una caricatura
de la Pantera Rosa, primero tendran que dibujar muchos "cuadros", cada
uno ligeramente diferente que el anterior y despus pasarlos rpidamente
uno despus de otro, pero en orden. A ese orden se le llama "secuencia
de animacin".

Los "cuadros" siempre tienen forma rectangular, sin embargo, los dibujos
que se muestran, pocas veces son rectangulares, lo que significa que
algunas reas del cuadro no deben aparentar ser transparentes. Es lo mismo
que ocurra con el letrero del tutorial anterior.

Un "sprite" es (desde un punto de vista de programador) una estructura
de datos que contiene la informacin de una figura animada. Esa informacin
puede inclur:
                - Posicin del sprite
                - Tamao del sprite
                - Velocidad de movimiento del sprite
                - Velocidad de animacin del sprite
                - Apuntador o ndice al cuadro que se va a desplegar
                - Secuencia de animacin (una lista o arreglo de cuadros)
                - Indicadores de "choque" con otros sprites

Obviamente, NO TODOS los sprites necesitan toda esa informacin. Por
ejemplo, los indicadores de "choque" con otros sprites son muy tiles
cuando se programan juegos, pero no son muy utilizados en demos.

Lo ms importante aqu es que un sprite es completamente independiente
de los cuadros de animacin. Un sprite NO ALMACENA los datos de los cuadros,
pero s almacena informacin sobre su propia secuencia de animacin. Esa
informacin puede ser un arreglo de apuntadores o ndices a los cuadros.

Por ejemplo, digamos que tenemos una figura animada de 20 * 20 pxels:


    type PTCuadro = ^Cuadro;              { apuntador a un cuadro }
         TCuadro = array[0..19, 0..19]    { datos de un cuadro }

         TSprite = record                 { datos de un sprite }
                   x, y : integer;        { posicin del sprite }
                   dimx, dimy : integer;  { dimensiones del sprite }
                   cuadro : byte;         { cuadro a desplegar }
                   Secuencia : array[1..20] of PTCuadro;   { Secuencia }
                   end;

    procedure DibujaSprite(spr : TSprite);
    var i, j : integer;
    begin
         for j := 0 to spr.dimy do
             for i := 0 to spr.dimx do
                 PutPixel(i + spr.x, j + spr.y, Secuencia[cuadro]^[i, j]);
    end;


Como pueden ver, el "sprite" y la rutina que lo dibuja no necesitan ser
modificados para dibujar otros "sprites" similares. Podramos cambiar
la secuencia de animacin, los cuadros y hasta las dimensiones sin
tener que modificar el procedimiento.

Esto es an mas flexible (y un poco mas complejo) si se utilizan
apuntadores "puros", es decir, que no apuntan a ningn tipo en especial
(tipo pointer). De esta forma, un arreglo como

                var Secuencia : array[1..20] of pointer;

nos permite hacer secuencias con diferentes "tipos" de cuadros, no solo
de 20 * 20. :)


Bueno, ahora vamos a ver los pasos que hay que seguir para realizar una
animacin.

NOTESE que el ttulo de este documento dice "Animacin SIMPLE", lo que
significa que solamente voy a explicar el procedimiento bsico para
hacer una animacin. Ustedes pueden hacer sus animaciones tan complejas
como ustedes quieran, lo cual es bueno si estn programando un juego.
Podran inclur rotaciones, sombreados, deformaciones, etc, etc, etc...,
pero eso depende de ustedes. Sera casi imposible describir todas las
cosas que se pueden hacer con los sprites en un documento como ste.

Otra cosa para los que piensan programar juegos: Trabajen en una unidad
(o librera) de sprites. Hagan los tipos de datos necesarios y luego
trabajen en los procedimientos de dibujo, movimiento y animacin de sus
sprites. Si saben programar orientado a objetos, hganlo, y si no saben,
aprendan. Cuando quieran hacer un juego, simplemente usen o incluyan su
librera y voila. Tal vez tengan que modificar alguno que otro procedimiento
para un juego en especial, pero siempre conserven la librera original,
y cada vez que se les ocurra una optimizacin o un nuevo efecto, inclyanlo
en su librera. Lo mismo se aplica a las libreras para trabajar en el
modo 13, en el modo X, libreras de 3D, etc...


METODOS SENCILLOS DE ANIMACION:
-------------------------------

(Esta seccin fu prcticamente traducida de los tutoriales de Denthor)


Digamos que tenemos un "sprite" (una figura animada) que comprende
varios cuadros y queremos animarla y desplazarla sobre un fondo esttico.

Aqu les presento tres formas de hacerlo. No son las nicas ni las mejores,
pero s las ms comunes y sencillas:


     Mtodo 1: "Tragamemoria pero sencillo"

          1) Crear e inicializar dos pantallas virtuales (PV1 y PV2)
          2) Dibujar el fondo en PV2
          3) Copiar PV2 a PV1
          4) Dibujar sprites en PV1
          5) Copiar PV1 a VGA (despus de un VRetrace, obviamente)
          6) Si es necesario, modificar la posicin de los sprites
          7) Repetir desde el paso 3

          Ventajas:  - Muy (el ms) fcil de implementar
                     - Es el mejor mtodo cuando se trata de muchos sprites
                     - Reduce al mnimo (o a nada) el parpadeo

          Desventajas:  - Necesita MUCHA memoria (2 pantallas virtuales
                          mas los datos de los sprites = mas de 130 K)
                        - No es lo ms rpido cuando son pocos sprites


     Mtodo 2: "No necesito memoria, pero..."

          1) Dibujar el fondo en VGA (o copiarlo de alguna pantalla virtual)
          2) Almacenar la parte del fondo donde se va a dibujar el sprite
          3) Dibujar el sprite
          4) Modificar posicin del sprite
          5) Reponer la parte del fondo que se obtuvo en el paso 2
          6) Repetir desde el paso 2

          Ventajas:  - No necesita ninguna pantalla virtual (ahorra memoria)
                     - Funciona para pocos y pequeos sprites

          Desventajas:  - Dibujar en VGA es mas lento que dibujar en una
                          pantalla virtual.
                        - Puede causar mucho parpadeo con sprites grandes.


     Mtodo 3: "Un poco de memoria y no me veo tan mal"

          1) Crear e inicializar una pantalla virtual (PV)
          2) Dibujar el fondo en PV
          3) Copiar PV a VGA
          4) Dibujar sprite en VGA
          5) Modificar posicin del sprite
          6) Copiar la porcin de fondo de PV a VGA
          7) Repetir desde el paso 4

          Ventajas:  - No necesita tanta memoria (solo una p. virtual)
                     - Es rpido cuando se trata de pocos sprites
                     - Hay menos parpadeo que en el mtodo 2

          Desventajas:  - Es un poco mas complejo que los otros mtodos
                        - Con muchos sprites puede haber parpadeos


Esos son tres de los mtodos ms comunes, pero puede haber ms. Lo que
ustedes deben hacer es adaptar el mtodo que se ajuste ms a su programa,
haciendo un balance entre memoria, velocidad y calidad. Incluso pueden
combinar dos o mas de estos mtodos, por ejemplo, en el mtodo 3, en
lugar de dibujar el fondo y los sprites a VGA, dibjenlos a OTRA pantalla
virtual y copien todo despues a VGA como en el mtodo 1. Eso reduce
mucho el parpadeo e incrementa la velocidad, pero consume mas memoria.

En el programa de ejemplo (TOSTADOR.PAS) se utiliza el mtodo 1. Se tienen
dos pantallas virtuales: FondoSeg y VirSeg. La imagen de fondo se encuentra
siempre en FondoSeg. De ah es copiada a VirSeg, en donde tambin se
dibujan los sprites. Finalmente se copia todo a VGA.


DISEO DE CUADROS:
------------------

Creo que esta seccin la debera escribir algn grafista....

hmmmm... no hay ninguno cerca, as que la voy a escribir yo.

Lo primero que hay que hacer es dibujar el primer cuadro de la animacin
sobre un fondo blanco o negro y guardarlo en el disco. Luego, partiendo
de ese cuadro, hacer modificaciones hasta conseguir el siguiente cuadro
y guardarlo tambin en el disco. Hacer lo mismo hasta completar la secuencia.

Lo importante aqu es que todos los cuadros tengan el mismo color de fondo,
ya que se color es el que debemos hacer "transparente", es decir que
NO vamos a dibujar los pxels que tengan ese color.

Si les es posible, intenten que el color de fondo sea el de ndice 0
en la paleta, ya que es mas fcil y rpido comprobar si un nmero es
igual a cero que con cualquier otro valor. Por lo menos en ASM.

Deben usar los menos colores como les sea posible en los cuadros.
Recuerden que solamente disponemos de 256 colores y hay que distriburlos
entre la imagen de fondo, los cuadros de animacin y otras cosas que
aparezcan en la pantalla (como los marcadores de puntuacin en un juego).

Normalmente, 128 colores para el fondo y 128 colores para los sprites es
suficiente, pero eso significa que debemos organizar la paleta de colores
de una forma ordenada, en caso de que alguno de los dos (sprites o fondo)
cambie, por lo tanto, sera una buena idea organizar la paleta de una
forma como la siguiente:

              { Ejemplo de paleta de colores para un juego }

              colores   0 - 127 : Colores de la imagen de fondo
              colores 128 - 239 : Colores para las animaciones
              colores 240 - 255 : Colores para los letreros de puntuacin

De esta forma, si tenemos que cambiar la imagen de fondo, solo tendramos
que cambiar los colores del 0 al 127 por los nuevos colores de fondo. De
la misma forma podemos cambiar los sprites sin tener que afectar al fondo
o a los marcadores. Hay que cuidar la paleta de colores. Es muy malo cuando
el demonio negro contra el que estamos luchando en nuestro juego se vuelve
rosa solamente porque cambiamos un color que no debamos.


Desgraciadamente, son pocos los programas de dibujo que permiten editar
la paleta a nuestra voluntad. Personalmente, yo prefiero Paint Shop Pro,
ya que adems de editar la paleta, permita guardarla a un archivo en
formato de texto, con lo que se puede alterar desde cualquier editor
de texto. Paint Shop Pro es shareware y debe de ser muy fcil conseguirlo
en Internet.

Otra ventaja de Paint Shop Pro es que permite reducir el nmero de colores
que utiliza una imagen, por lo tanto, si tenemos un fondo que usa los 256
colores, podemos reducirlo a 128 colores sin empeorar mucho la calidad.


COMO DIBUJAR LOS CUADROS:
-------------------------

Una vez que ya dise cada cuadro y los almacenaron en el viejo y
conocido formato PCX a 256 colores, hay que cargar cada cuadro a la
memoria para despus dibujarlos.

Como ejemplo, supongamos que tenemos una animacin de 5 cuadros de
20 * 20 pxels y queremos almacenar los cuadros en la memoria.


        type PTCuadro = ^TCuadro;
             TCuadro = array[0..19, 0..19] of byte;

        var Animacion : array[1..5] of PTCuadro;
            AnimPal : TPalette;


Como pueden ver, el arreglo Animacion contiene los APUNTADORES a los
cuadros. Recuerden que debemos utilizar apuntadores para referenciar
grandes cantidades de datos.

El siguiente procedimiento podra cargar los cuadros a la memoria:


        procedure CargaCuadros;

        var TempScr : PTVirtual;
            TempSeg : word;
            i, x, y : byte;

        begin
             SetupVirtual(TempScr, TempSeg);

             for i := 1 to 5 do
             begin
                  Animacion[i] := new(PTCuadro);
                  LoadPCX( 'cuadro' + chr(i+48) + '.pcx', TempSeg,
                           20, 20, 0, 0, AnimPal);

                  for y := 0 to 19 do
                      for x := 0 to 19 do
                          Animacion[i]^[x, y] := GetPixel(x, y, TempSeg);
             end;

             ShutDownVirtual(TempScr);
        end;


El procedimiento anterior carga y almacena los cuadros a partir de los
archivos cuadro1.pcx, cuadro2.pcx, ..., hasta cuadro5.pcx.

No olviden que antes de que termine el programa, hay que liberar la
memoria ocupada por los cuadros:

        for i := 1 to 5 do dispose(Animacion[i]);


Tampoco olviden reservar memoria para el cuadro ANTES de tratar de usarlo,
ya sea para lectura o escritura. A m siempre me pasa que la mquina se
queda colgada o simplemente se resetea porque se me olvid poner algo como:

              Animacion[i] := new(PTCuadro);


Como consejo general, cuando hagan un programa que utilice apuntadores,
escriban en una hoja TODAS las variables a las cuales se les debe asignar
memoria y asegrense de que a todas se les asigna memoria antes de acceder
a ellas y se liberan de la memoria antes de que termine el programa.


Bueno, ya tenemos los cuadros en memoria. El siguiente paso es dibujarlos
a la pantalla (ya sea virtual o VGA).

Hay que recordar que uno de los colores utilizados en los cuadros debe
de ser transparente. El nmero de color transparente depende de cada
quin, as que por simplicidad, voy a suponer que es el color 0.


       { Suposiciones:    c --> Nmero (ndice) del cuadro a dibujar   }
       {                  where --> Segmento de la pantalla utilizada  }
       {                  sprX, sprY --> Posicin del "sprite"         }
       {                  0 --> Color que deseamos hacer transparente  }

       for x := 0 to 19 do
           for y := 0 to 19 do
               if Animacion[c]^[x, y] <> 0 then
                  PutPixel(x + sprX, y + sprY, Animacion[c]^[x, y], where);


Eso sera todo lo que hay que hacer para dibujar un cuadro de la
animacin. Es muy fcil, verdad? Obviamente, ustedes pondran el
cdigo anterior en su procedimiento para dibujar sprites.

Sin embargo, el doble-ciclo anterior est muy lejos de ser rpido. Y eso
se lo debemos al buen amigo PutPixel.

Aunque no lo crean, el simple hecho de llamar al procedimiento PutPixel
es casi mas tardado que dibujar el pxel. La solucin es NO llamar a
PutPixel, sino insertar directamente el cdigo dentro del ciclo interior.

Hablando en Pascal, esto sera algo as:

         for x := 0 to 19 do
             for y := 0 to 19 do
                 if Animacion[c]^[x, y] <> 0 then
                    mem[where:(y+sprY)*320+x+sprX] := Animacion[c]^[x, y];

Espero que recuerden la vieja frmula de  y * 320 + x, la cual nos
da el OFFSET de la direccin a la cual queremos acceder.

Pero vean algo. Para calcular el OFFSET de cada punto, necesitamos
tres sumas y una multiplicacin POR CADA PIXEL!!! Eso no es bueno.

Ahora, si analizamos un poco las cosas, notaremos que cuando dibujamos
sobre una lnea horizontal, el famoso OFFSET se incrementa en 1, mientras
que si el desplazamiento es vertical, el OFFSET se incrementa en 320.

Bien, si reorganizamos los ciclos y aplicamos lo anterior, podemos
dibujar el cuadro utilizando solamente UNA suma por pxel mas OTRA suma
por cada 20 pxels (o cualquiera que sea la dimensin horizontal).

El cdigo quedara as:


          { off es una variable de tipo word }

          off := sprY * 320 + sprX;
          for y := 0 to 19 do
          begin
               for x := 0 to 19 do
               begin
                    if Animacion[c]^[x, y] <> 0 then
                       mem[where:off] := Animacion[c]^[x, y];
                    inc(off);
               end;
               inc(off, 300);
          end;

La razn por la que le sumamos 300 a off despus de cada ciclo en X,
es porque despus de ese ciclo, off ya aument en 20 unidades, por lo
que para pasar a la siguiente lnea solo hay que sumarle otros 300.
(300 + 20 = 320).

En el programa de ejemplo, se utiliza el cdigo anterior para dibujar
los cuadros (procedimiento DibujaTostadores). Prueben cambiando la lnea:

       mem[where:off] := Tostador[Cuadro]^[y, x];

por la lnea que utiliza el procedimiento PutPixel. Esa lnea est en
el programa de ejemplo puesta como un comentario.

Probablemente con diez sprites en la pantalla no se note mucho la
diferencia en velocidad, pero prueben a aumentar el nmero de sprites
a 50 y vern de qu estoy hablando. Para aumentar el nmero de sprites,
simplemente modifiquen la constante NumSprites al principio del programa.

La diferencia es mucho mayor cuando se utiliza ensamblador. Si son un
poco curiosos e intentan usar la vieja unidad Mode_13 en lugar de
la Nueva y Perfeccionada Mode13, notarn que las nuevas rutinas son
MUCHO mas rpidas que la anteriores. Y eso que nicamente estamos usando
la funcin CopyScreen en el ciclo principal.

La nueva funcin CopyScreen est completamente escrita en ensamblador
de 32 bits, lo que significa que puede mover hasta 4 bytes al mismo
tiempo. El procedimiento viejo mova 2 bytes a la vez (16 bits).

No sera excelente si nuestra rutina de dibujo de "sprites" estuviera
en ensamblador???  Bueno, pues solo para mostrarles la velocidad del ASM,
el programa de ejemplo TOST-ASM.PAS es el mismo que TOSTADOR.PAS con la
pequea diferencia de que el procedimiento de dibujo est en ensamblador.

Para los que les interese, aqu hay una PEQUEA explicacin de los cambios
efectuados al programa.


RUTINAS DE DIBUJO EN ENSAMBLADOR:
---------------------------------

Antes de empezar, voy a dar una MUY PEQUEA, INSIGNIFICANTE Y BASICA
explicacin del ensamblador.

El sistema operativo MS-DOS divide la memoria en SEGMENTOS de 64 Kb
cada uno. Para indicar una direccin de memoria especfica, necesitamos
saber el segmento de esa direccin y otro valor, llamado OFFSET, el
cual nos indica la posicin que buscamos dentro de un segmento.

Ya que los segmentos son de 64 kilobytes (65536 bytes), el offset debe
ser un nmero entre 0 y 65535, lo cual corresponde justamente a un
nmero de 16 bits. ( 2 elevado a la 16 = 65536 ). Los registros del
procesador tambin son de 16 bits, por lo tanto, para especificar
cualquier direccin en la memoria de la computadora, necesitamos
DOS registros, uno para el SEGMENTO y otro para el OFFSET.

La direccin final de memoria se especifica con la siguiente sintaxis:

                   REGISTRO:OFFSET

Por ejemplo:    $A000:0000
                $A000:Y * 320 + X
                VirSeg:YOff

Como pueden ver, el par REGISTRO:OFFSET es un nmero de 32 bits.


Como ya henos visto, el segmento de la memoria de video es $A000
y tambin conocemos la forma de calcular el offset para direccionar
un pxel en especial (OFFSET = Y * 320 + X).

Pero existen otros segmentos que nos deben interesar:

     - El segmento de cdigo: Este segmento contiene el cdigo compilado
                              de los programas que hacemos.

     - El segmento de datos: En este segmento se guardan todas las variables
                             de nuestros programas. Es por eso que debemos
                             tener tan pocas variables globales como podamos.

     - El segmento de pila: La pila es un lugar de almacenamiento temporal
                            que tiene la caracterstica de que el orden
                            en que se sacan los datos de la pila es inverso
                            al orden en que se meten a ella, es decir:
                            el ltimo que entra es el primero que sale.



Otros segmentos que hemos usado son los de las pantallas virtuales. Cada
pantalla virtual ocupa casi un segmento entero (64 Kb) y el MS-DOS pone
a nuestra disposicin 640 Kb de memoria convencional de la cual se ocupan
entre 20 y 200 Kb para el sistema operativo y controladores. Es por eso
que en un programa no debemos usar ms de una o dos pantallas virtuales.


El procesador tiene diversos REGISTROS, los cuales sirven para almacenar
informacin y hacer operaciones con ella. Desgraciadamente, los registros
son muy pocos y hay que saber aprovecharlos. El procesador tambin puede
acceder a la memoria y hacer algunas operaciones con ella, pero el acceso
a la memoria es MUCHO mas lento que la utilizacin de los registros.

En un procesador 80x86 existen los siguientes registros, siendo TODOS
ellos de 16 bits de longitud:

   Registros bsicos:

     - AX : Se usa para hacer casi todas las operaciones y es el nico
            registro en el que se puede multiplicar y dividir. Tambin
            se usa para las operaciones de entrada y salida en puertos.
            Se le conoce tambin como ACUMULADOR.

     - BX : Este es un registro de uso general, pero es el nico que
            tambin se puede usar como registro ndice, es decir que
            se puede usar para especificar el OFFSET de una direccin.
            Se le conoce tambin como REGISTRO BASE.

     - CX : Su propsito principal es servir de contador en los ciclos
            y en las instrucciones de desplazamiento y rotacin de bits.
            Por razones obvias, se le conoce como CONTADOR.

     - DX : Este registro tiene varios usos. Sirve como una extensin
            del registro AX en operaciones de multiplicacin y divisin.
            En una multiplicacin larga, el resultado se encuantra en
            DX:AX, y en una divisin, el resultado se encuentra en AX
            y el residuo (o mdulo) se encuentra en DX. Tambin se usa
            para indicar la direccin de puerto en las instrucciones
            de entrada y salida cuando sta es mayor que $FF.


   Registros de segmento:

     - CS : Este registro contiene el segmento de cdigo que se est
            ejecutando en ese momento. No puede ser alterado.

     - SS : Este registro contiene el segmento de la pila (stack).
            NO DEBE de ser alterado a menos de que crean saber lo
            que estn haciendo.

     - DS : Este registro contiene el segmento de datos del programa.
            Puede ser alterado pero DEBE de recuperar su valor original
            antes de intentar acceder a cualquier variable del programa.

     - ES : Este es el segmento "extra". Est a libre disposicin del
            programador.


   Registros Indice: Los registros ndice se usan normalmente para indicar
                     el OFFSET de una direccin, aunque algunos pueden
                     ser usados tambin como registros de uso general.

     - IP : Este registro apunta a la siguiente instruccin a ejecutar
            dentro del segmento de datos. No puede ser alterado.

     - SP : Este registro apunta al "tope" de la pila, es decir, la
            direccin dentro del segmento SS en la cual se insertar el
            siguiente dato en la pila. No es buena idea alterarlo.

     - BP : Este registro (apuntador base) es usado por el Turbo Pascal
            para referencial las variables que se pasan a un procedimiento
            o funcin como parmetros, los cuales se empujan a la pila.
            Puede ser modificado, pero en ese caso no se tendr acceso
            a los parmetros del procedimiento y adems, el valor original
            debe ser recuperado.

     - SI : Este es un registro ndice de uso general, aunque normalmente
            va ligado con el registro DS, referenciando la direccin DS:SI .

     - DI : Este registro es igual que SI, pero en lugar de usarse con
            el registro DS, se usa con ES para referenciar la direccin ES:DI .


Los registros ndice contienen OFFSETS, pero para poder referirse a la
direccin APUNTADA por esos OFFSETS, solamente hay que poner el offset
entre corchetes []. Por ejemplo:

                mov  di, 5      { Hace DI igual a 5 }
                mov [di], 5     { Pone un 5 en la direccin apuntada por DI }

Hay que reconocer muy bien la diferencia entre DI y [DI], ya que uno
se refiere al valor del registro y el otro al valor al que apunta ese
registro.

En ensamblador, cuando no se especifica un segmento, se toma por default
el segmento de datos (DS). Por ejemplo:

               mov ds:[si], 10   { Mueve el valor 10 a ds:[si] }
               mov [si],10       { Mueve el valor 10 a ds:[si] }
               mov es:[si], 10   { Mueve el valor 10 a es:[si] }


Esos son los registros de un 80x86, pero a partir del 80386 se incorporaron
algunos registros nuevos:

     - EAX, EBX, ECX y EDX : Son el equivalente a AX, BX, CX y DX pero
                             son de 32 bits.

     - FS y GS : Son registros de segmento, tal como ES.

Desgraciadamente, el Turbo Pascal no permite usar instrucciones del 386,
aunque existe la forma de inclurlas sin que el compilador se de cuenta,
pero eso est fuera de una explicacin bsica de ensamblador.


     Bytes, Words y DoubleWords:


         El registro AX es un registro de 16 bits, es decir que contiene
         DOS bytes (1 byte = 8 bits). Dos bytes tambin son conocidos
         como una PALABRA (Word), por lo tanto, el registro AX tiene
         una longitud de una palabra. Obviamente, una doble-palabra
         tendra una longitud de 4 bytes. Los registros de 32 bits del
         80386 son dobles-palabras (Doublewords  o DWords).

         El registro AX  "mide" una palabra, pero algunas veces solamente
         necesitamos acceder a un solo byte, como cuando queremos dibujar
         un pxel en el modo 13. El color del pxel se representa por un
         byte. Por eso es que existe la forma de usar el registro AX
         como si fueran dos registros de un byte cada uno.

         El registro AX se divide en otros dos registros, AH y AL, siendo
         AH el byte superior o MAS significativo, y AL el byte inferior
         o MENOS significativo. Pero AH y AL  en realidad SON el registro AX,
         por lo tanto, si modificamos alguno de ellos, en realidad estamos
         modificando AX. Por ejemplo:

                     mov ax, $0000     { AX contiene $0000 }
                     mov ah, $80       { AX contiene $8000 }
                     mov al, $FF       { AX contiene $80FF }
                     inc ah            { AX contiene $81FF }

         Los registros BX, CX y DX funcionan de la misma manera.

         En el caso del 80386 en adelante, los registros AX, BX, CX y DX
         son las PALABRAS MENOS SIGNIFICATIVAS de sus registros de 32 bits
         correspondientes (EAX, EBX, ECX y EDX).

*** NOTA .- En Turbo Pascal, la representacin de un nmero hexadecimal
            se hace anteponiendo un signo de pesos ($) al nmero ($A000).
            En ensamblador, esa representacin se hace poniendo una H
            al final del nmero y un cero en el caso de que el nmero
            comience con una letra, por ejemplo: 8000H, 0FFFFh, etc...

            El ensamblador en lnea del Turbo Pascal permite usar
            cualquiera de las dos sintaxis, siendo la mas fcil la
            de el signo de pesos. Pero al programar un ensamblador
            externo, hay que recordar usar la sintaxis de la "H".


Bueno, ahora vamos a ver algunas instrucciones bsicas de ensamblador:


     MOV destino, origen : Copia el valor de origen a destino. Es el
                           equivalente a  destino := origen;
                           El destino puede ser un registro o un lugar
                           en la memoria (una variable de Pascal).
                           El origen puede ser un registro, un lugar
                           en la memoria o un valor inmediato (constante).
                           Por ejemplo:

                               mov ax, $A000
                               mov es, ax
                               mov es:[di], al
                            *  mov ch, Var1
                            ** mov Var2, si

                           * Var1 es una variable de tipo BYTE
                           ** Var2 es una variable de tipo WORD

                           Las restricciones de esta instruccin son que
                           no se puede mover:

                              - De memoria a memoria
                              - Valor inmediato a memoria
                              - Valor inmediato a registro de segmento.

                           Entonces, los siguientes ejemplos no son vlidos:

                                mov Var1, Var2
                                mov VirSeg, $A000
                                mov es, $A000


     ADD destino, origen : Aade el valor de origen al de destino y coloca
                           el resultado en destino. Por ejemplo:

                                 mov ax, 100        { AX := 100 }
                                 mov cx, 20         { CX := 20 }
                                 add cx, 30         { CX := CX + 30 }
                                 add ax, cx         { AX := AX + CX }

                           Al final, AX tendr el valor de 150


     SUB destino, origen : Resta el valor de origen al de destino y coloca
                           el resultado en destino.

     INC destino : Incrementa destino en 1. Es ms rpido que ADD dest, 1

     DEC destino : Decrementa destino en 1. Es ms rpido que SUB dest, 1


     AND destino, origen
     OR  destino, origen
     NOT destino
     XOR destino, origen : Estas cuatro instrucciones realizan operaciones
                           lgicas a nivel de bits. No voy a explicar las
                           operaciones lgicas. Solamente voy a dar algunos
                           ejemplos:


                                   mov al,  00110011b   { <- 'b' por binario }
                                   and al,  00001111b

                           resultado en al: 00000011b


                                   mov al,  00110011b
                                   or  al,  00001111b

                           resultado en al: 00111111b


                                   mov al,  00110011b
                                   not al

                           resultado en al: 11001100b


                                   mov al,  00110011b
                                   xor al,  00001111b

                           resultado en al: 00111100b



     TEST destino, origen : Realiza exactamente la misma operacin que
                            AND destino, origen  pero con la diferencia
                            de que el registro destino no se altera.
                            O sea que el resultado de la operacin no
                            se guarda en ningn lado, pero s afecta los
                            indicadores, de esa forma podemos saber si
                            la operacin di cero como resultado y comprobar
                            el valor de los bits individualmente.


     JZ etiqueta
     JE etiqueta  : Cualquiera de estas instrucciones realizar un salto
                    a la etiqueta especificada si la operacin anterior
                    di como resultado un cero.

     JNZ etiqueta
     JNE etiqueta : Estas instrucciones realizan el salto si la operacin
                    anterior di como resultado algo diferente de cero.


*** NOTA.- Una etiqueta se puede definir con la palabra reservada LABEL
           del Turbo Pascal o con cualquier palabra precedida de una
           arroba. Por ejemplo:  @ciclo1,  @inicio,  etc...


     PUSH registro : Empuja (mete) el valor de un registro a la pila.

     POP  registro : Saca un valor de la pila y lo guarda en registro.


*** NOTA.- Recuerden que el orden en que se empujan los elementos en la
           pila es inverso al orden en que se sacan de ella. Por ejemplo:

                   push ax
                   push dx
                   ...
                   ...
                   ...
                   pop dx
                   pop ax

           El cdigo anterior guarda los valores de AX y DX y los recupera
           el final del cdigo.


                   push ax
                   push dx
                   ...
                   ...
                   ...
                   pop ax
                   pop dx

           El cdigo anterios guarda los valores de AX y DX, pero cuando
           los recupera, sus valores quedan intercambiados.


Existen MUCHAS mas instrucciones que iremos viendo poco a poco en los
siguientes tutoriales, pero an as, no sera mala idea que consiguieran
un libro de ensamblador y revisaran los procedimientos de la unidad Mode13.


Dentro del Turbo Pascal nosotros podemos insertar cdigo en ASM mediante
el ensamblador en lnea. Lo nico que tenemos que hacer es poner el
cdigo dentro de un bloque  asm... end;  Por ejemplo:

       procedure PutPixel(x, y : word; color : byte);
       begin
            asm
               mov ax, $A000
               mov es, ax        { No podemos mover un valor inmediato a ES }
               mov ax, 320       { AX := 320 }
               mul y             { AX := AX * y }
               add ax, x         { AX := AX + X }
               mov di, ax        { DI := AX }
               mov al, color     { AL := color }
               mov es:[di], al   { No podemos mover de memoria a memoria }
            end;
       end;


Este procedimiento es un PutPixel muy poco optimizado, pero sirve para
mostrar la forma de utilizar el ensamblador en lnea.


Un bloque  asm... end;  DEBE conservar los registros: BP, SP, SS, DS

y puede modificar los registros: AX, BX, CX, DX, SI, DI, ES

Adems, cuando entramos al ensamblador en lnea, no podemos asumir
nada acerca del valor que contienen los registros. Por ejemplo:

            asm
               mov ax, 100
            end;
            writeln('hola');
            asm
               add ax, 100
            end;


Es MUY posible que el registro AX haya cambiado al ejecutarse la instruccin
writeln, por lo tanto, el valor de AX al entrar al segundo bloque ASM
puede ocasionar cualquier resultado inesperado.

Lo que quiero decir es esto: NO hagan lo que muestra el ejemplo anterior.
Si inician un proceso en ensamblador, hganlo todo en ensamblador. Cualquier
"regreso" al pascal puede ocasionar cambios indeseados en los registros.


Tipos de datos:

     Como ya hemos visto, algunos de los tipos de datos en ensamblador son
     BYTE, WORD y DWORD.

     Hay algunas veces en que es necesario especificar el tipo de datos
     que se requiere. Por ejemplo, una instruccin como

                      mov es:[di], al

     obviamente mueve un BYTE, ya que el registro AL es de 8 bits, pero
     si hacemos algo como:

                var Apuntador : pointer;
                begin
                     asm
                        mov ax, Apuntador
                     end;
                end;

     Lo anterior provocara un error, ya que Apuntador es una variable
     cuya longitud son cuatro bytes (pointer) y AX es un registro de
     solamente 2 bytes.

     Una variable de tipo pointer en Turbo Pascal contiene cuatro bytes,
     es decir, dos palabras. La palabra inferior contiene un OFFSET y
     la palabra superior contiene un SEGMENTO. Por lo tanto, una variable
     de tipo pointer es en realidad eso, un apuntador.

     Pero los segmentos y los offsets son valores de 16 bits, y esos s
     los podemos mover al registro AX. El problema es cmo separar una
     variable de cuatro bytes en dos valores de dos bytes cada uno.

     En realidad es muy fcil. La direccin de memoria en donde se
     encuentra el valor de nuestra variable est dada por [Apuntador],
     y a partir de esa direccin, los cuatro bytes siguientes contienen
     el valor de la variable. Por lo tanto:

              [Apuntador]  apunta a la palabra INFERIOR de la variable
              [Apuntador + 2] apunta a la palabra SUPERIOR de la variable


     Un momento... significa eso que la palabra superior est DESPUES
     de la palabra inferior??? No debera ser al revs.

     Tal vez, pero efectivamente, en la PC, las palabras superior e
     inferior de un valor de 32 bits se almacenan en orden inverso. De
     la misma forma, los bytes superior e inferior de una palabra se
     almacenan en orden inverso, siendo primero el byte inferior y luego
     el superior.

     Una vez que sabemos DONDE se encuentra la informacin que buscamos,
     solamente hay que especificar el tipo de dato con un PREFIJO DE TIPO.

     Los prefijos de tipo son:  BYTE PTR
                                WORD PTR
                                DWORD PTR

     Por lo tanto, podemos hacer algo como esto:

                   mov ax, word ptr [Apuntador]
                   mov dx, word ptr [Apuntador + 2]

     Ahora en DX tenemos el segmento y en AX tenemos el offset de la
     direccin a la que apunta la variable Apuntador.

     Incluso podramos hacer algo como:

                   mov al, byte ptr [Apuntador]
                   mov bl, byte ptr [Apuntador + 1]
                   mov cl, byte ptr [Apuntador + 2]
                   mov dl, byte ptr [Apuntador + 3]

     Ahora tenemos los cuatro bytes de Apuntador por separado. Se imaginan
     las posibilidades??? No? Pues una de ellas es que podemos acceder
     a nuestros arreglos en Pascal desde el ensamblador.

     Mediante los prefijos de tipo tambin podemos referirnos al
     offset de la direccin de alguna variable en especial. Por ejemplo:

                  mov si, offset Apuntador

     La instruccin anterior mueve a SI el offset de la direccin de
     memoria donde se encuentra la variable Apuntador (no a la que apunta).

     Esto es un poco confuso, pero quiero que sepan bien la diferencia
     entre las siguientes expresiones:

           { Suponiendo que Apuntador es una variable de tipo pointer }

           mov ax, word ptr [Apuntador]
           mov bx, offset Apuntador
           mov cx, bx
           mov dx, word ptr [bx]


     La primera instruccin mueve a AX el offset de la direccin a la
     que la variable Apuntador apunta.

     La segunda instruccin mueve a BX el offset de la direccin ocupada
     por la variable Apuntador.

     La tercera instruccin mueve el valor contenido en BX a CX. Despus
     de esta instruccin, BX y CX valdrn lo mismo.

     La cuarta instruccin mueve a DX la palabra que se encuentra en la
     direccin de memoria DS:[BX], siendo el valor de BX el offset de
     esa direccin.

     Si estudian un poco las cuatro lneas de cdigo, se darn cuenta de
     que al final, AX y DX tendrn el mismo valor, as como BX y CX.


Bueno, antes de continuar, traten de digerir todo esto. Aguntense las
ganas de vomitar. Vayan y revisen el cdigo del programa TOSTADOR.PAS
(el que no tiene ensamblador), entindanlo, descansen y regresen.


El procedimiento DibujaTostadores:
----------------------------------

El ensamblador es algo muy confuso pero no es difcil. De hecho, el
ensamblador es un smbolo de simplicidad. Despus de todo, quin pensara
que algo como Duke Nukem 3D o Phantasmagoria es simplemente una secuencia
de sumas, multiplicaciones, operaciones lgicas, desplazamientos y
rotaciones de bits?

No pienso hacer de esto un tutorial de ensamblador, ya que se mucho menos
de asm que de grficos en VGA (lo cual tampoco es mucho), as que si
tienen inters en aprender ensamblador, aunque sea para acelerar un poco
sus rutinas, les aconsejo que compren un LIBRO (una de esas cosas con
hojas y portada) y asegrense de que el libro incluya una referencia de
todas las instrucciones, por lo menos hasta las del 486.

Al principio traten de "traducir" algunas partes de sus programas a
ensamblador, especialmente los ciclos ms internos que dibujan cosas
en la pantalla. No sern pocas las veces que tengan que pasar 2 horas
reseteando la mquina porque su programa se traba quin sabe por qu,
pero al da siguiente, el programa funcionar y ustedes se sorprendern
por la simpleza del ASM.

Bueno... creo que este tutorial estaba destinado a ser puro bla bla bla,
pero en el siguiente prometo apegarme a la realidad.


Como les haba dicho, en el ejemplo de los tostadores cambi un poco las
cosas para obtener mayor velocidad. Con 10 tostadores, el incremento en
velocidad ni siquiera es notable, pero cuando aumentamos el nmero de
sprites a 50 o 100 (el nmero mximo es 255), la rutina 100% Pascal se
convierte en una procesin de tostadores. Es ah donde se ve la ventaja
del ensamblador.


En el ensamblador en lnea del Turbo Pascal, podemos acceder directamente
a cualquier variable de Pascal e incluso a los registros (records).
Por ejemplo:

                     mov ax, Cuadro
                     mov bx, Sprite.Velocidad


Pero NO PODEMOS acceder a un arreglo tan fcilmente. Es decir que no
podemos hacer algo como esto:

                     mov bx, YOffset[3]
                     mov si, Sprite[n].XPos


La nica forma de acceder a un arreglo desde el ensamblador, es calcular
la posicin del elemento que buscamos. Para eso, primero debemos conocer
el SEGMENTO de memoria en donde se encuentra el arreglo. En el caso de
que el arreglo sea una variable global, el segmento del arreglo es el
segmento de datos del programa, el cual se guarda siempre en el registro
DS, por lo que no hay que obtener el segmento sino solamente el offset.

El offset se obtiene de la misma forma que cualquier otra variable de
Pascal, es decir, con el nombre de la variable. Para acceder a un
arreglo, simplemente hay que usar como offset el nombre del arreglo
y aadirle un desplazamiento equivalente al ndice que buscamos.

Si el tipo de dato que forma nuestro arreglo tiene una longitud de
mas de un byte, entonces el desplazamiento que debemos usar es igual
al ndice multiplicado por el nmero de bytes que ocupa cada dato.

Por ejemplo, si queremos acceder a un arreglo de enteros (integer),
los cuales ocupan 2 bytes cada uno, entonces el offset que necesitamos
es igual a el ndice del elemento que buscamos multiplicado por 2 y
sumado al offset del arreglo (el cual se especifica con su nombre).

Todo lo anterior se supone para arreglos cuyo primer ndice es cero.

Si declaramos un arreglo como este:

                 var MiArreglo : array[20..30] of byte;

entonces al usarlo en el ensamblador en lnea hay que recordar que el
primer ndice es SIEMPRE cero. En el caso de MiArreglo, los ndices se
recorreran de 0 a 10 cuando accedemos al arreglo desde ASM.


Vamos a ver un ejemplo:

        var Arreglo : array[0..9] of word;  { 10 elementos de tipo word }
        begin
             { Aqu va alguna parte del programa }
             asm
                mov si, offset Arreglo
                mov cx, 10     { Hacemos un ciclo para 10 elementos }

                @ciclo:  mov ax, 0
                         mov [si], ax
                         add si, 2
                         dec cx
                         jnz @ciclo
             end;
        end;

Ahora veamos lnea por lnea:


      mov si, offset Arreglo - Esta lnea obtiene el offset de la direccin
                               en donde comienza el arreglo y lo guarda
                               en el registro ndice SI.

      mov cx, 10             - Puesto que queremos hacer un ciclo para
                               recorrer todos los elementos del arreglo,
                               cargamos el registro CX (contador) con el
                               valor de 10.

      @ciclo:  mov ax, 0     - Indicamos el inicio del ciclo con una
                               etiqueta y borramos el registro AX.

      mov [si], ax           - Movemos el valor de AX a la direccin de
                               memoria apuntada por SI. En este caso no
                               se necesita un prefijo de tipo porque el
                               compilador sabe que AX es una palabra.

      add si, 2              - Incrementamos el ndice del arreglo en 2.
                               Porqu en 2? Porque los elementos del
                               arreglo son de tipo word y miden 2 bytes
                               cada uno.

      dec cx                 - Decrementamos el contador.

      jnz @ciclo             - Si la ltima operacin, es decir, si al
                               decrementar CX, el resultado es DIFERENTE
                               de cero, entonces salta a la etiqueta y
                               contina con el ciclo.


Como se habrn dado cuenta, el cdigo anterior llena el arreglo con
ceros. Es el equivalente en Pascal de:

       for i := 0 to 9 do Arreglo[i] := 0;


Parece algo muy estpido tener que meterse con ensamblador para hacer
algo tan simple como llenar de ceros un arreglo, pero no lo es.

Recuerdan el procedimiento ClearScreen? Bueno, pues podramos decir que
la pantalla es un gran arreglo de 64000 bytes. De hecho, la definicin
de tipo de una pantalla virtual es la siguiente:

        type TVirtual = array[1..64000] of byte;

Se imaginan tener que borrar la pantalla con algo como:

        for i := 1 to 64000 do VirScr^[i] := 0;

Eso sera lento. Muuuuuy lento. Pero con ensamblador podemos hacerlo
unas seis veces ms rpido (por lo menos).

Recuerden que una palabra equivale a dos bytes, por lo tanto, si moviramos
palabras en lugar de bytes, solamente seran 32000 movimientos en lugar
de 64000. Y si moviramos dobles-palabras (con la ayuda de un 386), el
nmero de accesos sera de 16000. Para el procesador, algunas veces mover
una doble palabra es tan rpido como mover un byte, como en el caso del
procedimiento ClearScreen en la unidad Mode13.


Ahora veamos cmo podemos usar todo esto para dibujar nuestros sprites.

Lo que hice fue sustitur el siguiente procedimiento por su equivalente
en ASM:


            procedure DibujaTostadores(where : word);
            var x, y, off : word;
            i, cuadro : byte;
            begin
                 for i := 1 to NumSprites do
                 begin
                      cuadro := Sprite[i].Cuadro;
                      off := Sprite[i].Y * 320 + Sprite[i].X;
                      for y := 0 to 27 do
                      begin
                           for x := 0 to 37 do
                           begin
                                if Tostador[Cuadro]^[y, x] <> 0 then
                                   mem[where:off] := Tostador[Cuadro]^[y, x];
                                inc(off);
                           end;
                           inc(off, 282); { 282 = 320 - 38 }
                      end;
                 end;
            end;


Primero vamos a hacer un poco de cambio de variables.

En lugar de los contadores X  y  Y, vamos a usar los registros CL y CH.
El segmento de la pantalla virtual (parmetro where) ser movido al
registro de segmento ES. Tendremos una variable 'cuadro' que contendr
un apuntador al cuadro que vamos a dibujar y una variable 'spr' que
contendr la informacin del sprite que estamos dibujando. La variable
'off' ser sustituda por el registro DI.

Tambin usaremos el par DS:SI para apuntar al arreglo que contiene los
datos del cuadro. Recuerden que si alteramos el registro DS, ya no podremos
acceder a las variables del programa.

El ciclo exterior, el que va de 1 hasta NumSprites permanecer en Pascal.


El procedimiento queda de la siguiente forma:

       procedure DibujaTostadores(where : word);
       var i : byte;
       spr : TSprite;
       cuadro : PToastPic;
       begin
            for i := 1 to NumSprites do
            begin
            spr := Sprite[i];
            cuadro := Tostador[spr.cuadro];
            asm
               mov es, where
               mov bx, spr.y
               mov di, spr.x
               add bx, bx
               mov ax, word ptr [cuadro + 2]
               mov si, word ptr [cuadro]
               mov ch, 28
               add di, word ptr [YOffset + bx]
               push ds
               mov ds, ax

               @loopy:  mov cl, 38

               @loopx:  mov al, [si]
                        test al, $FF
                        jz @jump
                        mov es:[di], al

               @jump:   inc di
                        inc si
                        dec cl
                        jnz @loopx
                        add di, 282
                        dec ch
                        jnz @loopy
                        pop ds
            end;
            end;
       end;


Bien. Ahora la explicacin lnea por lnea de lo que pasa dentro del
ciclo exterior:


      spr := Sprite[i]          - Puesto que es un poco complicado acceder
                                  a arreglos de "records" en ASM, vamos
                                  a mover el elemento deseado a una variable
                                  temporal.

      cuadro := Tostador[spr.cuadro]  - De la misma manera, movemos el
                                        apuntador al cuadro a dibujar
                                        a una variable temporal.

      mov es, where             - Movemos a ES el segmento de la pantalla
                                  virtual (o VGA).

      mov bx, spr.y             - Movemos a BX la coordenada Y del sprite.

      mov di, spr.x             - Movemos a DI la coordenada X del sprite.

      add bx, bx                - Esto equivale a multiplicar BX por dos.
                                  La razn por la que se hace esto es porque
                                  vamos a acceder a un arreglo cuyos elementos
                                  son de tipo word (2 bytes) y el ndice que
                                  queremos es la coordenada Y del sprite.

      mov ax, word ptr [cuadro + 2]  - Movemos a AX el segmento de la
                                       direccin de memoria apuntada por
                                       cuadro. Es decir, el segmento donde
                                       se encuentra la informacin del cuadro.

      mov si, word ptr [cuadro]      - Movemos a SI el offset de la direccin
                                       donde se encuentran los datos del cuadro.

      mov ch, 28                - Movemos a CH (el contador que usaremos para
                                  el desplazamiento vertical) el valor 28.

      add di, word ptr [YOffset + bx]  - Le sumamos a DI el valor del arreglo
                                         YOffset. El ndice del elemento
                                         es igual a la coordenada Y del sprite.
                                         El equivalente en Pascal sera:
                                            DI := YOffset[spr.y]

                                         El arreglo YOffset est declarado en
                                         la unidad Mode13 y contiene el valor
                                         de Y * 320 para Y = 0 hasta 199.
                                         De esta forma, nos ahorramos una
                                         multiplicacin.

      push ds            - Guardamos el segmento de datos en la pila.

      mov ds, ax         - Movemos a DS el valor de AX, el cual contena
                           el segmento del cuadro que vamos a dibujar.
                           A partir de este momento, no podemos acceder
                           a las variables del programa hasta que recuperemos
                           el antiguo valor de DS.

      @loopy:  mov cl, 38  - Aqu comienza el ciclo "vertical". Movemos
                             a CL el valor de 38. CL funcionar como
                             contador para el ciclo "horizontal".

      @loopx:  mov al, [si]  - Aqu comienza el ciclo "horizontal". Movemos
                               a AL el valor contenido en DS:[SI], es decir,
                               el siguiente pxel del cuadro.

      test al, $FF       - Comprobamos si alguno de los bits del registro AL
                           est activado. Si no es as, la operacin anterior
                           debe de dar como resultado un cero. Eso tambin
                           significa que el registro AL contiene un cero.

      jz @jump           - Si la operacin di cero, es decir, si AL = 0,
                           entonces no debemos dibujar el pxel, ya que
                           el color 0 debe ser transparente, y por lo
                           tanto realizamos un salto, evitando la siguiente
                           instruccin.

      mov es:[di], al    - Movemos al valor de AL a ES:[DI]. Recuerden que
                           ES:[DI] apunta a una posicin en la pantalla virtual.

      @jump:  inc di     - Incrementamos DI, que es el offset dentro de la
                           pantalla virtual.

      inc si             - Incrementamos SI, que es el offset dentro del
                           cuadro que estamos dibujando.

      dec cl             - Decrementamos el contador "horizontal".

      jnz @loopx         - Si no es cero todava, contina con el ciclo.

      add di, 282        - Cuando ha terminado el ciclo "horizontal",
                           incrementamos DI en 282 para pasar a la siguiente
                           lnea en la pantalla virtual.

      dec ch             - Decrementamos el contador "vertical"

      jnz @loopy         - Si an no es cero, contina con el ciclo.

      pop ds             - Cuando ha terminado el ciclo vertical, entonces
                           ya terminamos de dibujar el cuadro. Ahora
                           simplemente recuperamos el valor original de DS
                           desde la pila.



Puffff!... espero que la explicacin sirva para mostrar que el ensamblador
no es tan terrible.


EJERCICIOS:
-----------

     QUE? Despus de 1400 lneas de tutorial y una patada con ensamblador???

     La tarea es muy simple. Solamente hagan un (pequeo) programa que
     use sprites.

     Pueden hacer una pelota que cuando rebote contra el piso o las paredes,
     sta se "aplaste" como si fuera de goma. Tambin podran hacer que
     un personaje camine. O podran hacer que baile. Cualquier cosa sencilla.

     El ejemplo de los tostadores solamente tiene unas 150 lneas de cdigo
     y fu escrito en muy poco tiempo. De hecho, lo mas tardado fu dibujar
     los tostadores, lo cual no tiene que ver nada con programacin.



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

     Quin necesita conclusiones cuando se pueden ver 250 tostadores
     alados volando sobre un cielo azul nublado?

     Este tutorial, que estaba planeado para ser el ms corto de todos,
     puesto que la animacin es algo muy sencillo, termin convirtindose
     en el ms largo hasta ahora. Todo fu gracias a la introduccin al
     ensamblador, pero eso no suceder siempre. Como ya dije, este NO
     es un tutorial de ensamblador, aunque a partir de ahora, nuestros
     programas incluirn un poco de ASM, pero solamente donde sea
     necesario.


     Una vez ms, NECESITO RESPUESTAS sobre estos tut's. Hasta ahora
     solamente algunos me han escrito.

     Tambin me gustara que distribuyeran los tutoriales a las personas
     que crean que les interesa. Por experiencia propia, yo se que este
     tipo de cosas no las ensean en cualquier lugar.


     El siguiente tutorial tratar sobre algunos efectos especiales. En
     especial: plasma, fuego e interferencia. Tambin incluir UN DEMO
     y su cdigo fuente (casi completo). El demo incluye algunos efectos
     que ya hemos visto, los que se vern en el siguiente tutorial, y
     algunos que veremos depus como las figuras en 3D y texturas.

     El demo y su cdigo fuente se encontrarn en un archivo comprimido
     (TETDEMO.ZIP). Les recomiendo descomprimir ese archivo en un directorio
     separado, ya que el demo comprende varios archivos. Tambin les
     sugiero que lean el archivo LEAME.TXT que se incluye con el demo,
     ya que este archivo contiene informacin de utilidad.

     Este tutorial ha sobrepasado las 1500 lneas, as que guardar las
     rimas de esta semana para la siguiente...

     Paz.
                                                                FAC
-----------------------------------------------------------------------------
