General Information
Author: Peter Finzel
Language: ACTION!
Compiler/Interpreter: ACTION!
Published: ATARI Magazin #1 (01/02-87), ACTION! Center 1


Blitzschnelle Vektoren#

Dieses erste Action!-Center befasst sich mit Grafikanimation auf den 8-Bit-Ataris.#

Obwohl die Softwareflut bei den 8-Bit-Ataris längst nicht so hohe Wellen wie bei Schneider-Computer oder dem C64 schlägt, gibt es doch einige "Software-Perlen", um die uns die Besitzer der obigen Computertypen mehr als beneiden. Eine davon ist die Programmiersprache Action!. Dies ist eine strukturierte Compilersprache nach Art von Pascal bzw. C, die zu purer 6502-Maschinensprche übersetzt wird und damit Laufzeiten erreich, die sonst nur Assembler-Programmen vorbehalten sind.

Dabei ist Action! so einfach zu programmieren wie Basic, nur eben ein wenig anders, da es sich um eine strukturierte Sprache handelt. Mit anderen Worten, es gibt kein GOTO, dafür aber eine Reihe von Verzweigungs- und Schleifenstrukturen, wodurch der Sprungbefehl mehr als überflüssig wird. Lassen Sie sich durch das Fehlen von Zeilennummern und dem Einrücken der Zeilen nicht abschrecken. Letzteres ist nur ein Stilmittel zur Hervorhebung der Struktur. Natürlich könnte man Action!-Programme auch in bester Basic-Manier schreiben (möglichst viel in einer Zeile). Doch bringt dies nicht; das kompilierte Programm wird weder kürzer noch schneller.

Wo soviel Licht ist, dar natürlich ein wenig Schatten auch nicht fehlen. Sicherlich der negativste Aspekt an Action! ist der hohe Anschaffungspreis, der immerhin in der Größenordung eines neuen 800XL liegt. Die Programme sollen nur mit eingestecktem Action!-Modul laufen, doch glücklicherweise haben Handbücher nicht immer recht, und es gibt eine Anzahl von Tricks, mit denen man Action!-Programme vom Modul unabhängig macht. Wer meine Assemblerecke in der Zeitschrift CK-Computer Kontakt (6-7/86) verfolgt hat, weiß bereits Bescheid.

Genug der Vorrede, es wird höchste Zeit, dass wir uns mit einem konkreten Beispiel beschäftigen. wie Sie nun wissen, ist Action! enorm schnell und daher genau die richtige Sprache zum Programmieren von flinker Grafik. Nein, wir wollen nicht schon wieder die Player-Missiles strapazieren, sondern diesmal Animation in hochauflösender Grafik produzieren. Genauer ausgedrückt wird es sich um Vektorgrafik handeln. Was versteht man nun darunter?

In normaler (Raster-) Grafik ist ein Objekt (Shape, Sprite, Bob usw.) aus einzelnen Punkten aufgebaut. Bei Vektorgrafik dagegen wird ausschließlich mit Linien Vektoren gezeichnet. Zur Definition eines Objekts gibt man die Anfangs- und Endpunkte aller dazu benötigten Linien an. Der enorme Vorteil liegt darin, dass Vektorobjekte praktisch stufenlos vergrößert und verdreht werden können, indem man die Koordinaten der Anfangs- und Endpunkte mathematisch umrechnet. Im Beispiel werden wir uns ein Programm näher ansehen, das solche Objekte zeichnen und vergrößern kann.

Wer Ab und zu in eine Spielhalle geht, kennst sicherlich "Star Wars" oder das beinahe schon historische "Asteroids"; beide arbeiten mit reiner Vektorgrafik. die Automaten verfügen über spezielle Hardware (Vektor-Displays), die nur auf diese Grafik ausgelegt sind.

So etwas steht uns im Atari zwar nicht zur Verfügung, doch können wir es leicht nachahmen. Man benötigt dazu nur hochauflösende Grafik und ein Programm, das sehr schnell Linien zeichnen kann. Beides ist für den Atari kein Problem, denn schließlich besitzt er den ANTIC-Chip für Grafik und Action! für flotte Programme.

Wir dürfen allerdings nicht den Action!-Befehl DRAWTO zum Linienzeichnen verwenden. Er arbeitet nämlich mit einer Routine des Betriebssystems, die auch von Basic benutzt wird und nicht gerade schnell ist. Wesentlich besser ist es, die DRAWTO-Routine selbst in Action! zur schreiben (im Programm ist das die Routine LineTo). Es ist kaum zu glauben, aber sie ist tatsächlich schneller als ihr in Assembler programmiertes Gegenstück im ROM des Betriebssystems.

