Geschrieben von javadomi am 07.09.2005, 18:02

Der Meister sprach - Einleitung
Mit OpenGL kannst du Grafiken auf den Bildschirm ausgeben. Sogar Spiele kannst du damit programmieren (wie, erkläre ich in einen späteren Workshop). Nun werden wir uns mit den Grundlagen vertraut machen. Diesmal wartet sehr viel auf uns. Von der 3D Mathematik bis hin zu einen Camerasystem. Dabei begegnen wir Texturen und Lichtern.

Bevor man laufen laufen lernt - Vorraussetztungen
Du solltest schon etwas mit C++ vertraut sein. Einen C++ Compiler mit OpenGL musst du auch haben. Falls du noch keinen hast, dann lade dir den Borland BCC 5.5 Compiler unter http://www.borland.com herunter. Wie du mit ihn umgehst, erfährst du gleich. Ich möchte nochmal darauf hinweisen, dass du mein ersten Workshop gelesen haben musst. Außerdem dass dieser Workshop nicht für Anfäger gedacht ist. Du solltest schon wissen was HWND usw. ist. Dieser Workshop setzt da an, wo mein erster aufgehört hat (Kapitel 3).

Kapitel 4: Zeig mir wie man versteht - Compiler
Ich empfehle ausdrücklich den Borland Compiler zu benutzen. Da ich mich hier auf ihn spezialisiere, möchte ich noch mal die Benutzung dessen erklären. Einige wissen nähmlich nicht, wie man einen Programm ein Parameter übergibt. Ich verwende eine einfache Batch Datei zum übergeben von Parametern an Programme. Wir müssen erst wissen, was der Compiler so macht. Wir erstellen eine Batch Datei mit den Namen Show.bat in den Bin Verzeichnis des Compilers und in diese Datei schreiben wir folgende Zeile:

bcc32 > Compiler.txt

Dieser Code macht, dass die Ausgaben des Compilers in die Datei Compiler.txt gespeichert werden. Nun kannst du dir in Ruhe ansehen, was dein Compiler so
alles kann. Das geht auch mit den Linker, indem du in der Zeile bcc32 mit ilink32 ersetzt. Alle Dateien solltest du im Bin Verzeichnis des Compilers erstellen. Ich kompiliere und linke meine Konsolen Programme, die kein Windows API benutzen, immer so:

bcc32 -c Programm.cpp
ilink32 -C -q -c -Gl -Rr -Gt -Gpd -Gpr -Gn -x Programm.obj c0x32.obj, Prgr.exe,, import32.lib cw32.lib uuid.lib,,
del Programm.obj > nul
del Prgr.tds > nul

Aus Programm.cpp entsteht dann Prgr.exe und alle Temporären Dateien, die bei der
Kompilierung entstanden sind, werden entfernt. Du kannst natürlich auch wie im letzten Beispiel die Ausgaben in eine seperate Datei exportieren. Dazu hängst
du einfach "> Datei.txt" an. Du kannst die Dateien auch an einen Drucker chicken, aber das wäre nicht mehr OpenGL Grundlagen, sondern DOS Grundlagen. So exportierst du die Ausgaben in eine seperate Datei:

bcc32 -c Programm.cpp > Compile.txt
ilink32 -C -q -c -Gl -Rr -Gt -Gpd -Gpr -Gn -x Programm.obj c0x32.obj, Prgr.exe,, import32.lib cw32.lib uuid.lib,, > Link.txt
del Programm.obj > nul
del Prgr.tds > nul

Es ist nützlich, wenn deine Dos Box sofort schließt und du nicht weißt, was du für Fehler gemacht hast, hehe. Aber jetzt zeige ich euch, was ich euch eigentlich zeigen wollte, nämlich das, was ich euch zeigen wollte X-). Also: Die
Kompilation einer Windows Anwendung. Es geht ganz leicht:

bcc32 -c -tW Programm.cpp > Compile.txt
ilink32 -C -q -c -Gl -Rr -Gt -Gpd -Gpr -Gn -x /aa Programm.obj c0w32.obj, Prgr.exe,, import32.lib cw32.lib uuid.lib,, > Link.txt
del Programm.obj > nul
del Prgr.tds > nul

