02.11.2013 Aufrufe

Würfelsimulator

Würfelsimulator

Würfelsimulator

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!