miércoles, 8 de junio de 2016

VOLCADO DEL BUFFER CON LDI (VI): IMPRESIÓN DE SPRITES





Antes de proceder a estudiar la rutina, voy a intentar explicar algunos conceptos en el movimiento a pixeles (alta resolución).

A la hora de imprimir gráficos en el spectrum, lo que se hace es meter ciertos valores en las direcciones de pantalla (en nuestro caso en el buffer).
Si yo quiero imprimir el sprite del esqueleto en las coordenadas[(Y,X))] (0,0) tendré que empezar a meterlo en la dirección 8000h que es donde le corresponde (Gráfico sin desplazar). Para imprimirlo en la coordenada (0,2), lo meto también a partir de la dirección 8000h, pero desplazado 2 pixeles a la derecha. Y lo mismo en (0,4) y (0,6). Para imprimirlo en (0,8) se empezará a meter en la dirección 8001h sin desplazarlo y así sucesivamente.


8000h     8001h      8002h 



Gráfico sin desplazar (0,0)




Gráfico desplazado dos pixeles a la derecha (0,2)




Gráfico desplazado cuatro pixeles a la derecha (0,4)




Gráfico desplazado seis pixeles a la derecha (0,6)





Esto significa que si la coordenada X es cero o múltiplo de 8, el gráfico se mete sin desplazar y si no lo es, hay que desplazarlo el número de veces que la coordenada X exceda del múltiplo de 8 inmediatamente anterior.

Como podrás imaginar, para conseguir esto hay que hacer muchos desplazamientos y esto enlentece la rutina de impresión.

Yo uso un método explicado por Pablo Ariza en una Microhobby Especial.



Esta rutina nos crea una tabla de tal manera que:


  • En FANNh nos mete el número NN desplazado 2 veces a la derecha y en FBNN nos mete el sobrante de este desplazamiento.
  • En FCNNh nos mete el número NN desplazado 4 veces a la derecha y en FDNN nos mete el sobrante de este desplazamiento.
  • En FENNh nos mete el número NN desplazado 4 veces a la derecha y en FFNN nos mete el sobrante de este desplazamiento.
¿Qué quiero decir con sobrante?
Fíjate en este gráfico:

8000h                   8001h

Arriba tenemos un cuadrado sin desplazar que ocupa a partir de la posición 8000h. Abajo lo hemos desplazado dos pixeles a la derecha que nos ocupa la dirección 8000h y la 8001h. ¿Qué parte del gráfico nos ocupa la dirección 8001h? Pues lo que nos ha sobrado al desplazarlo dos pixeles. Aquí verás más claro lo que decía en la entrada anterior sobre que si la coordenada X no es cero o múltiplo de 8 (si hay desplazamiento) el cacho de buffer a guardar ocupa un caracter más de ancho que el sprite.

Esta tabla se usa así: Supongamos que un byte del sprite es 11111111b (FFh) y queremos imprimirlo en la coordenada (0,2). Necesitamos desplazarlo 2 veces a la derecha. Cargamos H con FAh y L con FFh. Al hacer LD A,(HL) en A obtenemos 00111111b y lo metemos en 8000h. Ahora necesitamos la parte sobrante. Pues  cargamos H con FBh y L con FFh. Al hacer LD A,(HL) obtenemos 11000000h y lo metemos en 8001h. Si hacemos lo mismo con los 8 bytes del gráfico lo habremos imprimido en la coordenada X=2.

Este sistema es rápido, pero supone reservar desde FA00h hasta FFFFh y el movimiento mínimo horizontal va a ser de 2 pixeles. Creo que estas dos limitaciones merecen la pena.


MÁSCARAS

Como ya he comentado, esta rutina imprime sprites, osea que no alteran el fondo por donde pasa (para eso guardamos los trozos de buffer) y tiene partes transparentes, por lo que se ve el fondo que hay a través de ellos.
Para conseguir esto último se usa la máscara: a cada byte del gráfico le corresponde su máscara, que es otro byte en el cual los unos son las partes transparentes, y los ceros las opacas. Por eso los sprites ocupan el doble que los gráficos.

Aquí podemos ver un cuadrado con su máscara correspondiente:


GRÁFICO                                                              MASCARA

Como puedes, ver he puesto unos en la máscara en los pixeles que quiero que sean transparentes; es decir en los bordes y en el centro. Suelo dejar un pixel opaco entre la máscara y el gráfico para que éste se defina mejor la imprimirlo sobre el fondo.


En este gráfico de abajo he fusionado el gráfico con la máscara para que lo veas más claro. En amarillo está el gráfico y en negro la máscara. En blanco están los pixeles que dejo de borde entre la máscara y el gráfico.



¿Cómo se maneja esto a la hora de imprimir?: Pues cogemos el byte de la máscara y hacemos un AND con el byte del fondo y a esto le hacemos un OR con el byte del gráfico.

Para crear los sprites uso el editor SevenuP de Metalbrain, que funciona de maravilla. Te explico lo fundamental de su uso para lo que nos ocupa.

