
                             VGA-Kurs - Part #8

Hallo! Leider mute 'T.C.P.'s Beginner's Guide To VGA Coding' in der letzten
Ausgabe ausfallen, weil ich keine Zeit hatte, aber diesmal gibt es wieder einen
neuen Teil, und zwar den 8.!
Und der behandelt auf vielfachen Wunsch (von 2 Lesern) das Thema 'Plasmas'.
Erstmal eine kleine Erklrung fr die, die noch nie solch einem Effekt begegnet
sind: Es gibt zwei Arten von Plasmas. Das Colorcycle- und das Realtime- oder
auch Sinus-Plasma. Beim Colorcycle-Plasma handelt es sich um ein nach einer
bestimmten Formel vorberechnetes, buntes Bild, dessen Farben einfach per
Palettenrotation vertauscht werden. Um das Bild zu berechnen bedient man sich
der Iteration. Es werden also 9 Punkte (8 am Rand, einer in der Mitte)
zuflliger Farbe auf den Screen gesetzt, so da sie ihn in 4 Rechtecke teilen.
Nun verfhrt man mit diesen Rechtecken genauso wie mit dem Screen am Anfang,
nur da die neuen Punkte aus den alten durch Interpolation errechnet werden.
Da diese Art von Plasma aber sowieso die uninteressantere ist, hier nur
schnell der Source und dann ohne groe Erklrungen zum Sinus-Plasma.

uses crt;

var n       : byte;
    Palette : array[0..767] of byte;

function newcol(mc,n,dvd:integer) : byte;
begin
  newcol := ((mc+n-random(n)) div dvd) mod 192;
end;

procedure subdivide(x1,y1,x2,y2:word);
var x,y,dxy,n1,n2,n3,n4 : word;

begin
  if (x2-x1 < 2) and (y2-y1 < 2) then exit;
  x := (x2+x1) div 2;
  y := (y2+y1) div 2;
  n1 := mem[$A000:320*y1+x1];
  n2 := mem[$A000:320*y2+x1];
  n3 := mem[$A000:320*y1+x2];
  n4 := mem[$A000:320*y2+x2];
  dxy := 5 * (x2-x1+y2-y1) div 3;
  if mem[$A000:320*y1+x] = 0 then mem[$A000:320*y1+x] := newcol(n1+n3,dxy,2);
  if mem[$A000:320*y+x1] = 0 then mem[$A000:320*y+x1] := newcol(n1+n2,dxy,2);
  if mem[$A000:320*y+x2] = 0 then mem[$A000:320*y+x2] := newcol(n3+n4,dxy,2);
  if mem[$A000:320*y2+x] = 0 then mem[$A000:320*y2+x] := newcol(n2+n4,dxy,2);
  mem[$A000:320*y+x] := newcol(n1+n2+n3+n4,dxy,4);
  subdivide(x1,y1,x,y);
  subdivide(x,y1,x2,y);
  subdivide(x1,y,x,y2);
  subdivide(x,y,x2,y2);
end;

procedure SetPal(col,r,g,b:byte);
begin
  port[$3C8] := col;
  port[$3C9] := r;
  port[$3C9] := g;
  port[$3C9] := b;
end;

procedure RotatePalette;
var Temp : array[0..2] of byte;

begin
  repeat
    move(Palette,Temp,3);
    move(Palette[3],Palette[0],765);
    move(Temp,Palette[765],3);
    for n := 0 to 255 do setpal(n,Palette[n*3],Palette[n*3+1],Palette[n*3+2]);
  until keypressed;
end;

begin
  randomize;
  asm mov ax,13h; int 10h end;  { In Mode 13h schalten }
  fillchar(Palette,768,0);      { Palette erstellen }
  for n := 0 to 255 do begin
    Palette[n*3+1] := n div 2;
    Palette[n*3+2] := n;
    setpal(n,0,n div 2,n);
  end;
  subdivide(0,0,319,199);       { Plasma aufbauen }
  RotatePalette;                { Palette rotieren }
  readkey;
  asm mov ax,3; int 10h end;    { Zurck zum Textmodus }