Wir sehen, dass bei der Compilen Zeile noch ein Parameter hinzugekommen ist, nämlich -tW. Das sagt den Compiler, dass er nicht nach main() sondern WinMain() suchen muss. Der Linker Zeile wurde /aa hinzugefügt. Das unterdrückt die Dos Box, die bei Windows eigentlich immer kommen sollte. Außerdem wurde die Objekt Datei c0x32.obj in c0w32.obj "umfetauft". Ich merke mir das so. X steht für Betriebssystemunabhägig und W steht für Windows. c0w32 enthält WinMain statt
einfach nur Main. Ich weiß nicht, wie ich sowas intelligentes nur sagen konnte. Denkt jetzt nicht, dass ich selbstgespräche führ. Liegt wahrscheinlich an meinen IQ, hehe. Nun aber Mund auf und Nase zu für das nähste Kapitel!!!

Kapitel 5: Der schöne Aufkleber - Texturen
Na jetzt wird es aber schön in unserer kolorierten Welt voller Dreiecke und Würfel, denn wir malen ein paar Bitmaps!!! Yeah! Als erstes schreiben wir eine
Funktion, die unsere Bilder im Bitmap Format laden soll, und in eine OpenGL Textur verwandel wird. Also wir wissen, dass wir ein Dateinamen brauchen und
wir müssen eine Variable die die fertige Textur darstellen soll zurückgeben.

GLuint LoadBitmap(LPTSTR FileName)
{

Ein schöner Anfang der Funktion. Den Prototyp haben wir also. Wir wollen den Bitmap erstmal als HResult erhalten und ihn dann in eine richtige BMP Variable
verzaubern.

HBITMAP hBild;
BITMAP Bild;

Jetzt laden wir die Datei mit einer Funktion aus der Include Datei windows.h und verwandel sie in die OpenGL kompitable Variable BITMAP:

hBild=(HBITMAP)LoadImage(GetModuleHandle(NULL), FileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);
GetObject(hBild, sizeof(Bild), &Bild);

Nun brauchen wir die OpenGL Variable für die Textur. Einige Befehle sagen, dass es in diese Variable geladen werden soll und wie es geladen werden soll:

GLuint tex;
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);

Einige Parameter an die Textur müssen wir auch senden und zwar wie sie gerendert werden soll:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

Diese Qualität kann aber auch noch verbessert werden, jedoch ist diese auch sehr gut. Für schlechtere Grafikkarten wird es eine Kriegserklärung sein diese Texturen zu rendern. Daher kannst du auch die Qualität vermindern, indem du aus beiden Zeilen GL_LINEAR entfernst und dafür GL_NEAREST einsetzts. Nun aber weiter zu den wichtigsten Befehl:

glTexImage2D(GL_TEXTURE_2D, 0, 3, Bild.bmWidth, Bild.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, Bild.bmBits);

Hier wird wieder gezaubert. Uns interessieren die Parameter Bild.bmHeight, Bild.bmWidth und Bild.bmBits. bmWidth und Height geben nur die Größe des Bildes an. Die Bits sind so aufgebaut:

GLubyte bits[64][64][4];

Der erste Index steht für die X Position und, wie du wahrscheinlich jetzt vermutest, der zweite für die Y Position. Natürlich steht der dritte für die Farbe. Den wollen wir uns aber genauer ansehen:

bits[x][y][0]=Rotanteil der Farbe
bits[x][y][1]=Grünanteil der Farbe
bits[x][y][2]=Blauanteil der Farbe
bits[x][y][3]=Das Ambient der Farbe (ist nicht von Bedeutung, oft 255)

So, damit wäre der Befehl mit sicherheit erledigt. Nun müssen wir nur noch die Texturen für OpenGL aktivieren, den Speicher freigeben und die Texturvariable tex zurückgeben.

glEnable(GL_TEXTURE_2D);
DeleteObject(hBild);
return tex;
}

So, nun haben wir eine tolle Funktion zum laden von Texturen. Nun aber sollten wir eine globale Variable für eine Textur definieren. Ich nenne sie jetzt liebevoll myTex.

GLuint myTex;

Nach der Initialization von OpenGL laden wir unsere Textur. Aber halt! Wir haben ja noch gar nichts gemalt. Das machen wir jetzt. Unser Bild muss 24-Bit haben und es darf 32*32, 64*64, 128*128, 256*256, 512*512, und so weiter in diesem Algorythm Groß sein. Die Texturen kannst du dir auch auf einer Webseite downloaden. Und auf meiner bald auch *spam* :-). So, nachdem du dein Bild im selben Verzeichnis wie der Exe Datei hast, kann ich ja weitermachen. Ich gehe davon aus, dass deine Textur mit den Dateinamen NableNuble.bmp versehen worden ist. Nach der Initialization von OpenGL die Textur laden:

LoadBitmap("NableNuble.bmp", myTex);