En File pulsas New y te pedirá el tamaño del gráfico a crear en pixeles. Te aparecerá una cuadrícula como la de estos ejemplos. Al pulsar la flecha blanca te cambia el modo del cursor. Pinta el gráfico que quieras y luego pulsa en Mask y activa Use mask y View mask. Aquí pintas la máscara.

Yo para mi rutina de sprites necesito la máscara invertida (ya lo explicaré) por lo que tienes que pulsar en el botón INV. Ya podemos salvar el sprite.

En File pulsas Output options y tienes que dejar las opciones como en esta figura:





El byte de la máscara va antes que el del gráfico y lo guardo en Horizontal Zig Zag osea, que se guarda la primera línea del gráfico de izquierda a derecha y después la segunda de derecha a izquierda, la tercera de izquierda a derecha y la cuarta de derecha a izquierda, etc. Esta forma de guardar los sprites nos supone que podamos programar una rutina de impresión más rápida.

Finalmente  en File, pulsamos en Export data y elegimos que nos lo grabe con extensión .BIN.


Con todo esto vamos a ver la rutina de impresión de sprites.




En el registro B metemos la altura del gráfico dividido por 2.
Cargamos DE con la dirección del buffer donde va el gráfico (este dato lo metimos en DIRPBUF+1 cuando lo hallamos en la rutina de guardar los fondos).
Cargamos HL con la dirección del gráfico a imprimir.
Guardamos el registro SP y lo cargamos con HL. Los datos del gráfico los vamos a ir cogiendo a base de POPs, como los datos de la cola de impresión de sprites.
Al hacer EX DE,HL, metemos en HL la dirección del buffer donde vamos a imprimir.
En A metemos la anchura del sprite y lo guardamos en las direcciones que nos hace falta para que la rutina sea más rápida.
Si la coordenada X es 0 o múltiplo de 8 significa que no hay rotaciones por lo que saltamos a IMPBUFCARACTERJUSTO, que es una rutina semejante pero sin rotar nada y más rápida.

Lo que nos ha sobrado de ser múltiplo de 8 se suma a F8h. Así ya sabemos qué tabla de rotaciones tenemos que usar y guardo este dato en la rutina: si X=12 (00001100b) y hacemos AND 7, nos da como resultado 4 (00000100b) osea que hay que usar la tabla de 4 desplazamientos. F8h+4=FCh que es donde está esta tabla. Lo guardo donde la voy a usar y lo incremento donde voy a usar los restos.
Cargo DE con el ancho del buffer, C con el ancho del sprite y con EXX paso a los registros alternativos.
Con POP DE cargo E con la máscara del primer byte del sprite y D con el gráfico.
En H meto el inicio de la tabla de desplazamientos. Al hacer "LD L,E",  en (HL) tengo la máscara rotada.
La cargo en A (LD A,(HL)) y la invierto con CPL, ya que las máscaras las salvamos invertidas desde SevenuP.

¿Por qué salvo la máscara invertida y luego la vuelvo a invertir en la rutina en vez de salvarla normal? Pues porque esta máscara que estoy invirtiendo es la original desplazada. Como usamos las tablas, a la izquierda nos ha metido ceros y eso no nos interesa ya que la parte desplazada ha de ser transparente. Por eso al hacer CPL nos convierte en unos los bits transparentes de la máscara y en ceros los opacos que es lo que nos hace falta.

Al hacer EXX, HL contiene la dirección del buffer, por lo que hacemos un AND (HL) como expliqué antes.
Volvemos a hacer EXX, por lo que H contiene la tabla de rotaciones. Con LD L,D conseguimos que en (HL) esté el gráfico rotado (D tenía el gráfico) y hacemos el OR.

Hasta este punto hemos imprimido el gráfico con la máscara desplazado. En la siguiente dirección de memoria (INC HL) meteremos el resto.
Para eso hacemos EXX  e incrementamos HL. Así H tiene el inicio de la tabla de los restos de los desplazamientos y hacemos lo mismo que antes.
Repetimos lo que nos ocupa de ancho el sprite y a partir de ANF1 nos ponemos a imprimir la siguiente línea del sprite. Para eso sumamos ANPAN (que está en DE) a la dirección del buffer donde terminó de imprimirse la primera línea del sprite y hacemos el mismo proceso pero de derecha a izquierda.

Por eso el dato de la tabla del sprite que guarda el alto del mismo se divide por la mitad: hacemos dos pasadas por cada iteración del bucle.

Recupero el Stack y repito todo el proceso con el siguiente sprite: guardo el fondo y lo imprimo.

Aquí he puesto un .GIF para que se vea más claro el proceso (espero).






Cuando la coordenada X es cero o múltiplo de 8, no hay rotaciones, así que saltamos a una rutina especial que imprime más rápido el sprite: IMPBUFCARACTERJUSTO




Una vez imprimidos todos los sprites, tenemos que volcar el buffer a la pantalla real para que se vea lo que hemos hecho.










No hay comentarios:

Publicar un comentario