end.

Der Grund, warum dieses Plasma eher langweilig wirkt, ist das Fehlen einer
Bewegung. Auerdem wren etwas rundere Formen angenehm.
Diese Mngel behebt das Sinus-Plasma. Sein Nachteil ist allerdings, da es
sehr viel mehr Rechenzeit erfordert, deshalb sollte es mglichst in Assembler
geschrieben werden.
Doch wie macht man das berhaupt? Zuerst bentigt man eine (Co)Sinus-Tabelle.
Diese entscheidet nachher die Form des Plasmas, allerdings nicht allein.
Auch wie die Werte aus dieser Tabelle entnommen werden, ist relevant.
Als Beispiel benutzen wir hier die Formel

Farbwert := SinTab[Wert1] + SinTab[Wert2] + SinTab[Wert3] + SinTab[Wert4]

Die vier Indizes sind Werte, die bei jedem Durchlauf um einen bestimmten oder
zuflligen Wert erhht werden, wodurch eine Bewegung zustande kommt.
Da wir die Variablen innerhalb der Loops verndern, mssen wir sie vorher in
Hilfsvariablen (TWert1-4) sichern.
Die Variablen Wert1 und Wert2 sind fr das horizontale und Wert3 und Wert4 fr
das vertikale Muster des Plasmas verantwortlich. Sie werden in den
entsprechenden Loops um feste Werte erhht, die die Gre der Plasma-Kreise
bestimmen.
Das Ganze nun erstmal in einer 100%-Pascal-Version:

uses crt;

var Wert1,Wert2,Wert3,Wert4 : byte;
    TWert1,TWert2,TWert3,TWert4 : byte;
    SinTab : array[0..255] of byte;
    n1,n2 : word;
    col : byte;

procedure SetPal(col,r,g,b:byte);
begin
  port[$3C8] := col;
  port[$3C9] := r;
  port[$3C9] := g;
  port[$3C9] := b;
end;

procedure CalcSinus(Ofs,Amp:byte;Len,Par:word);
begin
  for n1 := 0 to Len do SinTab[n1] := round(sin(n1/Par*pi*Len/180*2)*Amp)+Ofs;
end;

begin
  CalcSinus(32,31,255,360);
  asm mov ax,13h; int 10h end;
  for n1 := 0 to 127 do begin
    setpal(n1,n1 div 6,n1 div 3,n1 div 3);
    setpal(255-n1,n1 div 6,n1 div 3,n1 div 3);
  end;
  repeat
    TWert3 := Wert3;
    TWert4 := Wert4;
    for n1 := 0 to 319 do begin
      TWert1 := Wert1;
      TWert2 := Wert2;
      for n2 := 0 to 199 do begin
        col := SinTab[TWert1] + SinTab[TWert2] +
               SinTab[TWert3] + SinTab[TWert4];  { Farbwert berechnen }
        mem[$A000:n2*320+n1] := col;
        inc(TWert1,4);
        inc(TWert2,3);
      end;
      inc(TWert3,4);
      inc(TWert4,5);
    end;
    dec(Wert1,4);            { Bewegung des Plasmas }
    inc(Wert3,4);
    inc(Wert1,random(1));    { Zufallswerte bewirken, da das Plasma }
    dec(Wert2,random(2));    { etwas unregelmig wird }
    inc(Wert3,random(1));
    dec(Wert4,random(2));
  until keypressed;
  readkey;
  asm mov ax,3; int 10h end;
end.

