VGA – Tutorial 3 – Denthor

                       --==[ PARTE 3 ]==--

Autor: DENTHOR do ASPHYXIA
Atualizado por Snowman
Traduzido por Krull >>> tradução revisada e atualizada em 2019

Para obter os programas mencionados neste tutorial, por favor baixe o zip com a versão antiga do tutorial, neste link.

[Nota: essas coisas entre colchetes foram adicionadas pelo Snownan. O texto original ficou quase todo inalterado, exceto pela inclusão do material em C++]

Introdução

Saudações! Esta é a terceira parte da série de Treinamento em VGA! Desculpe ter demorado tanto tempo para sair, mas eu tive uma batalha com o departamento de trânsito durante três dias para conseguir registrar o meu carro, e então a Mailbox caiu. Ahh, bem, a vida fede. De qualquer modo, hoje faremos algumas coisas vital para a maioria dos programas: Linhas e Círculos.

E atenção para a parte da semana que vem: Telas Virtuais. O modo fácil de eliminar flickers, “sprites dobrados”, e sujeitar o usuário a assistir você montando a tela. Quase todo demo da ASPHYXIA tem usado uma tela virtual (com exceção da SilkyDemo), então isso é uma coisa pra se tomar cuidado. Também mostrarei como colocar todas aquelas procedures em units.

Se você gostaria de me contactar, ou ao time, há muitos modos que você pode fazê-lo:
1) Escrever uma mensagem para Grant Smith em email privado aqui ou na Mailbox BBS
2) Escrever uma mensagem aqui na conferência de Programação, aqui na Mailbox (de preferência se você tem uma pergunta geral de programação ou problemas de que outros se beneficiariam)
3) Escrever para ASPHYXIA na ASPHYXIA BBS
4) Escrever para Denthor, Eze ou Livewire na Connectix.
5) Escrever para
Grant Smith
P.O.Box 270 Kloof
3640
6) ligar para mim (Grant Smith) no número 73 2129
(deixe uma mensagem se você ligar quando eu estiver na faculdade)

OBS1 : Se você é um representante de uma companhia ou BBS e quer que a ASPHYXIA faça um demo para você, mande um email pra mim; podemos discutir.
OBS2 : Se você fez/tentou fazer um demo, MANDE PARA MIM! Estamos nos sentindo muito solitários e queremos encontrar/ajudar/trocar código com outros grupos de demos. O que você tem a perder? Mande uma mensagem aqui e podemos ver como transferir. Nós realmente queremos ouvir de você.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Algoritmo de Círculo

Vocês todos sabem como um círculo parece. Mas como desenhar um no computador?

Você provavelmente conhece círculos desenhados com os graus, nesses pontos:

                            0
                          ÜÛ|ÛÜ
                         ÛÛÛ|ÛÛÛ
                    270 ----+---- 90
                         ÛÛÛ|ÛÛÛ
                          ßÛ|Ûß
                           180

Desculpa pelo ASCII ;-)… de qualquer modo, Pascal e C++ não trabalham desse modo… eles trabalham com radianos ao invés de graus. (Você pode converter radianos para graus, mas eu não vou entrar nisso agora) Note no entanto que em Pascal e C++, o círculo se parece com isso:

                           270
                          ÜÛ|ÛÜ
                         ÛÛÛ|ÛÛÛ
                    180 ----+---- 0
                         ÛÛÛ|ÛÛÛ
                          ßÛ|Ûß
                            90

Mesmo assim, ainda podemos usar as famosas equações para desenhar nosso círculo… (Você deriva o seguinte usando o teorema de nosso bom amigo Pitágoras)
Sin (deg) = Y/R
Cos (deg) = X/R

(Isso é matemática padrão de 8ª (?) série… se você ainda não alcançou esse nível, fale com seu pai, ou se você empacar, mande-me uma mensagem e eu vou ensinar um pouco de trigonometria pra você. Espero agradar ;-))

Onde: 
Y = sua coordenada Y
X = sua coordenada X
R = seu Raio (o tamanho de seu círculo)
deg = o grau/ângulo

Para efeito de simplificação, vamos reescrever a equação para obter os valores de X e Y:

                 Y = R*Sin(deg)
                 X = R*Cos(deg)