Jetzt zu den Vektorobjekten. Ein solches wird im Programm in einem Byte-Array abgelegt. Das erste von je drei aufeinanderfolgenden Bytes bestimm, ob ein neuer Anfangspunkt festgelegt oder eine Linie gezogen werden soll. Das kann man sich bildlich so vorstellen, dass das Objekt auf kariertes Papier gezeichnet würde. Bei einer Null wird der Stift abgehoben und nur der neue Punkt angesteuert; eine Eins hingegen bedeutet, dass eine Linie vom letzten zum neuen Punkt gezogen wird. Ein Wert von $FF zeigt an, dass das Objekt fertig gezeichnet ist. Das zweite und dritte Byte geben die Koordinaten des neuen Punktes (zuerst X, dann Y) an.

Im Programm wurde as Beispiel das Atari-Logo (im Array Atari_L) abgelegt. Zum Entwurf eines Objektes zeichnet man es auf kariertes Papier und überträgt die Koordinaten nach der oben geschilderten Methode in ein Byte-Array. Beim Entwurf sollte man sich an einer Grüße von 10 x 12 Kästchen (Höhe x Breite) halten, da sonst die Mitte des Objektes nicht richtig berechnet wird (oder die DEFINEs Mitte_X und Mitte_Y ändern)

Die Prozedur Draw() zeichnet ein gesamtes Objekt in einen HiRes-Bildschirm. Sie können dabei noch im Parameter VERGR angeben, um wie viel das Objekt vergrößert oder verkleinert werden soll. Ein Wert von 0 bis 9 verkleinert. 10 bildet die Originalgröße ab, während es bei höheren Werden vergrößert wird. Daneben können noch Werte zur Verschiebung in horizontaler und vertikaler Richtung angegeben werden.

Animation kann mit der Routine Draw() erzeugt werden, indem man das Objekt zeichnet, dann den Bildschirm löscht und es an einem anderen Platz oder mit anderer Vergrößerung neu zeichnet. Der hässliche Nachteil dieser Methode ist nur, dass durch das dauernde Löschen und Neuzeichnen ein unruhiges und flimmerndes Bild entsteht, wodurch die Animation kaum mehr als solche erkennbar ist.

Daher wurde im Programmbeispiel zu einer List gegriffen. Man verwendet zwei Bildschirme, von denen einer zur sehen ist, während der andere gelöscht und neu gezeichnet wird. Die Umschaltung der beiden Bildschirme erledigt die Prozedur Switch_Screen(). Durch diese Technik Wird die Animation fließend.

Das Hauptprogramm nützt die Möglichkeiten der Draw()-Routine, um zwei Atari-Logos nebeneinander abwechselnd zu verkleinern und zu vergrößern. Der Effekt ist recht plastisch; je eines der beiden Fuji-Symbole scheint in der Tiefe des Raumes zu verschwinden, um danach wieder neu aufzutauchen.

Das Programm verwendet einige Tricks und Kniffe, die Sie auch in eigenen Programmen gewinnbringend einsetzten können. Da wäre zunächst die Definition einiger Variablen in der Zero-Page. Mit zwei SET-Anweisungen wird Action! instruiert, die Definition von row bis hin zu yf in der Zero-Page ab der Speicherzelle $F0 vorzunehmen. So kann Action! diese Variable (vor allem dem Array-Zeiger row) wesentlich schneller ansprechen. Nach ihrer Definition wird die Adresse des Objectcodes durch zwei weitere SET-Befehle auf die Adresse $7000 verlegt.

Mit den SET-Anweisungen kann man die Code-Erzeugung wie bei einem Assembler-Programm mit ORG (bzw. mit "*"=) steuern. Normalerweise würde das von Action! erzeugte Objektfile direkt nach dem Textfile abgelegt. Diese Methode würde sich beim vorliegenden Programm jedoch nicht anbieten, da eine Display-List definiert wird, die durch gewisse Einschränkungen des ANTIC-Bausteins keine 1 KByte-Grenze überschreiten darf. Legt man jedoch die Anfangsadresse fest auf $7000 und definieren die Display-List am Anfang des Programms wird dieses Problem vermieden.

