Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Supongo que casi todos los que nos paseamos por aquí tenemos el
suficiente nivel en matemáticas como para pensar una forma de dibujar
una recta: aplicando la ecuación de la recta "tal cual". La recta que pasa
por dos puntos (x1,y1) (x2,y2) tiene como ecuación:
x-x1 y-y1
------- = -------
x2-x1 y2-y1
y2-y1
y = y1 + ------- (x-x1)
x2-x1
y = y1 + c (x-x1)
var
d,
dx, dy, { Salto total según x e y }
ai, bi,
end
else { Si no están ordenadas }
begin
xi := - 1; { Increm. -1 (hacia atrás) }
begin
yi := 1;
dy := y2 - y;
end
else
begin
yi := - 1;
dy := y - y2;
end;
plot(x, y); { Dibujamos el primer punto }
repeat
if (d >= 0) then { Comprueba si hay que avanzar según y }
begin
y := y + yi; { Incrementamos Y como deba ser (+1 ó -1) }
d := d + ai; { y la variable de control }
end
else
d := d + bi; { Si no varía y, d sí lo hace según bi }
x := x + xi; { Incrementamos X como corresponda }
end
else { Si hay más salto según y que según x }
begin { (más vertical), todo similar }
ai := (dx - dy) * 2;
bi := dx * 2;
d := bi - dy;
repeat
if (d >= 0) then
begin
x := x + xi;
d := d + ai;
end
else
d := d + bi;
y := y + yi;
plot(x, y);
until (y = y2);
end;
end;
En este algoritmo, que está expresado de forma genérica, basta sustituir
el "Plot(x,y)" por nuestra propia rutina de dibujo de puntos, como el
PonPixel(x,y,color).
{filled ellipse}
procedure disk(xc, yc, a, b : integer);
var
x, y : integer;
aa, aa2,
bb, bb2,
d, dx, dy : longint;
begin
x := 0;
y := b;
aa := longint(a) * a;
aa2 := 2 * aa;
bb := longint(b) * b;
bb2 := 2 * bb;
d := bb - aa * b + aa div 4;
dx := 0;
dy := aa2 * b;
vLin(xc, yc - y, yc + y);
while (dx < dy) do
begin
if (d > 0) then
begin
dec(y);
dec(dy, aa2);
dec(d, dy);
end;
inc(x);
inc(dx, bb2);
inc(d, bb + dx);
vLin(xc - x, yc - y, yc + y);
vLin(xc + x, yc - y, yc + y);
end;
inc(d, (3 * (aa - bb) div 2 - (dx + dy)) div 2);
while (y >= 0) do
begin
if (d < 0) then
begin
inc(x);
inc(dx, bb2);
inc(d, bb + dx);
vLin(xc - x, yc - y, yc + y);
vLin(xc + x, yc - y, yc + y);
end;
dec(y);
dec(dy, aa2);
inc(d, aa - dy);
end;
end;
¿Y por qué se usa VLIN en vez del procedimiento anterior para dibujar
líneas? Pues por rapidez: normalmente lo más rápido es dibujar una
línea horizontal, ya que todos los puntos se encuentran seguidos en la
memoria de pantalla. El siguiente caso es el de una línea vertical: cada
punto está 320 bytes después del anterior en la memoria de pantalla. En
el caso general, estos incrementos varían, y hay que usar algoritmos más
genéricos y más difíciles de optimizar.
Algunas optimizaciones.
Vamos a empezar por hacer un programita que haga rotar una línea,
como si fueran una aguja de un reloj. Para ello aprovecharemos parte de
lo que vimos en el apartado anterior y parte de éste, ya aplicado...
{--------------------------}
{ Ejemplo en Pascal: }
{ }
program GrB3;
var
regs: registers; { Para acceder a los registros, claro }
bucle: real; { Para bucles, claro }
begin
regs.ah := 0; { Función 0 }
regs.al := modo; { El modo indicado }
begin
Mem[$A000 : y * 320 + x] := color;
end;
d,
dx, dy, { Salto total según x e y }
ai, bi,
begin
xi := 1; { Incremento +1 }
dx := x2 - x; { Espacio total en x }
end
else { Si no están ordenadas }
begin
xi := - 1; { Increm. -1 (hacia atrás) }
dx := x - x2; { y salto al revés (negativo) }
end;
if (y < y2) then { Análogo para las componentes Y }
begin
yi := 1;
dy := y2 - y;
end
else
begin
yi := - 1;
dy := y - y2;
end;
PonPixel(x, y,color); { Dibujamos el primer punto }
repeat
if (d >= 0) then { Comprueba si hay que avanzar según y }
begin
end
else
d := d + bi; { Si no varía y, d sí lo hace según bi }
x := x + xi; { Incrementamos X como corresponda }
PonPixel(x, y, color); { Dibujamos el punto }
until (x = x2); { Se repite hasta alcanzar el final }
end
else { Si hay más salto según y que según x }
begin { (más vertical), todo similar }
ai := (dx - dy) * 2;
bi := dx * 2;
d := bi - dy;
repeat
if (d >= 0) then
begin
x := x + xi;
d := d + ai;
end
else
d := d + bi;
y := y + yi;
PonPixel(x, y, color);
until (y = y2);
end;
end;
begin
ModoPantalla($13); { Modo 320x200x256 }
bucle := 0; { Empezamos en 0 __RADIANES__ }
repeat
linea(160,100, { Línea desde el centro de la pantalla }
160 + round(60*cos(bucle)), { Extremo en un círculo }
100 + round(40*sin(bucle)),
0); { Color negro (borrar) }
bucle := bucle + 0.1; { Siguiente posición }
15);
delay(25); { Esperamos 25 milisegundos }
until keyPressed; { Seguimos hasta que se pulse una tecla }
tecla := ReadKey; { Quitamos esa tecla del buffer del
teclado }
{--------------------------}
{ Ejemplo en Pascal: }
{ }
program GrB3;
var
regs: registers; { Para acceder a los registros, claro }
begin
regs.ah := 0; { Función 0 }
regs.al := modo; { El modo indicado }
intr($10,regs); { Interrupción de video }
end;
begin
Pantalla[y, x] := color;
end;
d,
dx, dy, { Salto total según x e y }
ai, bi,
begin
xi := 1; { Incremento +1 }
dx := x2 - x; { Espacio total en x }
end
else { Si no están ordenadas }
begin
xi := - 1; { Increm. -1 (hacia atrás) }
begin
yi := 1;
dy := y2 - y;
end
else
begin
yi := - 1;
dy := y - y2;
end;
PonPixel(x, y,color); { Dibujamos el primer punto }
repeat
if (d >= 0) then { Comprueba si hay que avanzar según y }
begin
end
else
d := d + bi; { Si no varía y, d sí lo hace según bi }
x := x + xi; { Incrementamos X como corresponda }
end
else { Si hay más salto según y que según x }
begin { (más vertical), todo similar }
ai := (dx - dy) * 2;
bi := dx * 2;
d := bi - dy;
repeat
if (d >= 0) then
begin
x := x + xi;
d := d + ai;
end
else
d := d + bi;
y := y + yi;
PonPixel(x, y, color);
until (y = y2);
end;
end;
begin
ModoPantalla($13); { Modo 320x200x256 }
bucle := 0; { Empezamos en 0 __RADIANES__ }
repeat
linea(160,100, { Línea desde el centro de la pantalla }
160 + round(60*cos(bucle)), { Extremo en un círculo }
100 + round(40*sin(bucle)),
0); { Color negro (borrar) }
15);
delay(25); { Esperamos 25 milisegundos }
until keyPressed; { Seguimos hasta que se pulse una tecla }
tecla := ReadKey; { Quitamos esa tecla del buffer del
teclado }
Pero imaginad que estamos rotando una figura complicada, con cientos
de puntos, y que además no trabajamos en el plano, sino en el espacio,
con lo que tenemos rotaciones en torno a tres ejes (teneis un ejemplo
después de la ampliación 5: ensamblador desde Turbo Pascal que, dicho
sea de paso, cuenta cómo corregir un fallo del algoritmo que he puesto
antes para dibujar líneas).
Se diría que las demos que a todos nos asombran no pueden estar
hechas así, ¿verdad? Pues el truco se llama tablas. Nada más y nada
menos. En vez de calcular cada pasada el coseno de 10 grados, se
calcula una vez este valor al principio del programa y se guarda en un
ARRAY, con lo cual no accederemos como "cos(10)" sino como
"coseno[10]".
Esa es la primera mejora, pero aun hay más. Multiplicar por números
reales es lento, así que la segunda mejora que se nos puede ocurrir es
trabajar con números enteros. ¿Pero cómo, si el seno y el coseno van
de 0 a 1? Pues multiplicándolos por 100 o 256, por ejemplo, antes de
guardarlos en nuestro array. Al fin y al cabo, en nuestra pantalla todas
las coordenadas son enteras. Basta tenerlo en cuenta a la hora de
multiplicar por el radio para que no se nos salga de la pantalla... ;-)
Además, es mejor usar números como 256 o 128 que 100 o 200. ¿Por
qué? Por lo que ya hemos comentado antes: las multiplicaciones y
divisiones por múltiplos de dos se pueden expresar como rotaciones de
bits (SHL y SHR), mucho más rápidas que una multiplicación en general.
(Hay un ejemplo de todo esto como ampliación al curso: entre los fuentes
de ejemplo, hablando de rotaciones en 3D).
Incluso efectos cómo ese de una lente o una bola de cristal que pasa por
encima de un dibujo, y se ve a través suyo el fondo deformado, suelen
estar basados en tablas para mayor rapidez...
Pero todo eso y más lo dejo para que jugueis. En el próximo apartado
vemos un poco cómo manejar la paleta de colores, y damos por
terminada la parte del curso relativa a gráficos.