Wer nicht gerade einen schnellen 486 hat, wird wohl mit der Geschwindigkeit
nicht ganz zufrieden sein, deshalb jetzt nochmal die Repeat-Schleife in der
Assembler-Version:

  { Einfach die Repeat-Schleife aus dem oberen Listing lschen und diese
    Zeilen einfgen: }
  asm
    mov     ax,0A000h                  { VGA-Segment nach ES }
    mov     es,ax
  @mainloop:
    xor     di,di                      { Bildschirmoffset auf 0 }
    mov     al,Wert3                   { Werte sichern }
    mov     TWert3,al
    mov     al,Wert4
    mov     TWert4,al
    mov     n1,0                       { Zhler initialisieren }
  @loop1:
    mov     al,Wert1                   { Werte sichern }
    mov     TWert1,al
    mov     al,Wert2
    mov     TWert2,al
    mov     n2,0                       { Zhler initialisieren }
  @loop2:
    xor     bx,bx                      { Farbwert wird in BX berechnet }
    mov     al,TWert4                  { Index holen }
    xor     ah,ah
    mov     si,ax
    mov     al,[si+offset SinTab]      { Wert auslesen }
    add     bx,ax                      { und auf BX addieren }
    mov     al,TWert3
    mov     si,ax
    mov     al,[si+offset SinTab]
    add     bx,ax
    mov     al,TWert2
    mov     si,ax
    mov     al,[si+offset SinTab]
    add     bx,ax
    mov     al,TWert1
    mov     si,ax
    mov     al,[si+offset SinTab]
    add     bx,ax
    mov     es:[di],bl                 { Pixel setzen }
    inc     di                         { Bildschirmoffset erhhen }
    add     TWert1,4                   { Indizes erhhen }
    add     TWert2,3
    inc     n2
    cmp     n2,320
    jne     @loop2
    add     TWert3,4
    add     TWert4,5
    inc     n1
    cmp     n1,200
    jne     @loop1
    sub     Wert1,4
    add     Wert3,4
    mov     ah,0Bh
    int     21h
    or      al,al
    jz      @mainloop
  end;

Man kann nun das Aussehen des Plasmas verndern, indem man z.B. das Offset und
die Amplitude der Sinustabelle beim Aufruf von CalcSinus ndert.
Auerdem knnte man hier parallel noch eine Palettenrotation einbauen, das
kostet nicht viel Rechenzeit, sieht aber um so besser aus.
Dieses Beispiel ist natrlich nur eine von vielen Methoden, Sinus-Plasmas zu
coden. Es wurden schon einige hier im PCH abgedruckt, wenn ihr sie noch habt,
seht sie euch doch nochmal an, ihr erfahrt dort bestimmt noch einiges mehr.
(Hey, an dieser Stelle hat der VGA-Kurs das 100.000ste Byte erreicht!!!)

Dieser Teil des Kurses ist noch nicht zu Ende! Es folgt nun ein Bonusteil,
und zwar ber Fraktale, wie Onkel Joe es sich im PCH 3/96 gewnscht hat.
(Ich htte nichts gegen weitere Bonusteile, wenn euch also ein Randthema der
VGA-Programmierung interessiert, schreibt mir, und ich werde mal sehen, ob ich
dazu was schreiben kann.)
Jeder kennt sie, jeder ist fasziniert von ihnen: Fraktale. Doch wie werden
diese Dinger eigentlich berechnet?
Das funktioniert so hnlich wie bei der Berechnung der Colorcycle-Plasmas,
allerdings kommt man hier nicht um Real-Zahlen herum, was sich nicht unerheblich
auf die Berechnungsgeschwindigkeit auswirkt.
Wie beim Plasma (das brigens auch ein Fraktal ist) wird mit Iteration
gearbeitet, d.h. da die Werte, die im letzen Schleifendurchlauf berechnet
wurden, als Basis fr die neuen Berechnungen herangezogen werden.
Der folgende Source behandelt die beiden populrsten Fraktal-Typen, Mandelbrot
und Julia. Fr die beiden Typen ist jeweils eine Prozedur zustndig, die aus
den X- und Y-Koordinaten des Pixels den zugehrigen Farbwert des Fraktals
berechnet. Ihr knnt die Berechnungsweise aus den Prozeduren entnehmen, die
Erklrung dieser Formeln liegt leider auerhalb meines mathematischen
Wissensstandes.

{$N+,E+}
uses crt;
{ Diese Konstanten knnen verndert werden, um das Aussehen der Fraktale zu
  bestimmen. }