Die Display-List wird einfach durch mehrere BYTE- und CARD-Definitionen erzeugt. Verwendet wird ein Modus, der GRAPHICS 6 entspricht (Auflösung 160 mal 96, zwei Farben). Natürlich könnte man in diesem Falle auch einen GRAPHICS-6-Aufruf benutzen, aber da wir später mit Page-Flipping arbeiten wollen, ist die erste Methode eleganter. Es genügt dann, der Variablen LMS einen anderen Wert zuweisen, um die Adresse des Videospeichers zu verändern.

Anschließend folgen einige Byte-Arrays, die das Objekt sowie eine Adresstabelle enthalten. Letztere ist für die Fast_Plot()-Routine nötig, damit die Adresse der Zeilenanfänge möglichst schnell herausgefunden werden können. Auch hier ein Trick. Anstatt ein großes CARD-Feld zu verwenden, werden LSBs und MSBs der Adressen in getrennten Byte-Feldern aufbewahrt. Auf diese Weise kann der Zugriff viel schneller erfolgen.

Nun folgt die Fast_Plot()-Routine, in der auch einige wirkungsvolle Tricks versteckt sind. Schreibt man nämlich zwischen Namen und Parameterklammer ein "=*", so verzichtet Action! darauf, die Parameter in lokale Variablen abzulegen. Man kann das dann selbst mit einem kleinen Codeblock erledigen. Die beiden Parameter werden aus dem X- und Y-Register in einen reservierten Zero-Page-Speicherbereich von Action! gebracht. Da es nun nicht mehr weiß, wo die beiden zu finden sind, legt man zwei adressierte Variablen (im vorliegenden Fall X und Y) darauf. Der indirekte Zugriff auf den Videospeicher geschieht über den Array-Zeiger row, der mittels Adresstabellen aus LSB und MSB zusammengesetzt wird.

Die LineTo()-Routine stammt aus dem Programm "View 3D" von Paul Chabot (Antic 6/85). Sie ist sehr schnell, da nur Additionen und Subtraktionen und die Fast_Plot()-Routine benutzt werden. die Prozedur Graphic_Init() aktiviert die Display-List und bereitet Adresstabelle und das Video-RAM vor, während Screen_Switch() zwischen den beiden Bildschirmen (die übrigens bei Adresse $8000 bzw. $8800 beginnen) hin- und herschalten kann.

Damit wären wir am Ende des ersten Action!-Centers angelangt. Ich hoffe, es hat Ihnen gefallen und Sie haben einige neue Anregungen bekommen. Im nächsten Heft werden wir besprechen, wie man Interrupts in Action! programmieren kann. Ich würde mich freuen, wenn Sie wieder mit von der Partie sind.

;************************************
;      VEKTORGRAPHIK IN ACTION!
;
;P. FINZEL                       1986
;************************************

DEFINE VRAM1  ="$8000", ;Screen 1
       VRAM2  ="$8800", ;Screen 1
       VRLen  ="1920",  ;Laenge Screen
       VMax   ="40",    ;max. Vergroesserung
       Mitte_X="6",     ;Mitte des
       Mitte_Y="5",     ;Objektes
       MODE   ="$B$B$B$B$B"
;
;Zero-Page Variable
;
SET $E=$F0 SET $F=0
BYTE ARRAY row
BYTE rowl=row, rowh=row+1
BYTE t,a,b,Xnow,Ynow
BYTE dx,dy,xf, yf
;
;Programm ab $7000 ablegen
;=========================
;
SET $E  =$7000
SET $491=$7000
;
;
;Variablen und Daten
;===================
;
CARD dlist =560 ;Display-List Zeiger
BYTE color0=708 ;Schattenreg. Farbe 1
;
CARD Wrk  =[ $8000 ] ;Zeiger auf bearbeiteten
BYTE Wrkh = Wrk+1    ;Screen (Wrkh int MSB)
BYTE scr  =[0]       ;momentaner Screen

;------------------------------------
; Die Display-List:
;------------------------------------
BYTE DLST0=[ $70 $70 $70 $4B ]
CARD LMS  =[ $8000 ]
BYTE DLST1=[ MODE MODE MODE MODE
             MODE MODE MODE MODE
             MODE MODE MODE MODE
             MODE MODE MODE MODE
             MODE MODE MODE $41 ]
CARD DJMP =[ 0 ]

;------------------------------------
; Adresstabelle
;------------------------------------
BYTE ARRAY
  adrl(96),adrh(96),