So, das hätten wir geschafft. Jetzt kommt der Render Vorgang. Er wird komplizierter als du dir wahrscheinlich vorstellst. Nein, nein, ich will dir keine Angst machen. Anders betrachtet ist es auch super einfach. Ich spreche jetzt aus der Sicht eines Assembler Programmierers. Eine fertige Engine sollte selbstverständlich einen Befehl haben, um eine Textur einzubinden. Diesen kennen wir bereits, hehe. Es ist glBindTexture():

glBindTexture(GL_TEXTURE_2D, myTex);

Und jetzt stell dir nicht vor, dass das alles war. Jetzt kommts noch besser. Der Würfel:

glBegin(GL_QUADS);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);

glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);

glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);

glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glEnd();

Diesen Würfel kennen wir schon aus meinen alten Workshop. Aber was hat der Befehl glTexCoord2f zu bedeuten? Ich erkläre es euch: Wenn wir ein Viereck haben mit A(1,1) B(2,1) C(2,2) D(1,2), dann müssen wir wissen wo die Textur gerendert werden soll. Wenn wir jetzt diesen Befehl mit den Parametern 0,0 an Punkt A ausführen, dann heißt das, dass der Punkt A den Anfang des Bitmaps hat. Und wenn wir an diesen Befehl die Parameter 0.5,0 übergeben hätten, dann wäre Punkt A der Anfang der Y Achse aber die X Achse würde genau in der Mitte einsetzen. Also die richtigen Zuweisungen an die Punkte wären jetzt

A 0,0 // Setze oben links
B 1,0 // Setze oben rechts
C 1,1 // Setze unten rechts
D 0,1 // Setze unten links

So, du solltest es so halbwegs verstanden haben, wenn du schlau bist. Nun aber lerne von mir wie ich von mir lerne! Auf zum nähsten Kapitel!

Kapitel 5: Bring mir die Erleuchtung - Licht
Es gibt also was schöneres als Texturen. Nun setzen wir die Arrays für das Licht ein. Es gibt zwei verschiedene Typen Licht. Die erste Sorte benutzt das Ambiente Licht. Diese Sorte kommt nicht von einer bestimmten Quelle. Alle Objekte in deinen Render Vorgang bekommen etwas von den Lich ab. Der zweite Typus des Lichts benutzt das Diffuse Licht. Dieses Licht hat eine Quelle und reflektiert alle Polygone in deinen Render Vorgang. Manche Polygone, die nahe an den Licht sind und sie das Licht trifft, erscheinen sehr hell und die, die das nicht machen, erscheinen viel dunkler. Das erzeugt einen super "Shade" Effekt. Das Licht wird immer im RGB Format angegeben. Also die erste Zahl ist der Rotanteil, die zweite der Grünanteil und die dritte der Blauanteil. Die letzte Zahl interessiert uns nicht.

GLfloat L_Ambient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
GLfloat L_Diffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };

Zum Schluss brauchen wir noch die Position des Lichts. Die drei ersten Werte geben die Position im XYZ Format an. Die Erste bewegt das Licht nach links und rechts. Die Zweite bewegt das Licht nach oben und unten. Und das Dritte bewegt das Licht zu dir oder in den Bildschirm. Wenn jetzt dieser Wert 50 beträgt, dann ist das Licht direkt bei deinen Kopf (oder in deinem Kopf). Aber wenn der Wert z.B. -5 beträgt, dann ist das Licht 5 Einheiten im Bildschirm. Und wieder mal intressiert uns der dritte Wert nicht:

GLfloat L_Position[]= { 0.0f, 0.0f, 2.0f, 1.0f };

So, die Variablen sind nun initialisiert. In unsere Initialisation von OpenGL muss aber noch was hin. Wir aktivieren nähmlich noch das Licht. Also schreiben wir an das Ende unserer Funktion InitGL() vor return. Erstmal das einfache Aktivieren des Lichts:

glEnable(GL_LIGHTING);

Das sagt einfach, dass das Lich benutzt werden soll. Nun setzten wir nacheinander das Ambient, das Diffuse und dann die Position:

glLightfv(GL_LIGHT0, GL_AMBIENT, L_Ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, L_Diffuse);
glLightfv(GL_LIGHT0, GL_POSITION,L_Position);

Da alles auf das Licht GL_LIGHT0 gesetzt wurde, aktivieren wir nun das schöne Licht mit dieser einfachen Zeile:

glEnable(GL_LIGHT0);

So, das war alles. Natürlich kannst du in deinen Rendervorgang das Ambient, das Diffuse und die Position jederzeit ändern, indem du einfach nochmal diese Zeilen schreibst:

glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
glLightfv(GL_LIGHT0, GL_POSITION,LightPosition);

In den Rendervorgang kannst du auch eine Textur, wie letztes Mal einsetzten. Es gibt auch sogenannte Materialien, die man für Objekte einsetzt, um zu bestimmen, wie sie das Licht reflektieren sollen. Das braucht man jedoch nicht unbedingt, trotzdem möchte ich das noch vor glEnable(GL_LIGHT0) ergenzen:

...
...
glLightfv(GL_LIGHT0, GL_POSITION,L_Position);
GLfloat mat_specular[]= { 1.0f, 0.5f, 0.5f, 1.0f };
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMateriali(GL_FRONT, GL_SHININESS, 128);

Darauf möchte ich in diesen Workshop noch nicht eingehen. Es ist so optimal eingestellt. Trotzdem kannst du nochmal damit herumspielen. Meistens haben alle Gegenstände die selben Materialien. Jetzt wird es aber etwas schwieriger in unseren Workshop. Befreie deinen Geist für die 3D Mathematik!

Kapitel 6: Zurück in die Schule - 3D Mathematik
Nun beschäftigen wir uns mehr mit Mathematik als mit C++. Aber glaub mir, dass wir es brauchen, umzu verstehen. Naja, ich werde dir erstmal erklären, wozu wir das brauchen. Da unser nähstes Kapitel die Camera sein wird, mit der du dich in einer drei Dimensionalen Welt bewegen wirst, musst du einige Formeln kennen, um die Position deren zu bestimmen. Keine Sorge. Das wird ein schwieriges Kapitel, aber nicht langes. Erstmal müssen wir wissen, was Sinus und Kosinus ist. Damit wir bei C++ bleiben, ist es in der Include Datei math.h aufgelistet. Sinus wird so berechnet:
sin von x = x - ((x^3) / 3!) + ((x^5) / 5!) - ((x^7) / 7!) +
und dann wieder Minus. Bis in das Unendliche. Also sowas trifft es schon genauer:
Unendlich
= E (-1)^n (x^(2n+1)) / (2n+1)!
n = 0
Huch, es ist etwas schwierig Sinus zu erklären. Besonders wenn man erst in der siebten Klasse ist. Aber keine Sorge, das kommt erst in der Sekundarstufe 2. Ach ja, ich habe beinahe Kosinus vergessen.
cos von x = 1 - ((x^2) / 2!) + ((x^4) / 4!) -
und dann so weiter mit Plus bis ins Unendliche. Diese Rechnung trifft es genauer:
Unendlich
= E (-1)^n ((x^2n) / (2n)!)
n = 0
So, jetzt kennst du Sinus und Kosinus. Wir sehen, dass Kosinus nahe bei Sinus verläuft. Jetzt fragst du dich wahrscheinlich, wie du das in C++ realisieren kannst. Ganz einfach. Nachdem du math.h eingebunden hast, benutzt du für Sinus die Funktion sin(double x) und Kosinus cos(double x). Aber wozu brauchen wir das? Für das bestimmen der Positionen in einer drei Dimensionalen Welt. Die Rotationen erspart uns OpenGL. Ganz einfach mit den Befehl glRotatef. Also jetzt wieder zurück zum Problem. Wenn wir in unserer 3D Welt stehen und uns vorwärts bewegen, dann ist es doch kein Problem! Einfach die Z Achse mit 1 subtrahieren. Und für Rückwärts Bewegungen die Z Achse mit 1 addieren. Jetzt kommt aber der Hacken: Was ist, wenn du dich um 90 Grad drehst (Rechsdrehung)? Wenn du danach noch vorwärts gehst, dann wirst du feststellen, dass du seitlich gehst, hehe. Jetzt kommt der Einsatz der 3D Mathematik. Wenn du dich um 90 Grad gedreht hast, dann sollte es logisch sein, dass sich nur die X Position erhöhen sollte. Und wenn du dich um 180 Grad drehst, und dann vorwärts gehst, dann sollte sich logischer Weise die Z Achse erhöhen. Wenn um 270 Grad, dann sollte sich die X Achse vermindern. Ok, aber wir können nicht 360 if Abfragen machen. Es wäre schon Problematisch, wenn man 45 Grad machen muss, weil da sich ja die Z Achse und die X Achse gleichzeitig verändert. Hier kommt jetzt Sinus und Kosinus ins Spiel. Wir wissen, dass diese Formeln nur mit Dezimalzahlen rechnen können. Wir wissen auch, dass PI im Bogenmas 180 Grad hat. Aber PI in Dezimal wäre der Anfang bei C++ genau 3.1415926535897932384626433832795. Das heißt, dass ein Grad in Dezimal genau 1 * (PI/180) = PI/180 Wir drehen uns ja um Alpha. Also sagen wir mal wir haben uns um 55 Grad nach rechts gedreht. Wir brauchen nun 55 Grad in Dezimal umzurechnen. Wir gehen so vor (Alpha ist 55 Grad): AlphaDezimal = Alpha * (PI/180) Also wäre die X Position, wenn wir vorwärts gehen, nun x - sin(Alpha*(PI/180)) Den Sinus können wir natürlich auch mit der Geschwindigkeit multiplizieren Ein Wert zwischen 0.1 und 0.2 wäre dafür gut geeignet. Wir verfahren so ähnlich mit der Z Achse. z - cos(Alpha*(PI/180)) Die Y Achse kann man auch bestimmen, aber sie bleibt bei Ego Shootern gleich, außer dass du springst. Trotzdem möchte ich dir die Formel dazu geben: y + -sin(Alpha*(PI/180)) So, das wäre alles für dieses Kapitel. Falls du einiges nicht richtig verstanden hast, dann mache dir darüber keine Sorgen. Wir werden es nicht mehr richtig verwenden. Aber jetzt hole dir ein heißes Kakao und lerne weiter! Auf zum nähsten Kapitel!

