← ST-Magazin 11 / 1988

Auf den Spuren der 3. Dimension

ST-Grafik

Das Programmieren dreidimensionaler Grafik ist nicht schwierig. Selbst ohne Bestnoten in Mathematik bewältigen auch Sie den Einstieg in die faszinierende Welt perspektivischer Grafik. Frank Mathy, Autor des Bestsellers »Programmierung von Grafik und Sound auf dem Atari ST«, erläutert die wichtigsten Darstellungs-Techniken und illustriert sie anhand eines Omikron-Basic-Programms zum Abtippen.

Nur Mut, die Welt der 3D-Grafik ist wirklich nicht so unnahbar, wie Sie vielleicht befürchten. Nicht nur mit theoretischem Wissen, sondern auch mit praktischen Beispielen anhand unseres 3D-Programmes wollen wir Ihnen deshalb dienen. Kernstück des Artikels ist unser Programm in Omikron Basic 2.0 für den Schwarzweiß-Monitor, das die wichtigsten 3D-Operationen beherrscht.

Das Programm erlaubt, beliebige dreidimensionale Objekte als Drahtmodell mit versteckten Linien oder schattiert darzustellen. Außerdem stehen drei Operationen zur Manipulation der Körper zur Verfügung.

Zunächst wollen wir den grundlegenden Unterschied zwischen »2D« und »3D« erklären.

»3D« steht für »dreidimensional« oder »in drei Dimensionen«. Eine 3D-Bildinformation umfaßt also Daten für alle drei Dimensionen Höhe, Breite und Tiefe. Wohlgemerkt ist nur die Bildinformation dreidimensional. Das später auf dem Bildschirm dargestellte Bild vermittelt einen 3D-Effekt, ist jedoch nur zweidimensional, da der Bildschirm flach ist. Die Tiefendimension fehlt.

»2D« steht entsprechend für »zweidimensional« oder »in zwei Dimensionen«. Die 2D-Bildinformation besteht deshalb nur aus den Informationen für die beiden Dimensionen Höhe und Breite. Alle gängigen Zeichenprogramme arbeiten in zwei Dimensionen. Wieso gibt es aber keine Malprogramme in 3D?

Der Grund liegt in der Art der Informations-Darstellung: Zeichenprogramme arbeiten in der Regel pixelorientiert. Der zweidimensionale Arbeitsbereich, das Raster, wird in eine endliche Zahl kleiner Einheiten, der Pixel oder Bildpunkte, unterteilt. Für jeden dieser Bildpunkte wird nun ein Status- oder Farbwert im Speicher abgelegt, der dessen Farbe definiert. Würden Sie dieses Feld um eine dritte Dimension erweitern, so käme es zu einer Vervielfachung des Speicher- und Rechenbedarfs. Deshalb müssen wir die Daten bei 3D-Program-men anders ablegen. Das geschieht in Form von Objekten. Hier legt der ST also nicht für jeden Punkt des 3D-Universums eine Farbinformation oder ähnliches ab, sondern wir definieren Körper, die sich in diesem Raum befinden.

In der Regel besteht ein dreidimensionales Objekt aus einer Gruppe verschiedener Flächen. Die Flächen sind jeweils durch eine Zahl von Eckpunkten definiert. Für jeden Eckpunkt muß der ST die genaue Lage festhalten. Hierfür legen Sie mit den X-, Y- und Z-Koordinaten die Position des Punktes im dreidimensionalen Koordinatensystem fest.

Wertesystem in 3D

Ein zweidimensionales Koordinatensystem beschreibt einen Punkt durch die horizontale und vertikale Position, repräsentiert durch die x- und y-Koordinate. Schwierigkeiten gibt es bei dem dreidimensionalen Koordinatensystem, da unsere Darstellungsmedien Bildschirm oder Papier nur zweidimensional sind. Für die dritte Dimension benötigen wir neben den horizontalen und vertikalen Achsen eine Tiefenachse, die quasi in den Bildschirm hineingeht.

Dieses Darstellungsproblem läßt sich auf verschiedene Weisen umgehen. Wir wollen es aber erst einmal bei der gängigsten Lösung belassen: das dreidimensionale kartesische Koordinatensystem nach dem Rechtssystem. Die x- und y-Achsen verlaufen auch hier rechtwinklig, die z-Achse hingegen diagonal zwischen den x- und y-Achsen im ersten Quadranten (rechtes oberes Viertel des Koordinatensystems) diagonal nach rechts oben.

Da wir nun mit drei Achsen arbeiten, bestimmen drei Komponenten in der Form P(x/y/z) einen Punkt. Möchten Sie beispielsweise den Punkt P(2/3/4) in das Koordinatensystem eintragen, so bewegen Sie sich zunächst um zwei Einheiten nach rechts, um drei Einheiten nach oben und von diesem Punkt aus um vier Einheiten diagonal nach rechts oben. Bei dem letzten Schritt bewegen Sie sich um 4/2 Einheiten nach rechts und um dieselbe Distanz nach oben, woraus sich der diagonale Schritt mit einer Länge von 2 ergibt. Ist die z-Koordinate negativ, so müssen wir diagonal nach links unten wandern. Der Mittelpunkt des 3D-Universums ist der Ursprung des Koordinatensystems mit den Koordinaten (0/0/0).

Unser Programm in Omikron Basic beherrscht die wichtigsten 3D-Darstellungs-Techniken

Beliebige 3D-Körper lassen sich als Drahtmodell, »Hidden-Line«-Darstellung und schattiert berechnen

Die Menüführung erlaubt die Betrachtung aus jeder Position mit beliebigem Lichteinfall

Das kartesische Koordinatensystem besitzt neben der x- und y-Achse auch noch eine Tiefenachse z