mask8(0)=[128 64 32 16 8 4 2 1]

;------------------------------------
;Objekt in 10x12 Raster:
;------------------------------------
BYTE ARRAY ATARI_L=[
0  3  0:1  4  0:1 4 5:1 1 10:1 0 10
1  3  5:1  3  0:0 5 0:1 7  0:1 7 10
1  5 10:1  5  0:0 8 0:1 9  0:1 9  5
1 12 10:1 11 10:1 8 5:1 8  0:$FF$0$0]
;
;------------------------------------
; Gaphikpunkt setzen
;------------------------------------
PROC Fast_Plot=*(BYTE x1,y1)
  BYTE X=$A0, Y=$A1
  BYTE xb=$A2, Xr=$A3

  [ $85 $A0 $86 $A1 ]

  IF Y<96 THEN
      rowl=adrl(y)
      rowh=adrh(y)+wrkh
      xb=x RSH 3:xr=x AND 7
      row(xb)== % mask8(xr)
  FI
RETURN

;------------------------------------
;Graphik-Linie ziehen
;------------------------------------
PROC LineTo(BYTE x,y)
BYTE i
Fast_Plot(xnow,ynow)
IF x=xnow AND y=ynow THEN RETURN FI
IF x>xnow THEN
       dx=x-xnow:xf=1
ELSE
       dx=xnow-x:xf=$FF
FI
IF y>ynow THEN
       dy=y-ynow:yf=1
ELSE
       dy=ynow-y:yf=$FF

FI
x=xnow:y=ynow
IF dx>dy THEN
  a=dy+dy:t=a-dx:b=t-dx
  FOR i=1 TO dx
    DO
    x==+xf
    IF t>127 THEN
       t==+a
    ELSE
       t==+b:y==+yf
    FI
    Fast_Plot(x,y)
  OD
ELSE
  a=dx+dx:t=a-dy:b=t-dy
  FOR i=1 TO dy
    DO
    y==+yf
    IF t>127 THEN
       t==+a
    ELSE
       t==+b:x==+xf
    FI
    Fast_Plot(x,y)
  OD
FI
xnow=x:ynow=y
RETURN

;------------------------------------
;Page-Flipping: Screen wechseln
;------------------------------------
PROC Switch_Screen=*()
  IF scr=0 THEN
       Lms=Vram2
       Wrk=Vram1
  ELSE
       Lms=Vram1
       Wrk=Vram2
  FI
  scr==+1&1
  Zero(Wrk,VRLen)
RETURN

;------------------------------------
;Display-List & Adresstabelle anlegen
;------------------------------------
PROC Graphic_Init()
  BYTE i

  row=0
  FOR i=0 TO 95
    DO
    adrl(i)=rowl
    adrh(i)=rowh
    row==+20
  OD
  Zero(Vram1,Vrlen)
  Zero(Vram2,Vrlen)
  scr=0        
  Lms=Vram1
  Wrk=Vram2
  DJMP=@Dlst0
  Dlist=@Dlst0
RETURN

;------------------------------------
;Graphik-Koerper zeichnen
;------------------------------------
PROC Draw(BYTE ARRAY Def,BYTE vergr,
                         INT xrel,yrel)
BYTE i         
INT X,Y

IF Vergr=0 THEN RETURN FI
i=0
WHILE Def(i)<>$FF
       DO
       X=Def(i+1) X==+xrel-Mitte_X
       Y=Def(i+2) Y==+yrel-Mitte_Y
       X==*Vergr/10+79
       Y==*Vergr/10+48
       IF Def(i)=0 THEN
               Xnow=X Ynow=Y
       ELSE
               LineTo(X,Y)
       FI
       i==+3
       OD
RETURN       

;------------------------------------
;Das Hauptprogramm
;------------------------------------
PROC Vektorgraphik()
  BYTE i
  Graphic_Init()
  DO
    FOR i=0 TO VMax STEP 3
      DO
      DRAW(ATARI_L,i,7,0)
      DRAW(ATARI_L,Vmax-i,0-7,0)
      Switch_Screen()
    OD
    FOR i=0 TO VMax STEP 3
      DO
      DRAW(ATARI_L,Vmax-i,7,0)
      DRAW(ATARI_L,i,0-7,0)
      Switch_Screen()
    OD
  OD
RETURN

PDF: Schnelle Vektoren in ACTION/vektoreninaction.PDF(info)