Kapitel 7: Einäugiges Monster - Camera
Diesmal fixieren wir uns nur auf den Render Vorgang deiner GLScene. Trotzdem brauchen wir ein paar globale Variablen. Wir wollen uns auf der X und der Z Achse bewegen. Außerdem wollen wir und auch um die X, Y und Z Achse drehen. Zu der Position des Spielers geben wir noch die Y Achse an. Das alles könnte so aussehen:

float x, y, z;
float xr,yr,zr;

Jetzt aber zum Render Vorgang. Wir werden den Spieler erstmal rotieren müssen. OpenGL hat bereits die optimale Funktion dafür:

glRotatef(xr,1.0f,0,0);
glRotatef(zr,0,0,1.0f);
glRotatef(360-yr,0,1.0f,0);

Jetzt müssen wir nur noch die Objekte gegen die Position der Camera setzen. Dies geschieht mit einen einzigen Befehl, den wir auch schon kennen:

glTranslatef(-x,-y,-z);

Im letzten Workshop haben wir schon eine perfekte Tasten abfrage in einen Array gemacht. Diesen Array haben wir damals keys genannt. Die Tastenabfrage machen wir dann so:

if (keys[VK_RIGHT])
{
yr -= 1; // Rechtsdrehung
}
if (keys[VK_LEFT])
{
yr += 1; // Linksdrehung
}
if (keys['W'])
{
xr-=1; // Nach oben schauen
}
if (keys['S'])
{
xr+=1; // Nach untern schauen
}
if (keys['A'])
{
zr-=1; // Um die Z Achse Links drehen
}
if (keys['D'])
{
zr+=1; // Um die Z Achse Rechts drehen
}
if (keys[VK_UP]) // Vorwärts (haben wir in Kapitel 6 beschprochen)
{
x -= sin(yr*(3.1415926535897932384626433832795/180))*0.2;
z -= cos(yr*(3.1415926535897932384626433832795/180))*0.2;
}
if (keys[VK_DOWN]) // Rückwärts (haben wir in Kapitel 6 beschprochen)
{
x += sin(yr*(3.1415926535897932384626433832795/180))*0.2;
z += cos(yr*(3.1415926535897932384626433832795/180))*0.2;
}

So, das war aber ein sehr kurzes Kapitel. Ich hoffe du hast jetzt erstmal genug von den Meister gelernt. Mehr in den Abschlussatz.

Aufwiedersehen - Ende
Es hat mir sehr viel Spass gemacht diesen Workshop zu schreiben und ich hoffe, dass du ihn mit sehr gut bewärtest, hehe. Nein, mir kommt es darauf nicht an. Mir ist es wichtig, dass du etwas verstehst. Deshalb möchte ich auch das du ihn kommentierst. Dann wird es wohl Zeit wieder mal Schluss zu machen. Aber keine Angst, ich werde mein Workshop fortsetzen. Noch viel Spass und Erfolg! Das waren 575 Zeilen und 3061 Wörter.

HINWEIS
Ich habe keine fertigen Quellcodes angegeben. Falls jemand sehr versessen auf die Sources ist, der fordert sie bitte bei mir an.

Bewertung Anzahl
6
75,0 %
3 Bewertungen
3
25,0 %
1 Bewertungen