Auch die Darstellung der Zentralperspektive läßt sich mit dem ST realisieren. B ist der Betrachter, M die Mattscheibe und K der Körper

Stellen wir auf diese Weise zwei in z-Richtung versetzte Quadrate dar, so erscheint das Quadrat mit der größeren z-Koordinate diagonal nach rechts oben versetzt. Hierdurch entsteht dann ein dreidimensionaler Effekt, der einer Sicht von rechts vorne auf das Objekt gleicht. Soviel zum Koordinatensystem. Fassen wir noch einmal die für die Objekte notwendigen Daten zusammen:

  • Zahl der Objekte
  • Für jedes Objekt:
  • Zahl der Flächen
  • Falls gewünscht: Farbinformation, Positionsangabe
  • Flächendaten:
  • Zahl der Eckpunkte
  • x-, y- und z-Koordinaten der Eckpunkte

Unser 3D-Programm verwaltet in der momentanen Version nur ein Objekt, für das es eine Positionsangabe, aber keine Farbinformation ablegt. In dem Programm befinden sich die Daten für ein einfaches Haus in den DATA-Zeilen 3510 bis 3940. Zeile 3510gibt über die Zahl der Flächen des Objektes Auskunft, darauf folgen die Pakete mit der Anzahl der Eckenpunkte (ab Zeile 3510), sowie den x-, y- und z-Koordinaten (Zeilen 3530 bis 3560).

Die Prozedur »Read_Data« (Zeilen 3390 bis 3490) übernimmt das Einlesen dieser Daten, wobei das Programm die Eckenzahl im Feld Ecken (i) und die Koordinaten in den Feldern x (i,j), y (i,j) und z (i,j) ablegt. Bei der Gitterdarstellung zieht das Programm automatisch eine Linie vom letzten zum ersten Punkt des Vielecks. Für ein Dreieck müssen Sie deshalb nur die drei Eckpunkte angeben.

Selbstverständlich lassen sich andere dreidimensionale Körper nach diesem Muster abgelegen. Zum Entwurf eignet sich das eben beschriebene Koordinatensystem sehr gut.

Die Zentralperspektive

Wie stellen wir nun unsere im Speicher abgelegten Informationen effektvoll dar? Unser kartesisches Rechtssystem liefert leider keinen guten 3D-Effekt. Deshalb orientieren wir uns an den üblichen Sehgewohnheiten.

Wie schätzen wir im Alltag die Entfernung eines Objektes ein? Zunächst hilft uns die stereoskopische Sehfähigkeit der menschlichen Augen, indem wir die leicht versetzten Bilder unserer Augen zu einem plastischen Bild verarbeiten. Halten wir uns ein Auge zu, so können wir immer noch die Entfernung eines Körpers abschätzen, indem wir dessen Erscheinungsgröße betrachten. Je weiter entfernt ein Körper ist, desto kleiner erscheint er. Diese Darstellungsform in der sogenannten Zentralperspektive (alle Punkte laufen auf einen zentralen Punkt zu) wird auch bei 3D-Computerspielen wie »Starglider« angewendet, um einen Tiefeneffekt zu simulieren. Wie läßt sich die Darstellung der Zentralperspektive aber mit dem ST realisieren?

Zur Berechnung des direkten Abstandes zwischen einer Fläche und dem Auge hilft uns der Satz des Pythagoras weiter

Betrachten Sie zunächst unsere Abbildung. Hier sehen Sie einen Blick von oben auf das Geschehen. Am Punkt »B« befindet sich der Betrachter, »K« markiert den betrachteten Körper und »M« die Mattscheibe (den Bildschirm), auf den das Bild projiziert wird. Durch den Standpunkt des Betrachters B verläuft die z-Achse, die genau senkrecht auf die Mattscheibe M trifft. Der Körper K befindet sich auf der anderen Seite der Mattscheibe etwas in x-Richtung versetzt und besitzt die x-Koordinate xK.

Die z-Koordinate des Betrachters ist zB, die der Mattscheibe 0 und die des Körpers zK. Den Abstand eines Punktes vom anderen berechnen Sie durch Subtraktion der Koordinaten. Der Abstand zwischen Mattscheibe und Auge beträgt deshalb 0-zB (Augenabstand), der zwischen Körper und Auge zK-zB.

Nun wollen wir ermitteln, welche x-Koordinate der Körper bei der Projektion auf die Mattscheibe M besitzt, die wir xM nennen. Wie können wir dies berechnen?

Da hier zwei ähnliche Dreiecke vorliegen, hilft uns ein grundlegender Satz aus der Geometrie: der Strahlensatz. Der Strahlensatz besagt in diesem Fall, daß die Seitenverhältnisse der beiden Dreiecke übereinstimmen:

xM/-zB = xk/(zk-Zb)

Der Term läßt sich nach xM umstellen, so daß wir folgende Gleichung erhalten:

xM = -(xK*zB)/(zK-zB)

Setzen wir nun die uns bekannten Variablen in die rechte Seite ein, so erhalten wir die x-Koordinate auf der Mattscheibe. Über die gleiche, leicht abgewandelte Formel erhalten wir auch die y-Koordinate:

yM = -(yK*zB)/(zK-zB)

Wie Sie sehen, ist das sichtbare Resultat sehr von der Position des Betrachters abhängig. Je näher Sie sich am Bildschirm befinden, desto stürzender sind die Linien, desto extremer erscheint die Zentralperspektive. Für normale Fälle hat sich eine »Augen-z-Koordinate« von -450 bewährt. Das Programm rechnet die 3D-Koordinaten mit der Prozedur »Projektion« (Zeilen 3320 bis 3380) in Bildschirmkoordinaten um. Sie gibt die fertig berechneten Bildschirmkoordinaten anschließend zurück.

Die Praxis der 3D-Grafik