Isso obviamente é perfeito para nós, porque nos dá X e Y para colocar em nossa rotina PutPixel (veja a Parte 1). Por causa de as funções Sin e Cos retornarem um valor Real [long], usamos uma função para arredondar (round) para transformar num inteiro (Integer).

[Pascal]
Procedure Circle (oX,oY,rad:integer;Col:Byte);
  VAR deg:real;
      X,Y:integer;
  BEGIN
    deg:=0;
    repeat
      X:=round(radCOS (deg));      Y:=round(radsin (deg));
      putpixel (x+ox,y+oy,Col);
      deg:=deg+0.005;
    until (deg>6.4);
  END;
[C++]
void Circle(int X, int Y, int rad, int col) {
 float deg = 0;
 do {
      X = round(rad * cos(deg));
      Y = round(rad * sin(deg));
      Putpixel (X+160, Y+100, col);
      deg += 0.005;
    }
    while (deg <= 6.4);
 }

No exemplo acima, quanto menos deg é aumentado, mais perto os pixels no círculo serão, mas mais lenta será a procedure. 0.005 parece ser melhor para a tela de 320×200. NOTA: ASPHYXIA não usa esse algoritmo de círculo em particular, a nossa é em Assembly, mas essa deveria ser rápida o suficiente para a maioria. Se não for, nos dê as coisas pra quê você está usando-a e nos daremos a nossa.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Algoritmos de Linha

Há muitos modos de desenhar uma linha no computador. Vou descrever uma e dar-lhes duas. (A segunda vocês podem calcular sozinhos; é baseada na primeira, mas é mais rápida)

A primeira coisa que você precisa fazer é passar o que você quer que a linha se pareça para sua procedure de linha. O que eu tenho feito é dizer que x1,y1 é o primeiro ponto da linha e x2,y2 é o segundo ponto. Também passamos a cor para a procedure. (Lembre, o topo esquerdo da tela é (0,0); veja a Parte 1)

Ex.           o(X1,Y1)
                ooooooooo
                         ooooooooo
                                  oooooooo(X2,Y2)

De novo, perdão pelos meus desenhos 😉

Para achar o tamanho da linha, dizemos o seguinte:

[Pascal]
        XLength = ABS (x1-x2)
        YLength = ABS (y1-y2)
 [C++] 
        xlength = abs(x1-x2);
        ylength = abs(y1-y2);

A função ABS significa que seja qual for o resultado, ele vai te dar um resultado absoluto, ou positivo. Nesse estágio, eu seto uma variável que diz se a diferença entre os dois x’s é positivo, zero ou negativo. (faço o mesmo para os y’s) Se a diferença é zero, apenas uso um loop mantendo os dois com diferença zero positiva, e saio.

Se nem os x’s nem y’s têm diferença zero, calculo a inclinação de X e Y, usando as seguintes equações:

[Pascal]
        Xslope = Xlength / Ylength
        Yslope = Ylength / Xlength
[C++]
 [Nota: C++ é significantemente diferente aqui. Eu tive que adicionar uma checagem de divisão por zero especial. C++ não gosta de x/0. Eu também  coloquei um type-casting que pode não ser preciso :)] 
   if ((xlength != 0) && (ylength != 0)) {
          xslope = (float)xlength/(float)ylength;
          yslope = (float)ylength/(float)xlength;
        }
        else {
          xslope = 0.0;
          yslope = 0.0;
        }

Como você pode ver, as inclinações são números reais.

NOTA : XSlope = 1 / YSlope

Agora, há dois modos de desenhar as linhas:

       X = XSlope * Y
       Y = YSlope * X

A pergunta é, qual delas usar? Se você usar a errada, sua linha vai parecer com isso:

    o
       o
          o

Ao invés disso:

    ooo
       ooo
          ooo

Bem, a solução é a seguinte:

                       *\``|``/*
                       ***\|/***
                       ----+----
                       ***/|\***
                       */``|``\*

