Würfelsimulator
Würfelsimulator
Würfelsimulator
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
<strong>Würfelsimulator</strong><br />
PDA-Programmierung<br />
Ausarbeitung<br />
von<br />
Ivo Torp<br />
Klaus Manneck<br />
Simon Brennecke<br />
im<br />
SS2010<br />
Seite 1 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Inhaltsverzeichnis<br />
Übersicht............................................................3<br />
Hintergrund........................................................3<br />
Problemstellung.................................................4<br />
Ist-Analyse.........................................................4<br />
Zielsetzung.........................................................4<br />
Technologien......................................................5<br />
Vorüberlegungen................................................5<br />
Dice-Framework...........................................5<br />
3d-Engine......................................................5<br />
Physik............................................................6<br />
Bewegung.................................................6<br />
Kollisionserkennung.................................7<br />
Kollisionsbehandlung...............................7<br />
Umsetzung.........................................................8<br />
Analyse der Zielplattform PDA....................8<br />
Testgerät...................................................8<br />
Hardware..................................................8<br />
Software....................................................8<br />
Software-Architektur.....................................9<br />
Aufbau......................................................9<br />
Datenstrukturen......................................10<br />
Programmablauf.....................................10<br />
Verfahren.....................................................11<br />
Generierung der Würfelkoordinaten.......11<br />
Dice8 .................................................11<br />
Dice20................................................11<br />
Dice12................................................11<br />
Sonderfall Dice10..............................12<br />
Generierung der Drahtgittermodelle.......13<br />
Generierung der Texturen.......................14<br />
Funktionsweise des Composition-Caches<br />
................................................................15<br />
Funktionsweise der Physik-Engine........16<br />
Programmablauf................................19<br />
Bewegung..........................................20<br />
Kollisionserkennung..........................21<br />
Kollisionsbehandlung........................24<br />
Funktionsweise der 3d-Engine...............27<br />
Initialisierung.....................................27<br />
Vertexbuffer.......................................29<br />
Rendern..............................................30<br />
Schnittstellen....................................................31<br />
Modulbeschreibungen.................................31<br />
GUI.........................................................31<br />
libDice....................................................31<br />
libPhysics................................................31<br />
libDXRender..........................................31<br />
Klassendiagramme......................................32<br />
libDice....................................................32<br />
libPhysics................................................33<br />
libDXRender..........................................34<br />
Screenshots......................................................35<br />
Echte Würfel? - Eine Statistik..........................36<br />
Zukunft.............................................................37<br />
Referenzen.......................................................37<br />
Seite 2 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Übersicht<br />
Projektname: <strong>Würfelsimulator</strong><br />
Fach:<br />
PDA-Programmierung<br />
Professor:<br />
Herr Prof. Dr. Zimmermann<br />
Programmiersprache: C#<br />
Zielplattform: PDA und PC<br />
Code-/Spitzname: Dice!<br />
Hintergrund<br />
Hintergrund ist das "Pen&Paper-Rollenspiel", für das viele verschiedene Arten von Würfel benötigt<br />
werden. Viele Menschen kennen nur den 6-seitigen Würfel. Im Umfeld des Pen&Paper-Rollenspiels<br />
existieren aber verschiedenste Arten von Würfeln. Ein verbreitetes Regelwerk<br />
"Dungeons&Dragons" setzt zum Beispiel auf 4-, 6-, 8-, 10-, 12- und 20-seitige Würfel. Zum<br />
Mitspielen benötigt jeder Spieler einige, oder nicht selten auch viele dieser Würfel.<br />
Bildquelle: Wikipedia ( http://de.wikipedia.org/wiki/Spielwürfel )<br />
Seite 3 / 37
Problemstellung<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Nun kann man diese Würfel natürlich käuflich erwerben, was Geld und Zeit kostet. Hier ergibt sich<br />
aber schon das erste Problem: Der 6-seitige Würfel ist verbreitet und praktisch in jedem<br />
Spielwarengeschäft erhältlich. Ein 20-, oder 4-seitiger Würfel wiederum ist praktisch nicht<br />
erhältlich. Spezialisierte Online-Shops bieten diese Würfel zu gehobenen Preisen an.<br />
Ein zweites Problem ist, das man nicht selten vergisst die Würfel einzupacken und mitzubringen.<br />
Ist-Analyse<br />
Letzteres kommt leider nicht selten vor. Aus diesem Grund existieren bereits viele<br />
"Würfelgeneratoren". Diese simulieren aber keine Würfel, sondern zeigen lediglich auf Knopfdruck<br />
eine neue Zufallszahl, skaliert auf die Größe des Würfels, an. Dies ist zwar funktional und in der<br />
Regel ausreichend, aber vernichtend für den Spielspaß am Rollenspiel. Man möchte ja sehen wie<br />
die Würfel fallen.<br />
Zielsetzung<br />
Aus diesen Gründen haben wir uns entschieden einen "<strong>Würfelsimulator</strong>" zu entwickeln. Dieser soll<br />
physikalisch korrekt und in 3d den Wurf eines Satzes von Würfeln simulieren, die Ergebnisse<br />
anzeigen und evt. beschränkte Auswertungen durchführen.<br />
Die Physik-Engine muss mindestens in der Lage sein, eine begrenzte Menge an Objekten<br />
realitätsnah zu bewegen, kollidieren und zur Ruhe kommen zu lassen. Dazu muss sie verschiedene<br />
physikalische Gesetze und Faktoren beachten: Gesetze der Mechanik und Kreisbewegung,<br />
Impulsübertragung und Stöße, Energieerhaltungssatz, Reibung, etc.<br />
Im Bestfall ist die Physik-Engine ein eigenständiges Modul, dass unabhängig von der restlichen<br />
Anwendung beliebige Objekte entgegennehmen und behandeln kann. Sie sollte möglichst alle<br />
bekannten physikalischen Faktoren mit einbeziehen und auch auf dem PDA ein flüssiges Ergebnis<br />
liefern. Da die Dice Anwendung nur Würfel simuliert (genau genommen gleichförmige, konvexe,<br />
starre Polyeder mit gleichverteilter Masse und ohne bewegliche Teile), wäre eine Erweiterung auf<br />
beliebige Objekte (z.B. verformbare, selbst bewegende oder aus Komponenten aufgebaute<br />
Gegenstände) zwar wünschenswert, um die Engine auch in anderen Projekten einsetzen zu können,<br />
aber im Rahmen dieses Projekts nicht unbedingt nötig.<br />
Seite 4 / 37
Technologien<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Zum Einsatz kamen folgende Technologien:<br />
• Microsoft Managed DirectX<br />
• Microsoft .net Compact Framework 3.5<br />
• Microsoft Visual Studio 2008<br />
• Subversion Codeverwaltung<br />
• Remote-Desktop<br />
• Virtuelle Maschinen<br />
Vorüberlegungen<br />
Dice-Framework<br />
Das Projekt wurde von Anfang an groß angelegt. Selbst nachdem aus Zeitgründen schon große<br />
Abstriche an der Kern-Funktionalität gemacht wurden, blieben etliche teilweise sehr<br />
unterschiedliche Funktionen übrig. Auch war von Anfang an klar, das die fehlenden Funktionen<br />
irgendwann einmal nachgetragen werden. Es wurde schnell klar, das ein eigenes Framework<br />
notwendig sein wird um alle Funktionen unter ein Dach zu bekommen und einfach zugänglich zu<br />
machen.<br />
3d-Engine<br />
Probleme gab es mit der 3D Leistung des Pocket Devices. Wie sich nach langer Recherche<br />
herausgestellt hat, war auf dem Gerät nur ein Software-Renderer-Treiber installiert. Dieser hat alle<br />
3D Berechnungen selbst über die CPU berechnet, anstatt die vorhandene Hardware-Beschleunigung<br />
zu verwenden. Da kein offizieller Treiber für diese Windows Mobile Version existiert (6.0), musste<br />
das Betriebssystem des PDA auf Windows Mobile 6.1 aktualisiert werden – um dann den einzigen<br />
(inoffiziellen) Direct3D Treiber installieren zu können.<br />
Seite 5 / 37
Physik<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Die Physik-Engine war der erste Teil des Dice Projekts, der bereits rudimentär existierte, als das<br />
Projekt begonnen wurde. Die ursprüngliche Idee war es, eine eigene physikalische Grundlage für<br />
weitergehende Projekte zu schaffen, wie z.B. die Programmierung eines Computerspiels mit<br />
realistischen Bewegungen. Die Entwicklung einer Starrkörpersimulation, um Würfel fallen zu<br />
lassen, war der erste Schritt zu dieser Idee.<br />
Begonnen wurde die Entwicklung in einem C# Testprogramm, das nur eine sehr grundlegende<br />
Darstellung der simulierten Objekte ermöglichte, nämlich ein parallel projiziertes<br />
Drahtgittermodell. Das Programm war dafür gedacht, eine Umgebung zu bieten, in der die<br />
Implementierung der Physik möglichst simpel und unabhängig von anderen Anwendungen<br />
erarbeitet werden konnte. Als die Physik-Engine mit dem restlichen Dice-Projekt verbunden werden<br />
sollte, wurde der Code noch einmal komplett neu geschrieben, um eine einheitliche Struktur<br />
umzusetzen, die Programmiersprache C# jedoch beibehalten, da auch die restlichen Teile des<br />
Projekts darin geschrieben wurden.<br />
Die Engine hat im Wesentlichen drei Aufgaben zu bewältigen: die realitätsgetreue Bewegung von<br />
Objekten, die Erkennung von Kollisionen zwischen diesen Objekten und die physikalisch korrekte<br />
Reaktion auf die gefundenen Kollisionen.<br />
Bewegung<br />
Die erste Aufgabe ist zugleich auch die einfachste. Die Bewegung befolgt einfache Gesetze der<br />
Mechanik, wie sie in allen gängigen Formelsammlungen zu finden sind. Da eine Bewegung im<br />
dreidimensionalen Raum stattfindet, sind alle beteiligten Größen vektoriell zu sehen. Die nötigen<br />
Grundgrößen zur Berechnung der Translation sind Beschleunigung (z.B. durch Gravitation), deren<br />
erste Ableitung Geschwindigkeit und als zweite Ableitung die zurückgelegte Strecke. Äquivalent<br />
dazu finden sich zur Berechnung der Rotation die Winkelbeschleunigung, Winkelgeschwindigkeit<br />
(Vektoren, deren Richtung die Achse der Drehung angibt, während die Länge die<br />
Drehgeschwindigkeit festlegt), sowie die Orientierung, die im Gegensatz zu den anderen<br />
v= v 0<br />
a⋅t<br />
s= s 0<br />
v⋅t 1 2 a⋅t 2<br />
Vektorgrößen als Matrix angegeben wird, da sie als<br />
Transformation auf die Koordinaten des Objekts<br />
angewendet wird. Die Strecke wäre im Prinzip auch<br />
als Transformationsmatrix darzustellen, doch da eine<br />
= 0<br />
⋅t<br />
= 0<br />
⋅t 1 2 ⋅t 2<br />
reine Translationsmatrix nur in einer einzigen Spalte tatsächlich Werte enthält, kann sie zu einem<br />
Vektor vereinfacht werden.<br />
Seite 6 / 37
Kollisionserkennung<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Die zweite Aufgabe erforderte einige Recherche und Überlegung. Das Testprogramm besaß nur eine<br />
sehr rudimentäre Kollisionserkennung, die noch keine Kollisionen zwischen verschiedenen, sich<br />
bewegenden Objekten registrieren konnte. Überprüft wurde lediglich, ob sich eine der Koordinaten<br />
der Eckpunkte eines Objekts über oder unter einem bestimmten Schwellwert (den Ausmaßen des<br />
Bildschirms bzw. Fensters) befand. Diese Art der Kollisionserkennung war jedoch alles andere als<br />
ausreichend. Um eine Kollision zwischen zwei konvexen Objekten festzustellen, müsste prinzipiell<br />
jeder Punkt eines Objekts gegen jede begrenzende Fläche (bzw. Ebene) des anderen Objekts geprüft<br />
werden. Falls sich ein Punkt von jeder Ebene aus gesehen auf der Innenseite befindet, liegt er<br />
zweifelsfrei innerhalb des Objekts. Diese Art der Kollisionsbestimmung erfordert jedoch gerade bei<br />
vielen, vieleckigen oder vielflächigen Objekten einen großen Aufwand, der sich exponentiell<br />
erhöht. Um dieses Problem zu vermeiden wurden verschiedene gängige Verfahren in Augenschein<br />
genommen, z.B. das sogenannte Voronoi-Clipping. Keines dieser Verfahren hat sich jedoch als<br />
brauchbar erwiesen, da entweder der Aufwand unverhältnismäßig hoch für den PDA, die<br />
Ergebnisse zu ungenau bzw. zu fehleranfällig waren oder das Verfahren nicht alle Daten liefern<br />
konnte, die die Physik für ihre Berechnungen benötigt. Daher wurde letztendlich ein eigenes<br />
Verfahren entwickelt, das für relativ gleichmäßige, konvexe Polyeder eine möglichst genaue<br />
Annäherung bei möglichst geringem Aufwand bieten soll (s. Umsetzung).<br />
Kollisionsbehandlung<br />
Die dritte Aufgabe wurde bereits im Testprogramm grundsätzlich gelöst. Zur Behandlung der<br />
Kollisionen wird der Impulserhaltungssatz herangezogen. Durch Umformen der Gleichung und<br />
Einsetzen der bereits bekannten Werte (Geschwindigkeit, Rotation, Kollisionspunkt), sowie<br />
Hinzunehmen von weiteren Parametern (Masse, Trägheitsmoment, Elastizität), kann ein gerichteter<br />
Impuls errechnet werden, der auf die kollidierenden Objekte angewandt wird, um sie wieder<br />
auseinander zu bewegen. Der Bias-Faktor (von engl. bias – Verzerrung), eine in der Natur nicht<br />
vorkommende Größe, wurde ebenfalls hinzugefügt, um Ungenauigkeiten auszugleichen, die<br />
aufgrund der beschränkten Rechenleistung zwangsläufig bei der Kollisionserkennung auftreten. Die<br />
meisten Bestandteile dieser Formeln wurden dem „Physics“ Artikel von Chris Hecker im Game<br />
Developer Magazine von März 1997 sowie dem Rigid Body Physics Tutorial auf www.xbdev.net<br />
entnommen.<br />
r= v A<br />
A<br />
× P A<br />
− v B<br />
B<br />
× P B<br />
<br />
<br />
j=n⋅<br />
1 m A<br />
1 m B<br />
<br />
−1e⋅r⋅n<br />
<br />
P 2<br />
A<br />
×n<br />
I A<br />
<br />
P B<br />
×n<br />
I B<br />
2<br />
<br />
v A<br />
= v A<br />
j<br />
P<br />
; <br />
m A<br />
= A<br />
A<br />
×j<br />
A I A<br />
v B<br />
= v B<br />
− j<br />
P<br />
; <br />
m B<br />
= B<br />
− B<br />
×j<br />
B<br />
I B<br />
Seite 7 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Umsetzung<br />
Analyse der Zielplattform PDA<br />
Testgerät<br />
Hersteller:<br />
HTC<br />
Modell:<br />
Kaiser TyTN II<br />
Prozessor:<br />
QUALCOMM(R) 7200 @ 400MHz<br />
Arbeitsspeicher:<br />
128 MiB<br />
Flash-Speicher:<br />
256 MiB<br />
Auflösung:<br />
240x320 Pixel<br />
3d-Beschleunigung: vorhanden<br />
Hardware<br />
Dia PDA-Hardware unterliegt im wesentlichen zwei Bedingungen: Sie muss klein sein und wenig<br />
Energie verbrauchen.<br />
Daraus resultieren verschiedene Einschränkungen. Einige hiervon sind:<br />
• Eingeschränkte Bildschirmgröße<br />
• Verhältnismäßig geringe Rechenleistung<br />
• Langsame Massenspeicher<br />
• Wenig Arbeitsspeicher<br />
• Eingeschränkte Eingabegeräte<br />
Software<br />
Aus den Einschränkungen der Hardware resultieren entsprechende Einschränkungen der Software.<br />
Das notwendigerweise zu verwendende .net Compact Framework 3.5 besitzt gerade im Multimedia-<br />
Bereich stark reduzierte Funktionalität im Verhältnis zur PC-Version:<br />
• Reduzierte Funktionalität der Bitmap-Klasse<br />
◦ Keine Möglichkeit zum direkten Zugriff auf den Pixel-Puffer<br />
◦ Dadurch auf GetPixel/SetPixel limitiert<br />
• Reduzierte Funktionalität der Graphics-Klasse<br />
◦ Gedreht Schrift zeichnen ist nicht möglich<br />
• Unpräzise Timer<br />
Seite 8 / 37
Software-Architektur<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Aufbau<br />
Das Projekt wurde in vier Softwaremodule unterteilt:<br />
• Benutzeroberfläche<br />
• Dice-Framework<br />
• Physik-Engine<br />
• 3d-Engine<br />
Die 3d-Engine und die Physik-Engine sind dabei vollkommen unabhängig von dem Rest. Man<br />
könnte Sie also jederzeit für andere Projekte weiterverwenden. Die beiden Module greifen nur über<br />
Schnittstellen auf den gemeinsamen Datenspeicher im Dice-Framework zu:<br />
Physik<br />
Engine<br />
Direct3D<br />
Renderer<br />
Dice<br />
Framework<br />
Das Dice-Framework verwaltet diese Daten und stellt selbst die abstrakte Funktionalität "Würfel zu<br />
werfen" über Schnittstellen bereit. Dazu konfiguriert es die Physik- und 3d-Engine und steuert diese<br />
entsprechend an. Die Benutzeroberfläche nutzt die Schnittstellen des Dice-Frameworks um die<br />
Benutzereingaben auszuführen. Die Benutzeroberfläche ist der einzige Teil der Software, der sich<br />
wesentlich über die Plattformgrenzen hinweg verändert hat. Die PC-Fassung musste mit Platz nicht<br />
sparen und war daher für einen kleinen PDA-Bildschirm ungeeignet. Die PDA-Version wurde<br />
komplett neu gestaltet um den neuen Bedingungen gerecht zu werden.<br />
Seite 9 / 37
Datenstrukturen<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Das Dice-Framework kennt folgende Datenstrukturen, die die verschiedene Elemente der<br />
Würfelumgebung beschreiben:<br />
• Material - Beschreibt das Material aus dem ein Objekte besteht<br />
• Oberfläche - Beschreibt eine Oberfläche eines Objekts<br />
• Objekt - Beschreibt ein generisches Objekt<br />
• Würfel - Beschreibt einen Würfel, spezialisiertes Objekt<br />
• Wand - Beschreibt eine Wand der Welt, spezialisiertes Objekt<br />
• Welt - Beschreibt die Welt selbst<br />
Jede Datenstrukturen beschreibt das entsprechende Element sowohl physikalisch (Masse,<br />
Trägheit, ...), als auch optisch (Textur) und liefert somit die Grunddaten für Physik- und 3d-Engine.<br />
Programmablauf<br />
Zunächst erstellt die Benutzeroberfläche eine neue Instanz der Welt. Daraufhin erzeugt das Dice-<br />
Framework bereits ein Grundgerüst mit Boden, Wänden und Skybox. Dann werden von der<br />
Benutzeroberfläche weitere Objekte in die Welt hinzugefügt. Dabei spielt es eigentlich keine Rolle<br />
was die Objekte darstellen. Das Framework behandelt alle außer Wände und Würfel gleich. Wände,<br />
da diese die absolute äußere Limitierung der Welt angeben, und Würfel da diese für das Ergebnis<br />
relevant sind. Nachdem dann die Welt konfiguriert ist können die Würfel geworfen werden. Hierzu<br />
ruft die Benutzeroberfläche die entsprechende Funktion im Framework auf. Diese Funktion startet<br />
dann die Physik- und 3d-Engine und führt den Würfelvorgang Schritt- für Schritt aus.<br />
Seite 10 / 37
Verfahren<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Generierung der Würfelkoordinaten<br />
Die Koordinaten eines bestimmten Würfels zu finden ist nicht immer einfach. Bei einem Dice6 sind<br />
die Koordinaten noch einfach zu finden, doch wie findet man Koordinaten für einen Dice8 , oder<br />
einen Dice12 – bei dem jede Seite 5 Ecken hat? Zunächst muss man wissen, dass bei Spielwürfeln<br />
jede Seite gleich groß und gleich geformt ist, und alle Eckpunkte denselben Abstand zum<br />
Mittelpunkt (Schwerpunkt) des Würfels haben müssen. Dies wird im Folgenden anhand einfacher<br />
Darstellungen gezeigt:<br />
Dice8<br />
Zunächst wird ein Dice6 zur Hilfe genommen. Alle Eckpunkte des<br />
Dice8 liegen genau auf den Mittelpunkten der Flächen des Dice6.<br />
Dice20<br />
Es wird auf jeder Achse eine Fläche mit dem Seitenverhältnis<br />
1 zu<br />
(goldener Schnitt) erstellt.<br />
Dann lassen sich die gefundenen Punkte wie in der Abbildung gezeigt zu<br />
den Flächen verbinden.<br />
Dice12<br />
Hier werden die selben Hilfsflächen wie beim Dice20 verwendet – und<br />
zusätzlich ein Dice6 im Inneren. Hier werden immer 5 Punkte zu einer<br />
Fläche verbunden.<br />
Seite 11 / 37
Sonderfall Dice10<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Bisher wurde die Meshgenerierung für alle Würfel außer den Dice10 erklärt. Diese waren bisher<br />
regelmäßige Polyeder – das heißt sie besitzen gleichseitige und gleichmäßige Würfelflächen.<br />
Dies trifft beim Dice10 nicht zu. Die Flächen des Dice10 sind sogenannte Drachenvierecke, die<br />
natürlich nicht gleichseitig sind.<br />
Da bekannt ist, dass alle Punkte denselben Radius haben (auf der<br />
Einheitskugel liegen), lassen sich die Koordinaten näherungsweise<br />
ermitteln.<br />
Zunächst werden die Spitzen oben und unten gesetzt, und auf genau<br />
mittlerer Höhe (siehe Seitenansicht) zwei Fünfecke – wie in der Draufsicht<br />
zu sehen, genau versetzt, platziert.<br />
Um die genaue Verschiebung der Fünfecke nach oben und unten zu finden,<br />
müssen diese langsam nach oben bzw. unten auf der Einheitskugel verschoben werden. Bei jeder<br />
Verschiebung wird dabei per Kreuzprodukt geprüft, ob die 4 entsprechenden Punkte einer „Fläche“<br />
tatsächlich auf einer Ebene liegen. Sobald dies (mit einer gewissen Toleranz) der Fall ist, wurde die<br />
korrekte Verschiebung gefunden. Die ermittelten Koordinaten wurden direkt in den <strong>Würfelsimulator</strong><br />
übernommen.<br />
Seitenansicht<br />
Draufsicht<br />
Seite 12 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Generierung der Drahtgittermodelle<br />
Die 3D-Modelle der Würfel (Meshes) werden zur Laufzeit generiert. Dies geschieht im Konstruktor<br />
der jeweiligen Dice-Klasse (z.B. Dice6 ). Dabei wird zunächst eine Liste der Eckpunkte erstellt.<br />
Vector3f[] vectors = new Vector3f[8];<br />
vectors[0] = new Vector3f(-0.5f, -0.5f, -0.5f);<br />
vectors[1] = new Vector3f(+0.5f, -0.5f, -0.5f);<br />
vectors[2] = new Vector3f(+0.5f, +0.5f, -0.5f);<br />
vectors[3] = new Vector3f(-0.5f, +0.5f, -0.5f);<br />
vectors[4] = new Vector3f(-0.5f, -0.5f, +0.5f);<br />
vectors[5] = new Vector3f(+0.5f, -0.5f, +0.5f);<br />
vectors[6] = new Vector3f(+0.5f, +0.5f, +0.5f);<br />
vectors[7] = new Vector3f(-0.5f, +0.5f, +0.5f);<br />
for (int i = 0; i < vectors.Length; i++)<br />
vectors[i].Normalize();<br />
Des Weiteren muss definiert werden, welche der Punkte zu einer Würfelseite gehören. Es wird pro<br />
Würfelart im Code festgelegt, wie viele Punkte eine Fläche bilden. Zu jedem Punkt einer jeden<br />
Fläche werden zusätzlich Texturkoordinaten hinzugefügt. Da (wie später noch erläutert) auf jeder<br />
Würfelseite eine eigene Textur mit jeweils einer Zahl verwendet wird, sind die Texturkoordinaten<br />
für jede Seite gleich.<br />
Für einen Dice6 sieht der Code, der die Eckpunkte, Flächen und Texturkoordinaten zusammenfasst,<br />
so aus:<br />
int[] vi = { 2, 3, 0, 1, 3, 2, 6, 7, 6, 2, 1, 5, 7, 4, 0, 3, 1, 0, 4, 5, 7, 6, 5, 4 };<br />
float[] tv = { 1.0f, 0.0f, 0.0f, 1.0f };<br />
float[] tu = { 1.0f, 1.0f, 0.0f, 0.0f };<br />
int i=0;<br />
for (int i = 0; i < 6; i++){<br />
Vertex3f[] vertices = new Vertex3f[4];<br />
for (int v = 0; v < vertices.Length; v++)<br />
vertices[v] = new Vertex3f(vectors[vi[n++]], tv[v % 4], tu[v % 4]);<br />
// … generate Surface with vertices …<br />
}<br />
Analog dazu wird es für die anderen Würfel gehandhabt. Dabei muss eine Fläche nicht zwingend<br />
aus 4 Ecken zusammengesetzt werden – so haben der Dice8 und der Dice20 nur 3-eckige Flächen,<br />
und der Dice12 hat sogar 5-eckige.<br />
Seite 13 / 37
Generierung der Texturen<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Bisher wurde nur die Geometrie erstellt. Zwar wurden Texturkoordinaten gesetzt, doch fehlen nun<br />
noch die Texturen selbst.<br />
Für jede Würfelseite wird eine eigene Textur verwendet, welche beim Laden eines Würfels erstellt<br />
und auch in einem Cache abgelegt wird, damit diese nicht mehrfach generiert werden muss.<br />
Texturen werden aus mehreren Parametern zusammengesetzt:<br />
Die Grundfarbe, die Zahl, die Umrandungsform, und einer Oberflächenstruktur (aus einer PNG-<br />
Datei).<br />
1. Zunächst wird eine Bitmap im Speicher mit der Grundfarbe gefüllt.<br />
4<br />
4<br />
2. Die Zahl wird per Graphics Objekt mittig auf das Bitmap platziert.<br />
3. Der Rahmen wird (je nach Würfel) dazu gezeichnet. Dieser Beispielrahmen ist<br />
für einen Dice10 geeignet.<br />
4. Zum Schluß wird per Alphablending die Strukturtextur mit der bisherigen Textur<br />
„vermischt“.<br />
Bei Würfeln wie dem Dice10, Dice12 und Dice20 müssen einige Besonderheiten<br />
beachtet werden.<br />
So gibt es (als Spielwürfel) Dice10-Würfel, die anstelle von „1“ mit „0“ in der Zählung beginnen,<br />
und wahlweise in Einer- oder Zehnerschritten beschriftet sind.<br />
Des Weiteren müssen bei jedem dieser Würfel an den Ziffern 6 und 9 eine Markierung angebracht<br />
werden, damit diese unterschieden werden können. Dies wurde im <strong>Würfelsimulator</strong> mit Hilfe eines<br />
Punktes rechts unten an der Ziffer erreicht.<br />
Seite 14 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Funktionsweise des Composition-Caches<br />
Natürlich kann das ganze Framework nicht ohne ein paar Hilfs-Funktionen und Hilfs-Klassen<br />
leben. Eine wichtige davon ist der "CompositionCache". Um seine Aufgabe verstehen zu können,<br />
ist es zunächst einmal wichtig zu verstehen, wie Würfel überhaupt erstellt werden.<br />
Der Konstruktor unseres Referenzwürfels (d6) sieht folgendermaßen aus:<br />
public Dice6(World World, Material Material, float Scale, FontFamily<br />
FontFamily, Color ForeColor);<br />
Der Konstruktor benötigt also Informationen wie:<br />
• Die Welt in dem der Würfel erzeugt werden soll<br />
• Das Material aus dem er bestehen soll<br />
• Seine Skalierung<br />
• Die Schriftart der Zahlen<br />
• Die Schriftfarbe der Zahlen<br />
Erkennbar wird, dass der Würfel erst zur Laufzeit aus kleinen Bauteilen zusammengesetzt wird.<br />
Dies kostet Rechenleistung, die auf einem PDA nur in beschränkter Menge zur Verfügung steht.<br />
Wenn man nun bedenkt das man oft mit vielen gleichen Würfeln würfelt, würde es doch Sinn<br />
machen, das Grundgerüst des Würfels zwischenzuspeichern, um danach schneller Kopien eines<br />
Würfels erzeugen zu können.<br />
Genau das übernimmt der "CompositionCache". Er speichert Rechenintensive Vorgänge wie die<br />
Texturgenerierung (GetPixel, SetPixel) zwischen und stellt Sie Würfeln, die die selbe Textur<br />
benötigen zur Verfügung.<br />
Warum Texturgenerierung zur Laufzeit?<br />
• Verschiedene Materialien (Metall, Stein, Plastik, Holz, etc....)<br />
• Verschiedene Farben für das selbe Material<br />
• Zahlen oder Punkte auf den Seiten der Würfel<br />
• Verschiedene Farben und Schriftarten für die Beschriftung der Würfel<br />
Seite 15 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Funktionsweise der Physik-Engine<br />
Die Physik-Engine ist als eigenständige Bibliothek in der libPhysics implementiert. Die einzigen<br />
Abhängigkeiten ergeben sich von DirectX durch die Nutzung der Vector3 und Matrix Klassen. Die<br />
Kommunikation mit den anderen Programmteilen erfolgt über drei Interfaces, die die grundsätzliche<br />
Objektstruktur darstellen.<br />
IObject stellt einen einzelnen, abgeschlossenen Starrkörper dar. Es enthält alle Werte, die für die<br />
Physik wichtig sind:<br />
• Position, Velocity und TransAcceleration für die geradlinige Bewegung<br />
• Orientation, Rotation und RotAccaleration für die Drehbewegung<br />
• Mass und Inertia zur Berechnung von Kräften und Impulsen<br />
• Corners und Faces, die den geometrischen Aufbau des Objekts beschreiben<br />
• OrientedCorners und Radius, Hilfsvariablen für den schnellen Zugriff auf geometrische<br />
Eigenschaften des Objekts<br />
• Resting, das gesetzt wird, sobald ein Objekt sich nicht mehr (relevant) bewegt<br />
ISurface beschreibt eine Seitenfläche eines Objekts und enthält folgende Daten:<br />
• das Object, zu dem die Fläche gehört, und das Material, aus dem sie besteht<br />
• Corners und Normal, die die räumliche Lage der Fläche relativ zu ihrem Objekt beschreiben<br />
• OrientedCorners und OrientedNormal, Hilfsvariablen für den schnellen Zugriff auf die<br />
räumliche Lage<br />
IMaterial enthält alle Oberflächeneigenschaften, die für die Kollisionsbehandlung benötigt werden:<br />
• Restitution für die Elastizität bei Stößen<br />
• StaticFriction und KineticFriction, die (approximierten) Reibungskoeffizienten<br />
Seite 16 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Die tatsächliche Funktionalität der Physik wird vom Environment zur Verfügung gestellt. Diese<br />
Klasse enthält globale Parameter, die die Umgebung beschreiben, in der die Simulation abläuft:<br />
• Objects, Walls und Collisions, Listen von Objekten, die simuliert werden, Ebenen, die als<br />
räumliche Begrenzung der Simulation fungieren, bzw. festgestellten Kollisionen, die intern<br />
bis zur Behandlung gespeichert werden.<br />
• Gravity als allgemeine Beschleunigung, die auf alle Objekte angewendet wird. Über diese<br />
Beschleunigung kann auch ein stetiger Wind oder ähnliches dargestellt werden.<br />
• Damping zum Abbremsen von Objekten, aufgeteilt in transDamping (Translation) und<br />
rotDamping (Rotation). Diese Variable kann zur Simulation von Medien genutzt werden, die<br />
dichter als Vakuum sind (z.B. Luft).<br />
• Tolerance, MaxIntervals und MinEnergy zur Bestimmung von Schwellwerten für die<br />
Kollisionserkennung. Standardwerte: Toleranz 0.1 m, maximale Intervalle 5, minimale<br />
Energie 0.<br />
• Bias Faktor, der global für alle Kollisionen gilt<br />
• Resting, um abzufragen, ob das gesamte System unterhalb der minimalen Energieschwelle<br />
liegt<br />
Die von außen ansprechbaren Methoden dienen einerseits zum Aufbauen (AddObject, AddWall)<br />
bzw. Aufräumen (ClearObjects, ClearWalls) der zu simulierenden Umgebung, sowie zum<br />
Durchführen der Simulation selbst (Simulate). Die privaten Methoden werden später ausführlich<br />
erklärt (s. Programmablauf).<br />
Da die Interfaces keine eigenen Methoden implementieren, wurde dem Environment die Hilfsklasse<br />
Oriented hinzugefügt, die statische Methoden zur Beschleunigung und Vereinfachung der<br />
Objektbehandlung enthält:<br />
• Corners(Object und Surface) sowie Normal(Surface) zur effizienten Bestimmung der<br />
gedrehten Koordinaten. Die Koordinaten werden nur berechnet, wenn sie tatsächlich<br />
benötigt werden, und dann für das restliche Intervall zwischengespeichert. Reset(Object und<br />
Surface) setzt sie wieder zurück.<br />
• FaceIndexTowards und FaceTowards zur Richtungsbestimmung von Flächen. Dies wird<br />
einerseits für die optimierte Kollisionserkennung benötigt und andererseits auch zum<br />
Ablesen der Würfelergebnisse verwendet.<br />
• Distance zur Distanzberechnung zwischen Punkt und Ebene<br />
• Energy zur Bestimmung der Bewegungsenergie Körpers<br />
Seite 17 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Zur Erkennung und Behandlung von Kollisionen wird in der libPhysics die Collision Klasse<br />
verwendet. Hierbei handelt es sich um eine abstrakte Klasse, die ein gemeinsames Grundgerüst für<br />
mehrere verschieden implementierte Kollisionstypen, die BodyCollision und die WallCollision,<br />
bietet. Jede Collision-Instanz stellt eine (mögliche) Kollision zweier Objekte miteinander oder eines<br />
Objekts mit einer Wand dar.<br />
Soll eine Kollision geprüft werden, wird zunächst eine Instanz mit den zu prüfenden Objekten<br />
erzeugt und dann die Methode Detect() aufgerufen, die jede abgeleitete Kollisionsklasse<br />
eigenständig implementiert. Diese führt die nötigen Berechnungen aus, um die Kollision danach mit<br />
einem Handle() Aufruf aufzulösen. Die genaue Funktionsweise dieser Methoden wird später<br />
erläutert (s. Programmablauf).<br />
Um den aktuellen Status einer Kollision zu beschreiben, wird das Enum State verwendet.<br />
UNDEFINED ist eine Kollision, wenn noch keinerlei Prüfung oder Rechnung stattgefunden hat.<br />
CLEAR bedeutet, dass die Objekte zu weit voneinander entfernt sind, um sich zu berühren. Mit<br />
COLLIDING wurde eine Berührung innerhalb der Toleranzgrenzen des Environment festgestellt,<br />
wohingegen PENETRATING eine Durchdringung bedeutet, die über diese Toleranzgrenze<br />
hinausgeht. SEPARATING ist eine Kollision dann, wenn die Objekte bereits kollidiert sind und sich<br />
nun wieder voneinander entfernen.<br />
Intern muss jede Kollision ihren aktuellen Status (currentState), die kollidierenden Objekte, die<br />
Umgebung (environment), in der die Kollision stattfindet, sowie alle bereits gefundenen<br />
Kollisionspunkte (cpoints) speichern. Die Punkte werden jeweils in einem CollisionPoint Struct<br />
gehalten, da sie mehrere Werte aufnehmen müssen:<br />
• Position, ihre absoluten Koordinaten<br />
• Normal, die Richtung, in die der Kollisionsimpuls an dieser Stelle übertragen wird,<br />
abhängig von der Ausrichtung der Flächen, die einander berühren<br />
• Distance, die Eindringtiefe der Objekte ineinander an dieser Stelle. Dieser Wert ist wichtig,<br />
um zu beurteilen, welchen Status eine Kollision besitzt und ob bereits Toleranzgrenzen<br />
überschritten wurden.<br />
Seite 18 / 37
Programmablauf<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Der Einstiegspunkt in die Physik, nachdem das Environment erzeugt und die Objekte hinzugefügt<br />
wurden, ist die Simulate() Methode. Diese wird von außen mit dem zu berechnenden Zeitintervall<br />
(dTime) aufgerufen und stößt den Prozess der Intervallhalbierung an.<br />
public void Simulate(float dTime)<br />
{<br />
CalculateAllAccelerations();<br />
Interval(dTime, 1);<br />
}<br />
private void Interval(float dTime, int Level)<br />
{<br />
MoveAll(dTime);<br />
switch (DetectAllCollisions())<br />
{<br />
case Collision.State.COLLIDING:<br />
HandleAllCollisions();<br />
break;<br />
case Collision.State.PENETRATING:<br />
if (Level > maxIntervals)<br />
{<br />
HandleAllCollisions();<br />
}<br />
else<br />
{<br />
Interval(-Math.Abs(dTime) / 2f, Level + 1);<br />
Interval(+Math.Abs(dTime) / 2f, Level + 1);<br />
}<br />
break;<br />
}<br />
}<br />
Die Intervallhalbierung dient dem Zweck, sich einer stattfindenden Kollision so genau wie möglich<br />
anzunähern, da auf einem Computersystem (insbesondere auf einem PDA) keine unbegrenzten<br />
Ressourcen und damit auch keine unbegrenzte Genauigkeit verfügbar sind.<br />
Der grobe Ablauf ist relativ simpel. Bevor das Intervall begonnen wird, werden zunächst für alle<br />
Objekte die aktuell wirkenden Beschleunigungen ausgerechnet. Dies ist erforderlich, da sich<br />
manche Kräfte, wie z.B. Reibung und Dämpfung, über die Zeit bzw. mit der Geschwindigkeit<br />
verändern können. Innerhalb eines Intervalls werden diese Kräfte als konstant angenommen, um<br />
einen unnötig hohen Rechenaufwand zu vermeiden.<br />
Als nächstes werden die Objekte mit den so berechneten Geschwindigkeiten um die angegebene<br />
Zeitspanne bewegt. Danach läuft die Kollisionserkennung durch, die uns sagen kann, ob sich zu<br />
diesem Zeitpunkt Objekte berühren oder überschneiden. Falls nicht, ist die Simulation ereignislos<br />
verlaufen und die Methode wird verlassen. Falls eine Durchdringung festgestellt wurde, greift<br />
allerdings die Halbierung: die Simulation wird rekursiv um die halbe Zeit zurückgedreht und noch<br />
einmal auf Kollisionen überprüft, usw. bis ein Kollisionspunkt innerhalb der Toleranzgrenzen<br />
gefunden wurde oder die maximale Anzahl an Iterationen überschritten wird. Sobald dies der Fall<br />
ist, werden alle aktuell bestehenden Kollisionen behandelt und aufgelöst, so dass alle halbierten<br />
Intervalle auf Basis der nun veränderten Objektbewegung weiter gerechnet werden können. Dieser<br />
Prozess wird fortgesetzt, bis die Behandlung am Ende des festgelegten Zeitintervalls angelangt<br />
Folgende Grafik stellt die Intervallschritte zur Findung einer Kollision anschaulich dar:<br />
Seite 19 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Durchdringung<br />
Durchdringung<br />
Keine Kollision<br />
Kollision<br />
Bewegung<br />
Um die Objekte zu bewegen, muss zuerst einmal deren Beschleunigung bestimmt werden, wie oben<br />
bereits erwähnt. Die wirkenden Kräfte, die von der libPhysics berücksichtigt werden, sind die<br />
geschwindigkeitsabhängige Dämpfung (die auf jede Bewegung wirkt) und Gravitation (die nur<br />
Translation betrifft).<br />
private void CalculateAllAccelerations()<br />
{<br />
foreach (IObject body in objects)<br />
CalculateAcceleration(body);<br />
}<br />
private void CalculateAcceleration(IObject Object)<br />
{<br />
if (!Object.Resting)<br />
{<br />
Object.TransAcceleration = new Vector3();<br />
Object.TransAcceleration += gravity;<br />
Object.TransAcceleration -= transDamping * Object.Velocity;<br />
}<br />
}<br />
Object.RotAccaleration = new Vector3();<br />
Object.RotAccaleration -= rotDamping * Object.Rotation;<br />
Nachdem die Beschleunigungen für dieses Intervall feststehen, können alle Objekte entsprechend<br />
bewegt werden. Hierbei ist zu beachten, dass durch die Annahme einer konstanten Beschleunigung<br />
innerhalb eines Intervalls eine Rechnung in beide zeitlichen Richtungen möglich ist. Dies ist für die<br />
oben genannte Intervallhalbierung von besonderer Wichtigkeit.<br />
private void MoveAll(float dTime)<br />
{<br />
foreach (IObject body in objects)<br />
{<br />
Translate(body, dTime);<br />
Rotate(body, dTime);<br />
}<br />
}<br />
private void Translate(IObject Object, float dTime)<br />
{<br />
if (!Object.Resting)<br />
{<br />
Object.Position += dTime * Object.Velocity + 0.5f * dTime * dTime *<br />
Object.TransAcceleration;<br />
Object.Velocity += dTime * Object.TransAcceleration;<br />
}<br />
}<br />
private void Rotate(IObject Object, float dTime)<br />
{<br />
if (!Object.Resting)<br />
{<br />
Matrix Orientation = Object.Orientation;<br />
Orientation.Multiply(Matrix.RotationAxis(Object.RotAccaleration, 0.5f * dTime *<br />
Seite 20 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
dTime * Object.RotAccaleration.Length()));<br />
Orientation.Multiply(Matrix.RotationAxis(Object.Rotation, dTime *<br />
Object.Rotation.Length()));<br />
Object.Orientation = Orientation;<br />
Object.Rotation += dTime * Object.RotAccaleration;<br />
Oriented.Reset(Object);<br />
}<br />
}<br />
Kollisionserkennung<br />
Die Kollisionserkennung wird nach jedem Intervallschritt einmal angestoßen. Zunächst werden alle<br />
im letzten Schritt gefundenen Kollisionen verworfen und danach für jede Objekt-Objekt bzw.<br />
Objekt-Wand Kombination, die nicht stillsteht, eine Kollisionsinstanz erzeugt und per Detect() auf<br />
ihren Status geprüft. Alle gefundenen Kollisionen werden zur Behandlung in die Liste<br />
aufgenommen, wobei der „tiefste“ Status zur Steuerung an die Intervallhalbierung zurückgegeben<br />
wird.<br />
private Collision.State DetectAllCollisions()<br />
{<br />
Collision.State currentState = Collision.State.UNDEFINED;<br />
collisions.Clear();<br />
for (int i = 0; i < objects.Length; i++)<br />
{<br />
foreach (ISurface wall in walls)<br />
{<br />
if (!objects[i].Resting)<br />
{<br />
Collision col = new WallCollision(objects[i], wall, this);<br />
col.Detect();<br />
Collision.State stat = col.CheckState;<br />
if (stat > Collision.State.SEPERATING)<br />
collisions.Add(col);<br />
if (stat > currentState)<br />
currentState = stat;<br />
}<br />
}<br />
for (int j = i + 1; j < objects.Length; j++)<br />
{<br />
if (!objects[i].Resting || !objects[j].Resting)<br />
{<br />
Collision col = new BodyCollision(objects[i], objects[j], this);<br />
col.Detect();<br />
Collision.State stat = col.CheckState;<br />
if (stat > Collision.State.SEPERATING)<br />
collisions.Add(col);<br />
if (stat > currentState)<br />
currentState = stat;<br />
}<br />
}<br />
}<br />
return currentState;<br />
}<br />
Die Implementierung der Detect() Methode unterscheidet sich in den verschiedenen<br />
Kollisionsarten. Beispielhaft soll hier nun der Ablauf für die BodyCollision (Objekt-Objekt-<br />
Kollision) gezeigt werden. Der Code der WallCollision (Objekt-Wand-Kollision) ist prinzipiell nur<br />
eine Untermenge davon, bei der eines der Objekte als eine einzelne, statische Fläche angenommen<br />
werden kann.<br />
Zu Beginn der Methode wird eine Initialisierung der benötigten Variablen<br />
vorgenommen. Die Kollisionsnormale wird zunächst als Vektor zwischen<br />
den Mittelpunkten der beiden Objekte approximiert, um mithilfe der Radien<br />
der beiden Objekte zu testen, ob sie sich überhaupt nah genug für eine<br />
Seite 21 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Kollision beieinander befinden. Falls nicht, kann die Kollisionserkennung an dieser Stelle schon<br />
wieder verlassen werden.<br />
float radius, distance, depth = environment.Tolerance;<br />
Vector3 point, normal = bodyA.Position – bodyB.Position;<br />
if (normal.LengthSq() > Math.Pow(bodyA.Radius + bodyB.Radius, 2) +<br />
environment.Tolerance)<br />
{ currentState = State.CLEAR; return; }<br />
Als nächster Schritt wird anhand dieses Mittelpunktvektors bestimmt,<br />
welche Flächen der beiden Objekte einander zugewandt sind. Da es sich in<br />
unserer Simulation einheitlich um konvexe Polyeder mit in etwa<br />
gleichmäßiger räumlicher Ausdehnung handelt, können wir davon ausgehen,<br />
dass nur diese beiden Flächen einander berühren können.<br />
faceA = Oriented.FaceTowards(bodyA, normal);<br />
faceB = Oriented.FaceTowards(bodyB, -normal);<br />
Die FaceTowards() Methode wird von der statischen Hilfsklasse Oriented<br />
zur Verfügung gestellt. Die Winkelbestimmung erfolgt über den Vergleich der Skalarprodukte mit<br />
der Normale jeder Fläche.<br />
public static class Oriented<br />
{<br />
...<br />
public static ISurface FaceTowards(IObject Object, Vector3 Normal)<br />
{<br />
return Object.Faces[FaceIndexTowards(Object, Normal)];<br />
}<br />
public static int FaceIndexTowards(IObject Object, Vector3 Normal)<br />
{<br />
float last = 0f, least = 0f;<br />
int index = -1;<br />
for (int i = 0; i < Object.Faces.Length; i++)<br />
{<br />
last = Vector3.Dot(Oriented.Normal(Object.Faces[i]), Normal);<br />
if (last < least)<br />
{<br />
least = last;<br />
index = i;<br />
}<br />
}<br />
}<br />
public static float Distance(ISurface Surface, Vector3 Vector)<br />
{<br />
return Vector3.Dot(Vector - (Corners(Surface)[0] + Surface.Object.Position),<br />
Normal(Surface));<br />
}<br />
...<br />
}<br />
Sobald die zugewandten Flächen feststehen, wird für jeden der Eckpunkte<br />
überprüft, ob er die Fläche des anderen Objektes durchdringt. Die<br />
Koordinaten jedes Eckpunktes sind relativ zu ihrem Objekt<br />
angegeben, daher wird zunächst die absolute Position bestimmt.<br />
Die Distanzermittlung behandelt die Fläche allerdings als Ebene<br />
von unbegrenzter Ausdehnung, daher muss, selbst wenn<br />
Seite 22 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
eine Durchdringung festgestellt wird, der Eckpunkt danach noch einmal mit der Bounding Sphere<br />
des anderen Objekts verglichen werden, um sicherzustellen, dass er nicht hinter der Ebene aber<br />
außerhalb des Objektes liegt. Die Normale der Fläche, mit der die Durchdringung geprüft wurde,<br />
sowie die ermittelte Distanz werden zur späteren Behandlung im Kollisionspunkt gespeichert. Die<br />
weiteste Eindringtiefe wird außerdem zum Bestimmen des Kollisionsstatus anhand der<br />
Toleranzgrenzen des Environment herangezogen.<br />
cpoints.Clear();<br />
normal = Oriented.Normal(faceB);<br />
radius = (float)Math.Pow(bodyB.Radius, 2) + environment.Tolerance;<br />
foreach (Vector3 corner in Oriented.Corners(faceA))<br />
{<br />
point = corner + bodyA.Position;<br />
distance = Oriented.Distance(faceB, point);<br />
if (distance < environment.Tolerance && (point -<br />
bodyB.Position).LengthSq() < radius)<br />
{<br />
cpoints.Add(new CollisionPoint(point, normal, distance));<br />
if (distance < depth) depth = distance;<br />
}<br />
}<br />
normal = -Oriented.Normal(faceA);<br />
radius = (float)Math.Pow(bodyA.Radius, 2) + environment.Tolerance;<br />
foreach (Vector3 corner in Oriented.Corners(faceB))<br />
{<br />
point = corner + bodyB.Position;<br />
distance = Oriented.Distance(faceA, point);<br />
if (distance < environment.Tolerance && (point -<br />
bodyA.Position).LengthSq() < radius)<br />
{<br />
cpoints.Add(new CollisionPoint(point, normal, distance));<br />
if (distance < depth) depth = distance;<br />
}<br />
}<br />
if (depth < -environment.Tolerance)<br />
currentState = State.PENETRATING;<br />
else if (depth < environment.Tolerance)<br />
currentState = State.COLLIDING;<br />
else<br />
currentState = State.CLEAR;<br />
Diese Überprüfung wird in der BodyCollision für jedes Objekt einmal<br />
ausgeführt. In der WallCollision findet die Überprüfung nur einmal statt,<br />
da nur ein Objekt mit einer Fläche verglichen werden muss, außerdem<br />
fällt der Abgleich mit der Bounding Sphere weg, da die Wand tatsächlich<br />
eine unbegrenzte Ebene ist. Sobald alle Kollisionspunkte gefunden<br />
wurden und der Gesamtstatus der Kollision festgestellt ist, kann die<br />
Kollisionserkennung abgeschlossen werden.<br />
Kollisionsbehandlung<br />
Nachdem eine Kollision festgestellt wurde, kann sie behandelt und aufgelöst werden. Auch die<br />
Handle() Methode ist genau wie Detect() eine abstrakte Methode, die von jeder Kollisionsklasse<br />
selbstständig implementiert ist. Dem obigen Beispiel folgend wird hier die Behandlung einer<br />
BodyCollision gezeigt, da die WallCollision nur eine Vereinfachung darstellt, bei der eines der<br />
Objekte (die Wand) als unbeweglich und von unendlicher Masse behandelt<br />
wird.<br />
Vor der Kollisionsbehandlung muss geprüft werden, ob überhaupt eine<br />
Seite 23 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Berührung stattfindet bzw. erkannt wurde. Falls nicht, wird die Kollisionsbehandlung an dieser<br />
Stelle abgebrochen. Andernfalls findet die Deklaration von temporären Variablen statt, die später<br />
verwendet werden, und die Behandlung wird fortgesetzt.<br />
if (currentState < State.COLLIDING) return;<br />
Vector3 pointA, pointB, normal, relativeVelocity;<br />
float normalVelocity;<br />
Die folgenden Abschnitte werden nacheinander für jeden Kollisionspunkt durchlaufen, falls mehr<br />
als einer gefunden wurde.<br />
Zunächst wird die relative Geschwindigkeit der beiden Objekte am<br />
Kollisionspunkt bestimmt. Dazu werden erst die Vektoren vom<br />
Objektmittelpunkt zum Kollisionspunkt festgelegt, um die<br />
Rotationsgeschwindigkeit an dieser Stelle errechnen zu können. Die<br />
Translationsgeschwindigkeit ist an allen Stellen gleich, daher kann sie<br />
direkt addiert werden.<br />
pointA = cpoint.Position - bodyA.Position;<br />
pointB = cpoint.Position - bodyB.Position;<br />
normal = cpoint.Normal;<br />
relativeVelocity = (bodyA.Velocity +<br />
Vector3.Cross(bodyA.Rotation, pointA))<br />
- (bodyB.Velocity + Vector3.Cross(bodyB.Rotation,<br />
pointB));<br />
Seite 24 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Die relative Geschwindigkeit wird dann per Skalarprodukt auf die Kollisionsnormale aufgetragen.<br />
Falls sich die beiden Objekte in Richtung der Normalen nicht aufeinander zubewegen (z.B. weil<br />
sich der Kollisionspunkt am Rand befindet und die Objekte sich nur streifen) braucht keine weitere<br />
Rechnung zu erfolgen und die Behandlung kann mit dem nächsten Punkt fortgesetzt werden.<br />
normal);<br />
normalVelocity = Vector3.Dot(relativeVelocity,<br />
if (normalVelocity > 0f) continue;<br />
Stoßen die Objekte zusammen, wird als nächstes unter Hinzunahme der<br />
Massen und Trägheitsmomente der beiden Objekte sowie der<br />
Oberflächeneigenschaften der kollidierenden Flächen der Impuls<br />
errechnet, der zwischen den Objekten übertragen wird. Wichtig hierbei<br />
sind einerseits die Elastizität und andererseits der Bias-Faktor.<br />
Elastizität gibt an, wie viel Energie bei einem Stoß erhalten bleibt. Eine<br />
Elastizität von 1 wäre ein komplett elastischer Stoß, bei dem keine<br />
Energie verloren geht, wohingegen 0 einen komplett plastischen Stoß<br />
darstellt, bei dem die Objekte ineinander stecken bleiben. Werte über 1<br />
oder unter 0 kommen in der Natur nicht vor, wären aber denkbar, wenn Objekte bei einem<br />
Zusammenstoß Energie gewinnen oder beschleunigt ineinander einsinken. Der Bias-Faktor ist eine<br />
Größe, die in der Natur nicht existiert, die aber zum Ausgleich von Ungenauigkeiten hinzugezogen<br />
wird, um die Simulation stabil zu halten. Da eine Kollision immer nur innerhalb bestimmter<br />
Toleranzgrenzen festgestellt werden kann, ist der Bias-Faktor dafür zuständig, die Objekte stärker<br />
auseinander zu befördern, je tiefer sie ineinander eindringen (ein Zustand, der bei realen Körpern<br />
eigentlich nicht vorkommen kann).<br />
Vector3 impulse = normal * (<br />
-((1 + faceA.Material.Restitution * faceB.Material.Restitution) *<br />
normalVelocity<br />
+ cpoint.Distance * environment.Bias)<br />
/ ((1f / bodyA.Mass + 1f / bodyB.Mass)<br />
+ Vector3.LengthSq(Vector3.Cross(pointA, normal)) / bodyA.Inertia<br />
+ Vector3.LengthSq(Vector3.Cross(pointB,<br />
normal)) / bodyB.Inertia));<br />
Wenn der Impuls feststeht, muss er nur noch auf beide Objekte angewandt<br />
werden. Zur Bestimmung der Rotationsbewegung spielt neben dem<br />
Trägheitsmoment wieder die Lage des Kollisionspunktes relativ zum<br />
Objekt eine Rolle, zur Translation kann der Impuls (nach Verrechung mit<br />
der Objektmasse) direkt addiert werden. Zu beachten ist, dass der Impuls<br />
auf das zweite Objekt negativ angewendet wird, da dieses auch zur<br />
Bestimmung der Relativgeschwindigkeit mit negativem Vorzeichen<br />
herangezogen wurde.<br />
Seite 25 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
bodyA.Velocity += impulse * (1f / bodyA.Mass);<br />
bodyA.Rotation += Vector3.Cross(pointA, impulse) * (1f / bodyA.Inertia);<br />
bodyB.Velocity -= impulse * (1f / bodyB.Mass);<br />
bodyB.Rotation -= Vector3.Cross(pointB, impulse) *<br />
(1f / bodyB.Inertia);<br />
Sobald diese Schleife für alle Kollisionspunkte durchlaufen wurde, kann<br />
der Status der Kollision auf SEPARATING gesetzt werden. Danach erfolgt<br />
zum Abschluss der Kollisionsbehandlung nur noch eine Überprüfung auf<br />
die Energie der Objekte. Da bei jedem Stoß Energie verloren gehen kann,<br />
wird nach jeder Kollision überprüft, welche der Objekte die Schwelle des<br />
Environment unterschreiten, und werden in den Ruhezustand versetzt, um<br />
Rechenleistung zu sparen und dem Framework anzuzeigen, dass diese<br />
Würfel nun abgelesen werden können.<br />
true;<br />
true;<br />
if (bodyA.Resting || bodyB.Resting)<br />
{<br />
if (Oriented.Energy(bodyA) < environment.MinEnergy) bodyA.Resting =<br />
}<br />
else bodyA.Resting = false;<br />
if (Oriented.Energy(bodyB) < environment.MinEnergy) bodyB.Resting =<br />
else bodyB.Resting = false;<br />
Um zu verhindern, dass zwei Objekte in der Luft zusammenstoßen und dann hängen bleiben, weil<br />
ihre Energie zu niedrig ist, wird der Ruhezustand nur aktiviert, wenn das Objekt mit einer<br />
unbeweglichen Wand (s. WallCollision) oder einem anderen ruhenden Objekt kollidiert.<br />
Andererseits können auf diese Weise auch bereits ruhende Objekte von anderen angestoßen und aus<br />
ihrem Ruhezustand heraus befördert werden. Der Code zur Berechnung der Energie wird auch hier<br />
von der Hilfsklasse Oriented zur Verfügung gestellt und folgt einer einfachen physikalischen<br />
Formel für kinetische Energie.<br />
public static class Oriented<br />
{<br />
...<br />
public static float Energy(IObject Object)<br />
{<br />
return 0.5f * Object.Mass * Object.Velocity.LengthSq()<br />
+ 0.5f * Object.Inertia * Object.Rotation.LengthSq();<br />
}<br />
...<br />
}<br />
Seite 26 / 37
Funktionsweise der 3d-Engine<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Das Direct3D Renderer Modul besteht aus nur einer Klasse. Es besitzt einige Interfaces zur<br />
Kommunikation mit anderen Programmteilen – in diesem Fall mit dem Dice Framework. Auch hier<br />
sind Objekte (Würfel) noch in Surfaces aufgeteilt.<br />
Der grundsätzliche Aufbau eines Direct3D Renderes ist immer sehr ähnlich.<br />
Es beginnt mit der Initialisierung, wo Auflösung, Farbtiefe und einige 3D Parameter gesetzt<br />
werden. Dann werden einer oder mehrere sogenannte VertexBuffers generiert. Und dann gehört<br />
natürlich noch eine „Render“-Funktion dazu. Diese Teile des Renderers werden im Folgenden<br />
genauer beschrieben.<br />
Initialisierung<br />
PresentParameters presentParams = new PresentParameters(); //Datenstruktur für DirectX<br />
DisplayMode dispMode = Manager.Adapters.Default.CurrentDisplayMode; //Aktueller<br />
Displaymodus<br />
presentParams.AutoDepthStencilFormat = DepthFormat.D16; // Bit-Größe des Z-Buffers<br />
presentParams.EnableAutoDepthStencil = true;<br />
// Z-Buffer aktivieren<br />
presentParams.BackBufferCount = 1;<br />
//
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
device.RenderState.ZBufferEnable = true;<br />
device.RenderState.CullMode = Cull.CounterClockwise;<br />
device.Lights[0].Type = LightType.Point;<br />
device.Lights[0].Diffuse = Color.White;<br />
device.Lights[0].AmbientColor = new ColorValue(0, 1, 0);<br />
device.Lights[0].Position = new Vector3(0.49f, 0.49f, 0.49f);<br />
device.Lights[0].Attenuation0 = 0;<br />
device.Lights[0].Attenuation1 = 0.75f;<br />
device.Lights[0].Attenuation2 = 0;<br />
device.Lights[0].Range = 100;<br />
device.Lights[0].Specular = Color.White;<br />
device.Lights[0].Update();<br />
device.Lights[0].Enabled = true;<br />
device.RenderState.Ambient = Color.FromArgb(0x20, 0x20, 0x20);<br />
device.RenderState.Lighting = true;<br />
Backface Culling ist ein Verfahren, um jene Polygone, die gerade nicht sichtbar sind (weil sie auf<br />
der Rückseite des Würfels sind) nicht mit zu rendern. Ob ein Polygon ausgeblendet wird, wird<br />
durch die Reihenfolge der Eckpunkte angegeben. Hier speziell werden Polygone ausgelassen,<br />
dessen Punkte (nach der Transformation in Bildschirmkoordinaten) gegen den Uhrzeigersinn<br />
verteilt sind.<br />
Für die Beleuchtung wird eine Lichtquelle definiert. Diese enthält Typ, Farbe, Position und<br />
Reichweite. Es wird nach dem Aktivieren des Lichts und der Beleuchtung (Lighting) von DirectX<br />
automatisch angewendet. Dieses einfache Beleuchtungsmodell kann keine Schatten werfen, sondern<br />
nur die Helligkeit von Polygonen ändern, je nach dem, wie genau die Normale des Polygons zur<br />
Lichtquelle ausgerichtet ist.<br />
Nun muss nur noch eine „Kamera“ gesetzt werden. Diese besteht aus ein paar Matrizen, welche,<br />
multipliziert mit den eigentlich zu rendernden Daten multipliziert werden, um entsprechend die<br />
Position und Drehung einer Kamera zu imitieren. Auch hier macht DirectX die meiste Arbeit :<br />
device.Transform.View =<br />
Matrix.LookAtRH(<br />
new Vector3(-0.5f, 0.0f, +0.5f),<br />
new Vector3(0.0f, 0.0f, 0.0f),<br />
new Vector3(0.0f, 0.0f, 1.0f));<br />
device.Transform.Projection =<br />
Matrix.PerspectiveFovRH(<br />
(float)Math.PI / 2.0f,<br />
(float)targetForm.ClientSize.Width/(float)targetForm.ClientSize.Height,<br />
0.1f, 40.0f );<br />
Seite 28 / 37
Vertexbuffer<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Dies sind schreibgeschützte Arrays, welche 3D-Geometrie und Texturkoordinaten enthalten. Sie<br />
werden nach der Erzeugung direkt in den Grafikspeicher geladen und können dadurch schneller<br />
abgerufen werden beim Rendern. Unter Windows Mobile mit dem Compact .NET Framework ist<br />
dies die einzige Möglichkeit, überhaupt etwas zu rendern.<br />
vb = new VertexBuffer(<br />
typeof(CustomVertex.PositionNormalTextured),<br />
numvertices, device, Usage.WriteOnly,<br />
CustomVertex.PositionNormalTextured.Format, Pool.Managed);<br />
Der Vertexbuffer muß zum Füllen mit Daten einmal gesperrt werden (Lock), kann dann wie ein<br />
Array beschrieben werden, und natürlich muß er wieder freigegeben werden (Unlock).<br />
CustomVertex.PositionNormalTextured[] v =<br />
(CustomVertex.PositionNormalTextured[])vb.Lock(0, 0);<br />
...<br />
v[c] = objs[j].Surfaces[i].Points[k];<br />
...<br />
vb.Unlock();<br />
Seite 29 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Rendern<br />
1 public void Render()<br />
2 {<br />
3 device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Blue, 1.0f, 0);<br />
4 device.BeginScene();<br />
5<br />
6 if (vb != null)<br />
7 {<br />
8 device.VertexFormat = CustomVertex.PositionNormalTextured.Format;<br />
9 device.SetStreamSource(0, vb, 0);<br />
10<br />
11 foreach (DrawingInfoObject dio in drInfo)<br />
12 {<br />
13 Matrix m;<br />
14 m = dio.o.Orientation;<br />
15 m.Multiply(Matrix.Translation(dio.o.Position));<br />
16<br />
17 device.Transform.World = m;<br />
18<br />
19 foreach (DrawingInfoSurface dis in dio.Surfaces)<br />
20 {<br />
21 device.SetTexture(0, dis.tex);<br />
22 device.DrawPrimitives(PrimitiveType.TriangleFan, dis.start, dis.length<br />
- 2);<br />
23 }<br />
24 }<br />
25 }<br />
26 device.EndScene();<br />
27 device.Present();<br />
28 }<br />
Dies ist der vollständige Code, der die Würfel in 3D rendert.<br />
In Zeile 3 wird der BackBuffer auf eine blaue Hintergrundfarbe geleert – ebenso wird der Z-Buffer<br />
geleert.<br />
Zeile 4 leitet den Bereich ein, in dem alle Renderbefehle stattfinden müssen. Für die meisten<br />
Programme ist es effizienter, wenn alle Polygone in nur einem “Renderbereich” zwischen<br />
device.BeginScene() und device.EndScene() liegen.<br />
In Zeile 8 wird das Vertexformat eingestellt. In diesem Fall Vertices mit Positionsangabe, eine<br />
Normalen und Texturkoordinaten.<br />
Zeile 9 wählt den Vertexbuffer als Datenquelle aus.<br />
In den Zeilen 13 bis 17 findet pro Würfel die genaue Positionierung und Drehung statt.<br />
In Zeile 21 wird pro einzelne Fläche eines Würfels die entsprechende Textur aktiviert.<br />
Zeile 22 ist schließlich der (einzig vorhandene) Befehl, der Polygone zum Rendern an die<br />
Grafikhardware “schickt”.<br />
Der Renderbereich wird in Zeile 26 verlassen, und der wichtigste Befehl in Zeile 27 führt den<br />
Kopiervorgang vom BackBuffer auf den Bildschirm aus.<br />
Seite 30 / 37
Schnittstellen<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Modulbeschreibungen<br />
GUI<br />
Die GUI (Graphical User Interface) ist die Schittstelle zum Benutzer. Sie wird benötigt, da weder<br />
das Framework noch die Physik- oder 3d-Engine Benutzerschnittsllen anbieten. Die GUI stellt in<br />
der aktuellen Fassung nur einen sehr geringen Teil der Funktionalität des Dice-Frameworks dem<br />
Benutzer zur Verfügung. Dies ist allerdings kein direktes Versagen der GUI, sondern eine<br />
Notwendigkeit. Die Funktionalität des Frameworks übersteigt die Anforderungen auf dem PDA,<br />
daher wurde eine Auswahl getroffen.<br />
libDice<br />
Die Kern-Bibliothek. Sie verwaltet die Texturen, Materialien, Objekte, Würfel und die Welt selbst<br />
und stellt diese Daten dann der Physik- und 3d-Engine zur Verfügung. Sie nimmt die Befehle der<br />
GUI entgegen und führt diese entsprechend aus.<br />
libPhysics<br />
Die Physik-Engine. Sie führt sämtliche Bewgungen in der Welt aus, ermittelt alle Kollisionen und<br />
Behandelt diese.<br />
libDXRender<br />
Der DirectX-basierte 3d-Renderer. Enthält die 3d-Engine und sorgt für die optische Darstellung des<br />
Würfelvorgangs.<br />
Seite 31 / 37
Klassendiagramme<br />
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
libDice<br />
Seite 32 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
libPhysics<br />
Seite 33 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
libDXRender<br />
Seite 34 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Screenshots<br />
Seite 35 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Echte Würfel? - Eine Statistik<br />
Eine Frage, die sich nun aufstellt ist : Werden alle Zahlen auf den Würfeln etwa gleich oft<br />
gewürfelt? Zu diesem Zweck wurde das Programm leicht geändert, um schnell viele Würfe zu<br />
absolvieren, und die gewürfelten Ergebnisse in einer Datei herauszuschreiben. Bei 600 Wurf mit<br />
jeder Würfelart wurden folgende Zahlen gewürfelt:<br />
140<br />
120<br />
100<br />
80<br />
60<br />
40<br />
20<br />
0<br />
D6<br />
0 1 2 3 4 5 6<br />
D6<br />
100<br />
90<br />
80<br />
70<br />
60<br />
50<br />
40<br />
30<br />
20<br />
10<br />
0<br />
D8<br />
0 1 2 3 4 5 6 7 8<br />
D8<br />
80<br />
D10<br />
70<br />
D12<br />
70<br />
60<br />
50<br />
40<br />
30<br />
20<br />
10<br />
D10<br />
60<br />
50<br />
40<br />
30<br />
20<br />
10<br />
D12<br />
0<br />
0 1 2 3 4 5 6 7 8 9<br />
0<br />
0 1 2 3 4 5 6 7 8 9 10 11 12<br />
45<br />
40<br />
35<br />
30<br />
25<br />
20<br />
15<br />
10<br />
5<br />
0<br />
D20<br />
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20<br />
D20<br />
Seite 36 / 37
PDA-Programmieren • "<strong>Würfelsimulator</strong>" • SS10 • Simon Brennecke • Ivo Torp • Klaus Manneck<br />
Zukunft<br />
"Dice!" war ursprünglich viel größer und umfassender konzipiert. Aus Zeitgründen wurde aber von<br />
vornherein nur ein Teil der Funktionen angestrebt. Diese Funktionen werden in Zukunft wohl nach<br />
und nach hinzugefügt. Da uns in der Zukunft aber wahrscheinlich kein PDA mehr mit ausreichend<br />
Rechenleistung zur Verfügung stehen wird, wird die Weiterentwicklung von "Dice!" wohl auf dem<br />
PC stattfinden müssen. Das Projekt wird wahrscheinlich auch später ein Teil einer größer<br />
angelegten privaten Projektarbeit im Bereich Pen&Paper-Rollenspiele. Das Teilprojekt "libPhysics"<br />
wird wahrscheinlich noch massiv ausgebaut und nach C++ portiert, um dann Teil unserer<br />
gemeinsamen Diplomarbeit zu werden.<br />
Referenzen<br />
• Wikipedia<br />
• „Physics“ Artikel von Chris Hecker im Game Developer Magazine von März 1997<br />
• Rigid Body Physics Tutorial auf www.xbdev.net<br />
• Das große Tafelwerk - Formelsammlung<br />
• Treiber für PDA von www.htcclassaction.org<br />
• Geräteinformationen von www.xda-developers.com<br />
• Windows Mobile 6.1 von www.htc.com<br />
• Microsoft Developer Network (MSDN)<br />
Seite 37 / 37