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.
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:
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.
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.
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.
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.
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 0(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!/180PI 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