Se a inclinação está na área dos asteriscos (*), usamos a primeira equação. se está na outra seção (`) então usamos a segunda. O que você faz é calcular a variável do lado esquerdo colocando a variável no lado direito num loop e resolver. Abaixo está nossa rotina completa:

[Pascal]
 Procedure Line (x1,y1,x2,y2:integer;col:byte);
 VAR x,y,xlength,ylength,dx,dy:integer;
     xslope,yslope:real;
 BEGIN
   xlength:=abs (x1-x2);
   if (x1-x2)<0 then dx:=-1;   
   if (x1-x2)=0 then dx:=0;   
   if (x1-x2)>0 then dx:=+1;
   ylength:=abs (y1-y2);
   if (y1-y2)<0 then dy:=-1;   
   if (y1-y2)=0 then dy:=0;   
   if (y1-y2)>0 then dy:=+1;
   if (dy=0) then 
   BEGIN
     if dx<0 then for x:=x1 to x2 do
       putpixel (x,y1,col);     
     if dx>0 then for x:=x2 to x1 do
         putpixel (x,y1,col);
     exit;
   END;
   if (dx=0) then 
   BEGIN
     if dy<0 then for y:=y1 to y2 do
       putpixel (x1,y,col);     
      if dy>0 then for y:=y2 to y1 do
       putpixel (x1,y,col);
     exit;
   END;
   xslope:=xlength/ylength;
   yslope:=ylength/xlength;
   if (yslope/xslope<1) and (yslope/xslope>-1) then BEGIN
      if dx<0 then for x:=x1 to x2 do 
      BEGIN
         y:=  round (yslope*x);
         putpixel (x,y,col);
       END;
      if dx>0 then for x:=x2 to x1 do 
      BEGIN
        y:= round (yslopex);
        putpixel (x,y,col);                  
      END;   
     END   
      ELSE   
      BEGIN     
       if dy<0 then for y:=y1 to y2 do 
       BEGIN                    
         x:= round (xslope*y); 
         putpixel (x,y,col);
       END;     
       if dy>0 then for y:=y2 to y1 do 
        BEGIN                    
         x:= round (xslopey);
          putpixel (x,y,col);
         END;
      END;
    END; 
[C++]
void Line2(int x1, int y1, int x2, int y2, int col) {
 int   x, y, xlength, ylength, dx, dy;
 float xslope, yslope;
 xlength = abs(x1-x2);
   if ((x1-x2)  < 0) dx = -1;   
   if ((x1-x2) == 0) dx =  0;   
   if ((x1-x2)  > 0) dx = +1;
 
 ylength = abs(y1-y2);
 if ((y1-y2)  < 0) dy = -1;   
  if ((y1-y2) == 0) dy =  0;   
  if ((y1-y2)  > 0) dy = +1;

if (dy == 0) {
     if (dx < 0)       
      for (x=x1; x<x2+1; x++)
       Putpixel (x,y1,col);
     if (dx > 0)   
      for (x=x2; x<x1+1; x++) 
        Putpixel (x,y1,col);
   }

 if (dx == 0) {
    if (dy < 0)       
     for (y=y1; y<y2+1; y++) 
     Putpixel (x1,y,col);       
    if (dy > 0)
    for (y=y2; y<y1+1; y++)
       Putpixel (x1,y,col);
   }
 if ((xlength != 0) && (ylength != 0)) {
     xslope = (float)xlength/(float)ylength;
     yslope = (float)ylength/(float)xlength;
   }
   else {
     xslope = 0.0;
     yslope = 0.0;
   }
 if ((xslope != 0) && (yslope != 0) &&
       (yslope/xslope < 1) && (yslope/xslope > -1)) {
     if (dx < 0)
       for (x=x1; x<x2+1; x++) {
     y = round (yslope*x);     
    Putpixel (x,y,col);
     }     
   if (dx > 0)       
      for (x=x2; x<x1+1; x++) {
      y = round (yslope*x);
     Putpixel (x,y,col);
   }
   }
   else {
     if (dy < 0)
       for (y=x1; y<x2+1; y++) {
     x = round (xslope*y);     
     Putpixel (x,y,col);       
     }     
    if (dy > 0)       
      for (y=x2; y<x1+1; y++) {
      x = round (xslope*y);
     Putpixel (x,y,col);
       }
   }
 }

Um pouco grande, né? Aqui vai um modo mais curto de se fazer a
mesma coisa:

[Pascal] 
function sgn(a:real):integer;
 begin
      if a>0 then sgn:=+1;
      if a<0 then sgn:=-1;
      if a=0 then sgn:=0;
 end; procedure line(a,b,c,d,col:integer);
 var u,s,v,d1x,d1y,d2x,d2y,m,n:real;
     i:integer;
 begin
      u:= c - a;
      v:= d - b;
      d1x:= SGN(u);
      d1y:= SGN(v);
      d2x:= SGN(u);
      d2y:= 0;
      m:= ABS(u);
      n := ABS(v);
      IF NOT (M>N) then
      BEGIN
           d2x := 0 ;
           d2y := SGN(v);
           m := ABS(v);
           n := ABS(u);
      END;
      s := INT(m / 2);
      FOR i := 0 TO round(m) DO
      BEGIN
           putpixel(a,b,col);
           s := s + n;
           IF not (s<m) THEN
           BEGIN
                s := s - m;
                a:= a +round(d1x);
                b := b + round(d1y);
           END
           ELSE
           BEGIN
                a := a + round(d2x);
                b := b + round(d2y);
           END;
      end;
 END; 


[C++]
    int sgn (long a) {
   if (a > 0) return +1;
   else if (a < 0) return -1;
   else return 0; 
   } 
  void Line(int a, int b, int c, int d, int col) { 
  long u,s,v,d1x,d1y,d2x,d2y,m,n;
   int  i;  
   u   = c-a;
   v   = d-b;
   d1x = sgn(u);
   d1y = sgn(v);
   d2x = sgn(u);
   d2y = 0;
   m   = abs(u);
   n   = abs(v); 
   if (m<=n) {
     d2x = 0;
     d2y = sgn(v);
     m   = abs(v);
     n   = abs(u);
   } 
     s = (int)(m / 2); 
     for (i=0;i= m) {
       s -= m;
       a += d1x;
       b += d1y;
     }
     else {
       a += d2x;
       b += d2y;
     }
   } 
} 

Essa rotina é muito rápida, e deveria servir para todos os seus requerimentos (ASPHYXIA usou-a por um bom tempo, antes de fazermos nossa nova). No fim do programa, as duas rotinas, de círculo e linha, são testadas. Umas poucas das procedures das primeiras partes são usadas também.

Rotinas de círculo e linha podem parecer coisas triviais, mas elas são um componente vital de muitos programas, e você pode gostar de olhar outros métodos de desenhá-los, em livros na biblioteca (eu sei que na Universidade eles têm livros pra fazer esse tipo de coisa em todo lugar). Uma boa rotina de linha para se olhar é a de Bressenhams… Existe a rotina de Bressenhams para círculos também… Eu tenho documentação para elas, se alguém estiver interessado, elas são de longe, as rotinas mais rápidas que você usará.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Fechando

A faculdade começou de novo, então eu estou indo pra cama antes de 3 da manhã (UAU) , então minha quote dessa semana não foi escrita do mesmo modo que a da semana passada (Para aquela, eu tinha conseguido dormir 8 horas em 3 dias, e pensei e escrevi a quote às 2:23 antes de cair de sono)

     [  "O que ele faz?" ela pergunta.        
        "É um computador," ele responde.       
         "Sim, querido, mas o que ele faz?"        
       "Ele, é... computa! É um computador."  
       "O que ele computa?"        
       "O que? Er? Hmmmm. Números! Sim, números!"
Ele sorri com preocupação.        
       "Por que?"        
       "Por que? Bem ..hum.. por que?" Ele começa a suar.        
       "Quero dizer, é só uma coisa pra empoeirar, ou ele realmente faz algo útil?"
       "Hmmm... você pode ligar para outros computadores com ele!" A esperança acende em seus olhos. "Então você pode pegar programas de outros     computadores!"        
        "Tô vendo. diga-me, o que esses programas fazem?"        
        "Fazem? Eu acho que eu não..."
        "Ah, sim. Eles computam. Números. Por nenhum motivo." Ele murcha sob a contemplação dela.        
        "Sim, mas..."    
        Ela sorri, e ele se esgota, derrotado. Ela dá uma outra olhada para a coisa. "Se bem que", ela diz, com um estranho olhar em seus olhos. Ele olha pra cima, um louco olhar de esperança em sua face. "Ele vem em cor de rosa?" ela pergunta. ]                                    
                 - Grant Smith                     
                     Terça, 27 de Julho de 1993                         
                             9:35 da noite. 

Vejo você na próxima,
– Denthor
– Krull

A Nova Krull's HomePage