Zeile 3350 ermittelt zunächst den Abstand zwischen Auge A und Körper K, die beiden Folgezeilen berechnen dann die Bildschirmkoordinaten. Hierbei addiert das Programm 320 beziehungsweise 200, da die Mitte des Bildschirms der Ursprung sein soll. Bei der y-Koordinate subtrahiert das Programm das ermittelte yM von 200, da die y-Achse auf dem Bildschirm von oben nach unten ausgerichtet ist (y=0 steht für den oberen Bildschirmrand). Im kartesischen Koordinatensystem ist die Ausrichtung hingegen umgekehrt.

Die Darstellung der einzelnen Teile des Gittermodells erfolgt in der »Darstellen«-Routine (Zeilen 3060 bis 3300). Die Routine übernimmt, abhängig vom Wert der Variablen »Bildmodus«, das Darstellen des Körpers als Gittermodell (0), mit versteckten Linien (1) oder schattiert (2). Der Gittermodell-Modus löscht zunächst den Bildschirmteil, worauf das Programm für jedes Polygon die Bildschirmkoordinaten berechnet (Zeile 3230) und dieses als durchsichtiges Polygon mit schwarzem Rand (Zeilen 3250 bis 3280) zeichnet.

Der Hidden-Line-Modus

Die Darstellung der Grafik als Gittermodell wirkt nicht sehr realistisch. Besonders das Durchscheinen der hinteren Konstruktionslinien stört. Wie lassen sich diese, eigentlich verdeckten Linien entfernen?

Für die Lösung dieses Problems gibt es viele, teilweise sehr komplizierte Ansätze. Wir lernen hier nur die einfachste Methode kennen, die allerdings nicht immer völlig fehlerfrei arbeitet.

Das Prinzip sieht folgendermaßen aus: Wir sortieren die Flächen nach ihrem Abstand vom Betrachter in absteigender Reihenfolge, was das Programm mit der Prozedur »Sortpoly« (Zeilen 2890 bis 3040) durchführt. Danach stellen wir die Flächen in der sortierten Reihenfolge auf dem Bildschirm dar. Dabei erscheinen zunächst die weiter entfernten und dann die näherliegenden Flächen. Hierzu verwenden wir die Polygon-Routine, die das Vieleck weiß füllt und schwarz umrandet. Die weiße Füllung löscht die verdeckten, dahinterliegenden Polygone.

Wie können wir nun den Abstand einer Fläche vom Auge ermitteln? Zunächst berechnen wir die einzelnen Abstände der Fläche vom Auge in x-, y- und z-Richtung, indem wir die x-, y- und z-Abstände sämtlicher Eckpunkte miteinander addieren (Zeilen 2940 bis 2990) und durch die Zahl der Ecken dividieren (Zeile 3000).

Pythagoras läßt grüßen

Der direkte Abstand läßt sich nun über den Satz des Pythagoras ermitteln, der Ihnen im zweidimensionalen Raum sicher bekannt ist. Unsere Grafik zeigt, wie sich der Abstand zwischen Ursprung und einem Bildpunkt berechnen läßt. Die x-Komponente a und die y-Komponente b bilden mit der Verbindungsgeraden d zwischen Ursprung und Bildpunkt ein rechtwinkliges Dreieck. Hier können wir also den Satz des Pythagoras anwenden: a2+b2=d2 oder d=SQR(a2+b2)

Derselbe Satz gilt auch in leicht abgewandelter Form im dreidimensionalen Raum. Hier kommt die z-Komponente c hinzu:

a2+b2+c2=d2 oder d=SQR(a2+b2+c2)

Diese Formel verwenden wir in Zeile 3010 zur Berechnung des Abstands. Zuvor erzeugten wir in Zeile 2930 ein Indexfeld mit den Nummern der Flächen namens »Prioritaet(i)«. Es wird in Zeile 3030 zusammen mit dem Dist(i)-Feld sortiert. Somit haben wir nach dem Sortiervorgang im Indexfeld Prioritaet(i) die Nummern der Flächen in der genau umgekehrten Reihenfolge vorliegen, in der sie auf den Bildschirm gebracht werden.

Beim Zeichenvorgang setzen wir die Parameter von Zeile 3120 bis 3130 so, daß das Programm eine weiße Fläche mit schwarzem Rand zeichnet. Nach dem Sortieren in Zeile 3180 wählt das Programm das Polynom aus (Zeile 3210) und zeichnet es anschließend (Zeilen 3250 bis 3280). Somit erhalten wir eine Darstellung mit versteckten Linien (»Hidden Line«-Modus).

Die Darstellung der Grafik wirkt noch realistischer, wenn die einzelnen Flächen gefüllt und schattiert sind. Die größte Schwierigkeit bereitet die Berechnung der Helligkeiten der Polygone. Die Mathematik hierfür entstammt der Vektorrechnung.

Schattierte Darstellung

An dieser Stelle wollen wir das Prinzip nur kurz erläutern. Unser Programm ist in der Lage, parallel verlaufende Lichtstrahlen aus einer Richtung zu simulieren. Dazu muß das Programm wissen, in welche Richtung das Licht strahlt. Wir geben deshalb einen Punkt an, den der durch den Ursprung des Koordinatensystems verlaufende Lichtstrahl kreuzt. Dies geschieht über die Prozedur »Set-light« (Zeilen 2830 bis 2870). Die Prozedur berechnet auch gleich die Länge des angegebenen Lichtvektors in Zeile 2860.

Ein Vektor ist eine Gerade von einer bestimmten Länge mit einer bestimmten Ausrichtung und wird im dreidimensionalen Raum durch ein Koordinatentripel (Dreierpaar aus x-, y- und z-Koordinaten) definiert.