const Colors = 32;         { Anzahl der Farben des Fraktals }
      Width = 320;         { Breite des Fraktals }
      Height = 200;        { Hhe des Fraktals }
      Limit = 8.0;         { Berechnungsgrenze, regelt Schrfe }
      XRMin = -2.0;        { Linke Grenze des Fraktals }
      XRMax = 1.0;         { Rechte Grenze des Fraktals }
      YRMin = -1.3;        { Obere Grenze des Fraktals }
      YRMax = 1.3;         { Untere Grenze des Fraktals }

type real = double;        { Zur Optimierung der Fliekomma-Berechnungen }

var XPos,YPos : word;
    RealP,ImagP : real;
    CurrX,CurrY : real;
    a2,b2 : real;
    n : byte;

function CalcColorMandel(XPos,YPos:word) : byte;
begin
  CurrX := XPos / Width * (XRMax-XRMin) + XRMin;
  CurrY := YPos / Height * (YRMax-YRMin) + YRMin;
  RealP := 0;
  ImagP := 0;
  n := 0;
  repeat
    a2 := sqr(RealP);
    b2 := sqr(ImagP);
    ImagP := 2 * RealP * ImagP + CurrY;
    RealP := a2 - b2 + CurrX;
    inc(n);
  until (n >= Colors) or (a2+b2 >= Limit);
  CalcColorMandel := n - 1;
end;

function CalcColorJulia(XPos,YPos:word) : byte;
begin
  CurrX := 0.07;
  CurrY := -0.4;
  RealP := -2 + 0.5 + XPos / (Width/3);
  ImagP := 2 - 0.5 - YPos / (Height/3);
  n := 0;
  repeat
    a2 := sqr(ImagP) - sqr(RealP) + CurrX;
    b2 := 2 * RealP * ImagP + CurrY;
    RealP := a2;
    ImagP := b2;
    inc(n);
  until (n >= Colors) or (sqr(a2)+sqr(b2) > 4) or
        (abs(a2) > 2) or (abs(b2) > 2);
  CalcColorJulia := n - 1;
end;

begin
  asm mov ax,13h; int 10h end;   { In Mode 13h schalten }
  for YPos := 0 to Height-1 do   { Mandelbrot-Menge aufbauen }
    for XPos := 0 to Width-1 do
      mem[$A000:YPos*320+XPos] := CalcColorMandel(XPos,YPos);
  readkey;
  for YPos := 0 to Height-1 do   { Julia-Menge aufbauen }
    for XPos := 0 to Width-1 do
      mem[$A000:YPos*320+XPos] := CalcColorJulia(XPos,YPos);
  readkey;
  asm mov ax,3; int 10h end;     { Zurck zum Textmodus }
end.

So, wir sind mal wieder am Ende angelangt, und es folgt die Vorschau auf den
nchsten Teil: Ich will nicht zu viel versprechen, aber ich werde beim nchsten
mal mit 3D-Grafik beginnen. Dieses Thema ist wirklich nicht leicht anschaulich
zu machen, vor allem Leser ohne Mathekenntnisse (Geometrie!) werden einige
Probleme haben, aber ich werde es trotzdem versuchen. Also, bis bald!





[ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ]
[                                                                      ]
[ No  part   of  this   document  may  be   reproduced,   transmitted, ]
[ transcribed,  stored in a  retrieval system,  or translated into any ]
[ human or computer language, in any form or by any means; electronic, ]
[ mechanical,  magnetic,  optical,   chemical,  manual  or  otherwise, ]
[ without the expressed written permission of the author.              ]
[                                                                      ]
[ The information  contained in this text  is believed  to be correct. ]
[ The text is subject to change  without notice and does not represent ]
[ a commitment on the part of the author.                              ]
[ The author does not make a  warranty of any kind with regard to this ]
[ material, including,  but not limited to,  the implied warranties of ]
[ merchantability  and fitness  for a particular  purpose.  The author ]
[ shall not be liable for errors contained herein or for incidental or ]
[ consequential damages in connection with the furnishing, performance ]
[ or use of this material.                                             ]