Möchten Sie den Vektor von einem Punkt a zu einem Punkt b ermitteln, so subtrahieren Sie die Koordinaten von Punkt a von denen von Punkt b:

xV=xB-xA, yV=yB-yA und zV = zB-zA.

Zwei Vektoren, die von einem gemeinsamen Punkt ausgehen, spannen eine Ebene auf und definieren eine Fläche. Diese Vektoren heißen deshalb Spannvektoren.

Zum Ermitteln der Helligkeit einer Fläche müssen wir zunächst das Lot auf die Fläche bestimmen, also den Vektor, der senkrecht auf der Fläche steht, auch Normalvektor genannt. Hierzu berechnen wir zunächst die beiden Spannvektoren. Im Beispielprogramm erfolgt dies in den Zeilen 2650 bis 2730. Nun bilden wir k aus diesen beiden Spannvektoren das Vektor-Kreuzprodukt (Zeilen 2740 bis 2760), dessen Resultat wiederum ein Vektor, nämlich der Lotvektor, ist. Nun berechnen wir den Cosinus des zwischen Lichtvektor (Lix/Liy/Liz) und Normalvektor (Nx/Ny/Nz) eingeschlossenen Winkels. »Lilen« und »Nlen« geben die Länge der beiden Vektoren an. Der Cosinus wird in Zeile 2780 nach folgender Formel ermittelt:

Cosinusv = ((NxLix + NyLiy + NzLiz)/(NlenLilen)).

Dieser Wert wird in derselben Zeile noch quadriert, da die Helligkeit der Fläche proportional zum Quadrat des Cosinus des zwischen Normalen- und Lichtvektor eingeschlossenen Winkels ist. Zeile 2790 ermittelt nun der Wert für das Füllmuster einer entsprechenden Helligkeit und legt ihn im Feld »Hell(i)« ab.

Die Darstellungsroutine bestimmt auch hier zunächst die Zeichenfolge der Polygone (Zeile 3180). Anschließend zeichnet sie die Polygone in der angegebenen Reihenfolge und verwendet dabei die im »Hell(i)«-Feld abgelegten Füllmusterwerte (Zeile 3270).

Kommen wir nun zu den unterschiedlichen Methoden, mit denen sich die 3D-Körper manipulieren lassen. Die einfachste Funktion ist das Verschieben eines Körpers. Sie läßt sich durch das Addieren oder Subtrahieren des Verschiebe-Wertes zu sämtlichen Koordinaten erreichen. Wollen wir den Körper beispielsweise weit nach links, etwas nach oben und weiter weg bewegen, so führen wir folgende Additionen durch: xK=xK-70, yK=yK-20 und zK=zK + 100. Die Prozedur »Translation« ist im Programm für das Verschieben zuständig (Zeilen 1920 bis 2030). Sie merkt sich auch die aktuelle Position mit den Variablen »xpos«, »ypos« und »zpos«. Sie sind für die Rotations- und Skalierungs-Operationen wichtig.

Möchten Sie die Größe eines Objektes verändern, müssen Sie eine Skalierungs-Operation durchführen. Hierzu multiplizieren Sie die x-, y- und z-Koordinaten mit festzusetzenden Faktoren, die sich durchaus unterscheiden dürfen. Möchten Sie beispielsweise das Objekt doppelt so breit, halb so hoch und dreimal so tief wie bisher haben, so führen Sie folgende Operationen durch: xk=xk2, yK=yK0.5 und zK=zK*3.

Für eine Punktspiegelung am Ursprung der jeweiligen Achse verwenden Sie einfach einen negativen Skalierungsfaktor. Dies macht sich bei den Daten des Beispielprogramms nicht bemerkbar, da das Haus symmetrisch ist.

Die Routine »Skalierung« zwischen den Zeilen 2050 bis 2140 subtrahiert und addiert vor der Multiplikation jeweils die Objektpositions-Variable xpos ypos beziehungsweise zpos, damit sich das Objekt weiterhin mit seinem Mittelpunkt an der gleichen Position befindet.

Zum Abschluß wollen wir noch die Rotation von Körpern ansprechen. Hierfür verwenden wir die Winkelfunktionen Sinus und Cosinus, wobei wir die Regeln im Rahmen dieses Artikels leider nicht herleiten können. Ein Buch zur Vektorrechnung hilft hier weiter.

Für die Rotation um die drei verschiedenen Achsen besitzt das Programm drei verschiedene Routinen, die im Prinzip gleich arbeiten. Die Routine subtrahiert zunächst von jeder Koordinate die Positionsvariable xpos, ypos und zpos, damit die Rotation auch dann um den Mittelpunkt der Körpers erfolgt, wenn dieser zuvor verschoben wurde. Nun erfolgt die Rotation, wobei wir zunächst das Beispiel einer Rotation um die x-Achse betrachten (Zeilen 2150 bis 2290). Hier bleiben die x-Werte erhalten, während sich die y- und z-Koordinaten folgendermaßen ändern:

yNEU = Cosinus(Winkel) * yALT - Sinus (Winkel) * zALT

zNEU = Sinus(Winkel) * yALT + Cosinus (Winkel) * zALT

Anschließend werden die Variablen xpos, ypos und zpos wieder addiert, damit das Objekt seine Ausgangsposition erreicht. Außerdem berechnet die Routine die Helligkeitswerte der Flächen in Zeile 2280 neu, da sich der Lichteinfall mit der Rotation geändert hat.

Bei einer Rotation um die y-Achse (Zeilen 2300 bis 2440) ändern sich die x- und z-Koordinaten:

xNEU= Cosinus(Winkel) * xALT - Sinus (Winkel) * zALT

zNEU = Sinus(Winkel) * xALT + Cosinus (Winkel) * zALT

Wollen Sie Ihren Körper um die z-Achse drehen (Zeilen 2450 bsi 2590), so kommen folgende Rechenregeln zum Einsatz:

Xneu= Cosinus(Winkel) * xALT - Sinus (Winkel) * yALX

yNEU = Sinus(Winkel) * xALT + Cosinus (Winkel) * yALT

Hier ändern wir nur die x- und y-Koordinaten.

Abschließend wünschen wir Ihnen viel Spaß beim Experimentieren und hoffen, daß Ihnen diese Einführung den Einstieg in die faszinierende Welt der 3D-Grafik erleichtert. (am)

 998 ' ######################################################
 999 ' »Auf den Spuren der 3. Dimension«                    #
1000 ' 3D-Grafikdemo in Omikron-Basic für ST-Magazin        #
1010 ' (c) Frank Mathy/ST-Magazin, November 1988            #
1020 ' Darstellen von dreidimensionalen Objekten            #
1030 ' als Gittermodell, mit Hidden Line oder schattiert    #
1040 '#######################################################
1041
1042
1050 CLS : XBIOS (Res!,4): IF Res!<2 THEN FORM_ALERT
(1,"[3][ | Läuft nur in der | Monochrom-Auflösung... ][ OK ]"): END 
1060 Appl_Init:V_Opnvwk:Bildmodus!=2 
1070 DRAW 0,366 TO 639,366 
1080 DEFSNG "A-Z": RAD
1090 Maxecken!=6: RESTORE Daten: READ Maxpoly!
1100 Xpos!=Ypos!=Zpos!=0:Auge!=-450 
1110 DIM X!(Maxpoly!,Maxecken!)
1120 DIM Y!(Maxpoly!,Maxecken!)
1130 DIM Z!(Maxpoly!,Maxecken!)
1140 DIM Dist!(Maxpoly!),Prioritaet!(Maxpoly!),Ecken!(Maxpoly!), Heil!(Maxpoly!)
1150 Read_Data:Setlight(1,1,2)
1160 Skalierung(2,2,2):Yrotation(-65):Xrotation(-20)
1170 Ron$= CHR$(27)+"p":Roff$= CHR$(27)+"q"
1180 PRINT @(24,6);Ron$+"A"+Roff$+"uge   ";"
1190 PRINT Ron$+"L"+Roff$+"icht   ";
1200 PRINT Ron$+"M"+Roff$+"odus    ";
1210 PRINT Ron$+"R"+Roff$+"otation      ";
1220 PRINT Ron$+"S"+Roff$+"kalierung     ";
1230 PRINT Ron$+"T"+Roff$+"ranslation   ";
1240 PRINT Ron$+"Q"+Roff$+"uit";
1250 Darstellen 
1260 REPEAT
1270 	PRINT @(23.6);"Bitte wählen..."
1280 	PRINT @(23,23);" ";
1290 	K$= INKEY$ : IF K$="" THEN GOTO 1290 
1300 	K$= LOWER$(MID$(K$,4,1))
1310 	IF K$="m" THEN
1320 		PRINT @(23,6);Ron$+"D"+Roff$+"rahtgitter     "; 
1330 		PRINT Ron$+"H"+Roff$+"idden Line     ";
1340 		PRINT Ron$+"S"+Roff$+"chattiert...";
1350 		S$= INKEY$ : IF S$=" " THEN GOTO 1350
1360 		S$= LOWER$( MID$(S$,4,1))
1370 		IF S$="d" AND Bildmodus!<>0 THEN Bildmodus!=0 :Darstellen
1380 		IF S$="h" AND Bildmodus!<>1 THEN Bildmodus!=1 :Darstellen
1390 		IF S$="s" AND Bildmodus!<>2 THEN Bildmodus!=2 :Darstellen 
1400 	ENDIF 
1410 	IF K$="r" THEN
1420 		PRINT @(23,6):Ron$+"X/x-"+Roff$+". ";
1430 		PRINT Ron$+"Y/y-"+Roff$+", "+Ron$+"Z/z"+Roff$+"-Achse oder ";
1440 		PRINT Ron$+"Q"+Roff$+"uit";
1450 		REPEAT
1460 			S$= INKEY$ : IF S$="" THEN GOTO 1460
1470 			S$= MID$(S$,4,1)
1480 			IF S$="X" THEN Xrotation(10):Darstellen 
1490 			IF S$="x" THEN Xrotation(-10):Darstellen
1500 			IF S$="Y" THEN Yrotation(10):Darstellen 
1510 			IF S$="y" THEN Yrotation(-10):Darstellen
1520 			IF S$="Z" THEN Zrotation(10):Darstellen
1530 			IF S$="z" THEN Zrotation(-10):Darstellen
1540 		UNTIL LOWER$(S$)="q"
1550 	ENDIF
1560 	IF K$="1" THEN
1570 		Lix$= STR$(Lix!): IF Lix!>=0 THEN Lix$= MID$(Lix$,2)
1580 		Liy$= STR$(Liy!): IF Liy!>=0 THEN Liy$= MID$(Liy$,2)
1590 		Liz$= STR$(Liz!): IF Liz!>=0 THEN Liz$= MID$(Liz$,2)
1600 		INPUT @(23,6);"X-Lichtkomponente: ";Lix$ USING "0+-";Taste!,2 
1610 		INPUT @(23,6);"Y-Lichtkomponente: ";Liy$ USING "0+-",Taste!,2 
1620 		INPUT @(23,6);"Z-Lichtkomponente: ";Liz$ USING "0+-",Taste!,2 
1630 		Setlight( VAL(Lix$), VAL(Liy$),VAL(Liz$))
1640 		IF Lilen!=0 THEN GOTO 1600
1650 		Shade:Darstellen
1660 	ENDIF
1670 	IF K$="a" THEN
1680 		Auge$= MID$( STR$(-Auge!).2)
1690 		INPUT @(23,6);"Augen-Z-Koordinate: -";Auge$ USING "0",Taste!,3 
1700 		IF VAL(Auge$)<50 THEN GOTO 1690 
1710 		Auge!=- VAL(Auge$)
1720 		Darstellen
1730 	ENDIF
1740 	IF K$="t" THEN
1750 		Xt$="0":Yt$="0":Zt$="0"
1760 		INPUT @(23.6);"X-Translation: ";Xt$ USING "0+-",Taste!,4 
1770 		INPUT @(23,6);"Y-Translation: ";Yt$ USING "0+-",Taste!,4 
1780 		INPUT @(23,6);"Z-Translation: ";Zt$ USING "0+-",Taste!,4 
1790 		Translation( VAL(Xt$), VAL(Yt$), VAL(Zt$))
1800 		Darstellen
1810 	ENDIF
1820 	IF K$="s" THEN
1830 		Xs$="1":Ys$="1":Zs$="1"
1840 		INPUT @(23,6);"X-Skalierungsfaktor: ";Xs$ USING "0+-",Taste!,4 
1850 		INPUT @(23,6);"Y-Skalierungsfaktor: ";Ys$ USING "0+-",Taste!,4 
1860 		INPUT @(23,6);"Z-Skalierungsfaktor: ";Zs$ USING "0+-",Taste!,4
1870 		Skalierung( VAL(Xs$), VAL(Ys$), VAL(Zs$))
1880		Shade:Darstellen
1890 	ENDIF
1900 UNTIL K$="q"
1910 V_C1svwk:Appl_Exit: CLS : END
1920 ' Verschieben/Translation
1930 DEF PROC Translation(Dx!,Dy!,Dz!)
1940 LOCAL I!,J!
1950 Xpos!=Xpos!+Dx!:Ypos!=Ypos!+Dy!:Zpos!=Zpos!+Dz! 
1960 FOR I!=0 TO Polyzahl!
1970 	FOR J!=0 TO Ecken!(I!)-1 
1980 		X!(I!,J!)=X!(I!,J!)+Dx!
1990		Y!(I!,J!)=Y!(I!,J!)+Dy!
2000		Z!(I!,J!)=Z!(I!,J!)+Dz!
2010 	NEXT J!
2020 NEXT I!
2030 RETURN 2040 ' Skalierung
2050 DEF PROC Skalierung(Xfaktor!,Yfaktor!,Zfaktor!) 
2060 LOCAL I!,J!
2070 FOR I!=0 TO Polyzahl!
2080 	FOR J!=0 TO Ecken!(I!)-1
2090		X!(I!,J!)=(X!(I!,J!)-Xpos!)*Xfaktor!+XPos!
2100 		Y!(I!,J!)=(Y!(I!,J!)-Ypos!)*Yfaktor!+Ypos! 
2110 		Z!(I!,J!)=(Z!(I!,J!)-Zpos!)*Zfaktor!+Zpos! 
2120 	NEXT J!
2130 NEXT I!
2140 RETURN
2150 ' Rotation um X-Achse 
2160 DEF PROC Xrotation(Winkel!)
2170 LOCAL I!,J!,Sinus!,Cosinus!,Alty!,Altz!
2180 Winkel!=Winkel!/180*PI 
2190 Sinus!= SIN(Winkel!)
2200 Cosinus!= COS(Winkel!)
2210 FOR I!=0 TO Polyzahl!
2220 	FOR J!=0 TO Ecken!(I!)-1
2230 		Alty!=Y!(I!,J!)-Ypos!:Altz!=Z!(I!,J!)-Zpos! 
2240		Y!(I!,J!)=Cosinus!*Alty!-Sinus!*Altz!+Ypos!
2250		Z!(I!,J!)=Sinus!*Alty!+Cosinus!*Altz!+Zpos!
2260 	NEXT J!
2270 NEXT I!
2280 Shade 
2290 RETURN
2300 ' Rotation um Y-Achse 
2310 DEF PROC Yrotation(Winkel!)
2320 LOCAL I!,J!,Sinus!,Cosinus!,Altx!.Altz!
2330 Winkel!=Winkel!/180* PI 
2340 Sinus!= SIN(Winkel!)
2350 Cosinus!= COS(Winkel!)
2360 FOR I!=0 TO Polyzahl!
2370 	FOR J!=0 TO Ecken!(I!)-1
2380		Altx!=x!(I!,J!)-Xpos!:Altz!=Z!(I!,J!)-Zpos!
2390		X!(I!,J!)=Cosinus!*Altx!-Sinus!*Altz!+Xpos!
2400		Z!(I!,J!)=Sinus!*Altx!+Cosinus!*Altz!+Zpos!
2410 	NEXT J!
2420 NEXT I!
2430 Shade 
2440 RETURN
2450 ' Rotation um Z-Achse 
2460 DEF PROC Zrotation(Winkel!)
2470 LOCAL I!,J!,Sinus!,Cosinus!,Altx!,Alty!
2480 Winkel!=Winkel!/180* PI 
2490 Sinus!= SIN(Winkel!)
2500 Cosinus!= COS(Winkel!)
2510 FOR I!=0 TO Polyzahl!
2520 	FOR J!=0 TO Ecken!(I!)-l
2530 		Altx!=X!(I!,J!)-Xpos!:Alty!=Y!(I!,J!)-Ypos! 
2540 		X!(I!,J!)=Cosinus!*Altx!-Sinus!*Alty!+Xpos! 
2550 		Y!(I!,J!)=Sinus!*Altx!+Cosinus!*Alty!+Ypos! 
2560 	NEXT J!
2570 NEXT I!
2580 Shade 
2590 RETURN 
2600 '
2610 ' Berechnung der Polygonhelligkeiten 
2620 DEF PROC Shade
2630 LOCAL I!,Ax!,Ay!,Az!,Bx!,By!,Bz!,Cx!,Cy!,Cz!,Nx!,Ny!,Nz!,Nlen!,Co!
2640 FOR I!=0 TO Polyzahl!
2650 	Ax!=X!(I!,0)
2660 	Ay!=Y!(I!,0)
2670 	Az!=Z!(I!,0)
2680 	Bx!=X!(I!,1)-Ax!
2690 	By!=Y!(I!,1)-Ay!
2700 	Bz!=Z!(I!,1)-Az!
2710 	Cx!=X!(I!,2)-Ax!
2720 	Cy!=Y!(I!,2)-Ay!
2730 	Cz!=Z!(I!,2)-Az!
2740 	Nx!=(By!*Cz!-Bz!*Cy!)/1000 
2750 	Ny!=(-Bx!*Cz!+Bz!*Cx!)/1000 
2760 	Nz!=(Bx!*Cy!-By!*Cx!)/1000 
2770 	Nlen!=SQR(Nx! 2+Ny!^2+Nz!^2)     //MARK
2780 	Co!=((Nx!*Lix!+Ny!*Liy!+Nz!*Liz!)/(Nlen!*Lilen!))^2 
2790 	Hell!(I!)= INT((1-Co!)*8.5)
2800 NEXT I!
2810 RETURN 
2820 '
2830 ' Setzen des Lichtverlaufs 
2840 DEF PROC Setlight(X!,Y!,Z!)
2850 Lix!=X!:Liy!=Y!:Liz!=Z!
2860 Lilen!= SQR(X!^2+Y!^2+Z!^2)
2870 RETURN 
2880 '
2890 ' Sortierung der Flächen nach Abstand
2900 DEF PROC Sortpoly
2910 LOCAL I!,J!,Cx!,Cy!,Cz!
2920 FOR I!=0 TO Polyzahl!
2930 	Prioritaet!(I!)=I!
2940 	Cx!=Cy!=Cz!=0
2950 	FOR J!=0 TO Ecken!(I!)-1
2960 		Cx!=Cx!+X!(I!,J!)
2970 		Cy!=Cy!+Y!(I!,J!)
2980 		Cz!=Cz!+Z!(I!,J!)-Auge!
2990 	NEXT J!
3000 	Cx!=Cx!/Ecken!(I!):Cy!=Cy!/Ecken!(I!):Cz!=Cz!/Ecken!(I!)
3010 	Dist!(I!)= SQR(Cx!^2+Cy!^2+Cz!^2)
3020 NEXT I!
3030 SORT Dist!(0) TO Prioritaet!(0)
3040 RETURN 
3050 '
3060 ' Darstellung der Polygone 
3070 DEF PROC Darstellen
3080 LOCAL Ap!,Zp!,Ze!,XI!,Y1!,X2!,Y2!,Tl!,T2!
3090 Vsf_Interior(0):Vsf_Perimeter(0)
3100 V_Recf1(0,0,639,365)
3110 IF Bildmodus!=1 THEN 
3120 	Vsf_Interior(0)
3130 	Vsf_Color(1):Vsf_Perimeter(1):Vsl_Color(1)
3140 ENDIF
3150 IF Bildmodus!=2 THEN
3160 	Vsf_Interior(2):Vsf_Color(1):Vsf_Perimeter(0)
3170 ENDIF
3180 IF Bildmodus!>0 THEN Sortpoly 
3190 Vs_Clip(0,0,639,365)
3200 FOR Ap!=0 TO Polyzahl!
3210 	IF Bildmodus!>0 THEN Zp!=Prioritaet!(Polyzahl!-Ap!) ELSE Zp!=Ap!
3220 	FOR Ze!=0 TO Ecken!(Zp!)-l
3230 		Projektion(X!(Zp!,Ze!),Y!(Zp!,Ze!),Z!(Zp!,Ze!), Ptsin%(0,Ze!),Ptsin%(1,Ze!)) 
3240 	NEXT Ze!
3250 	Ptsin%(0,Ecken!(Zp!))=Ptsin%(0,0)
3260 	Ptsin%(1,Ecken!(Zp!))=Ptsin%(1,0)
3270 	IF Bildmodus!=2 THEN Vsf_Style(Hell!(Zp!))
3280 	IF Bildmodus!=0 THEN V_Pline(Ecken!(Zp!)+1) ELSE V_Fillarea(Ecken!(Zp!)+1)
3290 NEXT Ap!
3300 RETURN 
3310 '
3320 ' Umrechnung 3D -> 2D
3330 DEF PROC Projektion(X3d!,Y3d!,Z3d!,R X2d!,R Y2d!)  //MARK
3340 LOCAL D!
3350 D!=Z3d!-Auge!
3360 X2d!=320+(-Auge!*X3d!)/D!
3370 Y2d!=200-(-Auge!*Y3d!)/D!
3380 RETURN
3390 DEF PROC Read_Data 
3400 LOCAL I!,J!
3410 FOR I!=0 TO Maxpoly!
3420 	READ Ecken!(I!)
3430 	IF Ecken!(I!)=0 THEN EXIT
3440 	FOR J!=0 TO Ecken!(I!)-l
3450 		READ X!(I!,J!),Y!(I!,J!),Z!(I!,J!)
3460 	NEXT J!
3470 NEXT I!
3480 Polyzahl!=Maxpoly!
3490 RETURN 
3500 Daten   //MARK
3510 DATA 8: Zahl der Polygone-1
3520 DATA 4: Zahl der Ecken von Vorderteil
3530 DATA 40,40,-60
3540 DATA 40,-40,-60
3550 DATA -40,-40,-60
3560 DATA -40,40,-60
3570 DATA 4:' Zahl der Ecken von Hinterseite
3580 DATA 40,40,60
3590 DATA 40,-40,60
3600 DATA -40,-40,60
3610 DATA -40,40,60
3620 DATA 4:' Zahl der Ecken von der Unterseite
3630 DATA -40,-40,-60
3640 DATA -40,-40,60
3650 DATA 40,-40,60
3660 DATA 40,-40,-60
3670 DATA 4:' Zahl der Ecken von der linken Seite
3680 DATA -40,40,-60
3690 DATA -40,40,-60
3700 DATA -40,-40,60
3710 DATA -40,40,60
3720 DATA 4:' Zahl der Ecken von der rechten Seite
3730 DATA 40,40,60
3740 DATA 40,-40,-60
3750 DATA 40,-40,60
3760 DATA 40,40,60
3770 DATA 3:' Zahl der Ecken von der vorderen Dachkante 
3780 DATA -40,40,-60 
3790 DATA 0,80,-40 
3800 DATA 40.40,-60
3810 DATA 3:' Zahl der Ecken von der hinteren Dackkante 
3820 DATA 40,40,60 
3830 DATA 0,80,40 
3840 DATA 40,40,60
3850 DATA 4:' Zahl der Ecken von der Linken Dachhälfte 
3860 DATA -40,40,-60 
3870 DATA -40,40,60 
3880 DATA 0,80,40
3890 DATA 0,80,-40
3900 DATA 4:' Zahl der Ecken von der rechten Dachhälfte
3910 DATA 40,40,-60
3920 DATA 40,40,60
3930 DATA 0,80,40
3940 DATA 0,80,-40
3950 '
3960 'GEM-Einbindung
3970 DEF PROC Appl_Init: LOCAL X!,Y!,W!,H!: DIM Ctrl%(12),Intin%(128),Addrout%L(1)
3980 DIM Intout%(128),ptsin%(1,128),Ptsout%(1,6),Addrin%L(3),Global%(15)
3990 Appl_Exit: Graf_Handle(Ctrl%(6))
4000 RETURN
4010 '
4020 DEF PROC Appl_Exit: LOCAL I!
4030 Graf_Handle(Ctrl%(6)): CLIP : RETURN
4040
4050 IF TIMER THEN DEF PROC Graf_Handle(R Intout%<0).
R Intout%(1).R Intout%(2),R Intout(3),
R Intout%(4))   //MARK
ELSE DEF PROC Graf_Handle(R Intout(0))
4060 AES (77,Global!(15),Intin%(0),Addrin%L(0),
Intout%(5),Addrout%L(0))
4070 RETURN
4080 'VDI 
4090 '
4100 DEF PROC V_Pline(X!)
4110 VDI (6,Ctrl%(12),Intin%(0),Ptsin%(0,X!),Intout%(0),Ptsout%(0,0))
4120 RETURN
4130 '
4140 DEF PROC V_Fillarea(X!)
4150 	VDI (9,Ctrl%(12),Intin%(0),Ptsin%(0,X!),Intout%(0),Ptsout%(0,0))
4160 RETURN
4170 '
4180 DEF PROC Vsl_Color(Intin%(0))
4190 	VDI(17,Ctrl(12),Intin%(1),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4200 RETURN
4210 '
4220 DEF PROC Vsf_Interior(Intin%(0))
4230 	VDI (23,Ctrl%(12),Intin%(1),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4240 RETURN
4250 '
4260 DEF PROC Vsf_Style(Intin%(0))
4270 	VDI (24, Ctrl%(12),Intin%(1),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4280 RETURN
4290 '
4300 DEF PROC Vsf_Color(Intin%(0))
4310 	VDI (25,Ctrl%(12),Intin%(1),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4320 RETURN
4330 '
4340 DEF PROC V_opnvwk
4350 	V_Opnvwk(1,1,1,1,1.1,1,1,1,1,2)
4360 RETURN
4370 DEF PROC V_Opnvwk(Intin%(0),Intin%(1),Intin%(2) Intin%(3), Intin%(4),Intin%(5),Intin%(6), Intin%(7),Intin%(8),Intin%(9),Intin%(10)) 
4380 	VDI (100, Ctrl%(12), Intin%(11), Ptsin(0,0), Intout%(45),Ptsout%(0,6))
4390 RETURN
4400 '
4410 DEF PROC V_Clsvwk
4420 	VDI (101,Ctrl%(12),Intin%(0),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4430 RETURN
4440 '
4450 DEF PROC Vsf_Perimeter(Intin%(0))
4460 	VDI (104,Ctrl%(12),Intin%(1),Ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4470 RETURN
4480 '
4490 DEF PROC V_Recfl(Ptsin%(0,0),Ptsin%(1,0),Ptsin%(0,1),Ptsin%(1,1))
4500 	VDI(114,Ctrl%(12),Intin%(0),Ptsin%(0,2),Intout%(0),Ptsout%(0.0))
4510 RETURN
4520 '
4530 DEF PROC V_Hide_C
4540 	VDI (123,Ctrl%(12),Intin%(0),ptsin%(0,0),Intout%(0),Ptsout%(0,0))
4550 RETURN
4560 '
4570 DEF PROC Vs_Clip(Ptsin%(0,0), Ptsin%(1,0),Ptsin%(0,1),Ptsin%(1,1))
4580 	Intin%(0)=1
4590 	VDI (129,Ctrl%(12),Intin%(1),Ptsin%(0,4),Intout%(0),Ptsout%(0,0))
4600 RETURN
4610 '
4620 DEF PROC Vs_Clip: Intin%(0)=0 
4630 	VDI(129,Ctrl%(12),Intin!(1),Ptsin%(0,4),Intout%(0),Ptsout%(0,0))
4640 RETURN
Frank Mathy