08.01.2013 Aufrufe

Ray Tracing und Texturierung von Freiformflächen - OPUS Bayern

Ray Tracing und Texturierung von Freiformflächen - OPUS Bayern

Ray Tracing und Texturierung von Freiformflächen - OPUS Bayern

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.

Diplomarbeit<br />

RAY TRACING UND TEXTURIERUNG<br />

VON FREIFORMFLÄCHEN<br />

Fachbereich: Informatik<br />

Verfasser: Josef Scheuer<br />

Betreuer: Prof. <strong>Ray</strong>mond Zavodnik


Vorwort<br />

Diese Diplomarbeit basiert auf den Arbeiten <strong>von</strong> A. Riepl [7] <strong>und</strong> O. Groth [3]. Das Ziel<br />

dabei war die Planung <strong>und</strong> Integration <strong>von</strong> <strong>Freiformflächen</strong> <strong>und</strong> deren <strong>Texturierung</strong> in ein<br />

bestehendes <strong>Ray</strong> <strong>Tracing</strong>-System.<br />

Diese Aufgabenstellung macht den Anschein eines sehr gut abgrenzbaren Themas. Die vielen<br />

Sonderbehandlungen, die diese Objekte erfordern, führten in ihrer Implementierung jedoch zu<br />

so manchem Designproblem. Daraus ergab sich die Notwendigkeit, bereits bestehende Teile<br />

des Systems zu überarbeiten <strong>und</strong> an die neuen Gegebenheiten anzupassen. Nur so war es<br />

möglich zu einem funktionsfähigen Ganzen zu kommen.<br />

Durch diese Arbeit war es mir möglich, sehr tief in den Bereich der Computergraphik einzusteigen<br />

<strong>und</strong> viele Erfahrungen zu sammeln. Daher gilt besonderer Dank meinem Betreuer<br />

Herrn Prof. <strong>Ray</strong>mond Zavodnik, bei dem ich immer ein offenes Ohr für meine Fragen fand,<br />

<strong>und</strong> der mir bei Problemen oft neue Lösungswege aufzeigte.<br />

An dieser Stelle möchte ich mich auch bei Herrn Prof. Dr. Wilhelm Barth bedanken, der diese<br />

Diplomarbeit durch das Zusenden einiger seiner Arbeiten unterstützte.<br />

Marius Strobl <strong>und</strong> Christian Buchberger danke ich für die Bereitstellung <strong>von</strong> Rechenkapazität<br />

zur Erstellung der Beispielbilder. Ohne ihre Hilfe hätten die Berechnungen bei diesen hohen<br />

Auflösungen ein Vielfaches an Zeit in Anspruch genommen.<br />

Josef Scheuer<br />

Regensburg<br />

September, 2004<br />

i


Inhaltsverzeichnis<br />

Einführung v<br />

1 Das <strong>Ray</strong> <strong>Tracing</strong>-Verfahren 1<br />

1.1 Gr<strong>und</strong>legende Funktionsweise . . . . . . . . . . . . . . . . . . . . . . . . . 2<br />

1.2 Beleuchtungsmodelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

1.2.1 Die Phong-Schattierung . . . . . . . . . . . . . . . . . . . . . . . . 3<br />

1.2.2 Das Whitted-Modell . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />

1.2.3 Materialien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />

1.3 Schnittpunkt- <strong>und</strong> Normalenberechnung . . . . . . . . . . . . . . . . . . . . 5<br />

1.3.1 Primitive Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />

1.4 Objekt-Transformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

1.4.1 Instancing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />

1.5 Optimierungstechniken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

1.5.1 Adaptive Depth Control . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

1.5.2 Bo<strong>und</strong>ing Volumes . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />

1.5.3 Hierarchische Bo<strong>und</strong>ing Volumes <strong>und</strong> Octrees . . . . . . . . . . . . 9<br />

2 <strong>Freiformflächen</strong> 10<br />

2.1 Tensorprodukt-Flächen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.2 Bernstein-Polynome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />

2.3 Bézierkurven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.3.1 Algorithmus nach de Casteljau . . . . . . . . . . . . . . . . . . . . . 14<br />

2.3.2 Aufteilung <strong>von</strong> Bezierkurven . . . . . . . . . . . . . . . . . . . . . . 16<br />

2.4 Bezierflächen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

2.4.1 Flächennormalen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18<br />

2.4.2 Aufteilung <strong>von</strong> Bezierflächen . . . . . . . . . . . . . . . . . . . . . 19<br />

2.5 Methoden zur Schnittpunktberechnung . . . . . . . . . . . . . . . . . . . . . 20<br />

ii


INHALTSVERZEICHNIS iii<br />

2.5.1 Das Newton Näherungs-Verfahren . . . . . . . . . . . . . . . . . . . 20<br />

2.5.1.1 Konvergenzprobleme . . . . . . . . . . . . . . . . . . . . 21<br />

2.5.2 Vorverarbeitung der Flächen . . . . . . . . . . . . . . . . . . . . . . 22<br />

2.5.2.1 Das Parallelepiped als Hüllkörper . . . . . . . . . . . . . . 22<br />

2.5.2.2 Kriterien zur Flächenaufteilung . . . . . . . . . . . . . . . 24<br />

2.5.2.3 Organisation der Teilflächen . . . . . . . . . . . . . . . . . 25<br />

2.5.2.4 Aufbau des Suchbaumes . . . . . . . . . . . . . . . . . . . 26<br />

2.5.3 Schnittpunktermittlung . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />

2.5.3.1 Durchsuchen der Flächensegmente . . . . . . . . . . . . . 27<br />

2.5.3.2 Schnittberechnung der Hüllkörper . . . . . . . . . . . . . 28<br />

2.5.3.3 Ermittlung des Iterationsstartpunkts . . . . . . . . . . . . . 29<br />

2.5.3.4 Die Bedeutung der Gr<strong>und</strong>fläche . . . . . . . . . . . . . . . 31<br />

2.5.3.5 Der Iterationsablauf . . . . . . . . . . . . . . . . . . . . . 32<br />

2.6 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

3 <strong>Texturierung</strong> 35<br />

3.1 Arbeitsweise <strong>von</strong> <strong>Texturierung</strong>ssystemen . . . . . . . . . . . . . . . . . . . . 36<br />

3.2 Arten <strong>von</strong> Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />

3.2.1 2D Texture Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . 37<br />

3.2.1.1 Die Bitmap-Textur . . . . . . . . . . . . . . . . . . . . . . 38<br />

3.2.1.2 Environment Mapping . . . . . . . . . . . . . . . . . . . . 39<br />

3.2.2 Prozedurale Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />

3.2.2.1 Noise - Der Rauschgenerator . . . . . . . . . . . . . . . . 41<br />

3.2.2.2 Die Karo-Textur . . . . . . . . . . . . . . . . . . . . . . . 43<br />

3.2.2.3 Die Klotz-Textur . . . . . . . . . . . . . . . . . . . . . . . 44<br />

3.2.2.4 Die Marmor-Textur . . . . . . . . . . . . . . . . . . . . . 44<br />

3.2.2.5 Die Holz-Textur . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3.2.3 Bump Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

3.2.3.1 Die Bump-Funktion . . . . . . . . . . . . . . . . . . . . . 48<br />

3.2.3.2 Grenzen des Bump Mapping . . . . . . . . . . . . . . . . 49<br />

3.3 Das Textur-System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />

3.4 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />

4 Implementierung 51<br />

4.1 Allgemeine Funktionsweise . . . . . . . . . . . . . . . . . . . . . . . . . . . 52


INHALTSVERZEICHNIS iv<br />

4.1.1 Vorbereitungen <strong>und</strong> Parsing . . . . . . . . . . . . . . . . . . . . . . 52<br />

4.1.2 Ablauf der Bildberechnung . . . . . . . . . . . . . . . . . . . . . . . 53<br />

4.2 Die Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

4.2.1 Objekt-Transformation . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

4.2.2 Ablauf objektinterner Berechnungen . . . . . . . . . . . . . . . . . . 56<br />

4.3 Die Texturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

4.3.1 Das Textur-System . . . . . . . . . . . . . . . . . . . . . . . . . . . 57<br />

4.3.2 Ablauf interner Textur-Berechnungen . . . . . . . . . . . . . . . . . 58<br />

4.4 Änderungen am Gesamtsystem . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

4.4.1 Die Szenendefinition . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

4.4.1.1 Die Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

4.4.1.2 Definitionen . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

4.4.1.3 Include-Dateien . . . . . . . . . . . . . . . . . . . . . . . 60<br />

4.4.2 Die Bildformate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60<br />

A Diagramme 61<br />

B Parameterliste für den Programmstart 66<br />

C Szenen-Definition 67<br />

D C++ Quelltexte 73<br />

Erklärung 211


Einführung<br />

Computergenerierte Bilder <strong>und</strong> Animationen gehören mittlerweile zum Alltag. Man ist es<br />

gewohnt, in Werbung, Film <strong>und</strong> Fernsehen synthetisch erzeugte Szenen zu sehen, die zum<br />

Teil sehr nahe an die Grenze zur Photorealität reichen.<br />

Der Aufwand den jedoch die Erstellung solcher Sequenzen erfordert bleibt meist im Verborgenen.<br />

Abhängig <strong>von</strong> der Komplexität der Szene <strong>und</strong> dem eingesetzten Verfahren können die<br />

Berechnungen dazu auch Rechnerfarmen über mehrere Wochen beschäftigen.<br />

Die Berechnungsmethoden, die dabei zum Einsatz kommen <strong>und</strong> der Versuch damit die Realität<br />

so gut wie möglich nachzubilden, machen die Computergraphik nach wie vor zu einem<br />

der interessantesten Gebiete im weiten Feld der Computerwissenschaften.<br />

<strong>Ray</strong> <strong>Tracing</strong> als eines der Verfahren ist Thema dieser Diplomarbeit. Speziell die Berechnung<br />

<strong>von</strong> <strong>Freiformflächen</strong> <strong>und</strong> deren <strong>Texturierung</strong> stellen dabei die Schwerpunkte dar.<br />

Kapitel 1 beschreibt die elementare Funktionalität des <strong>Ray</strong> <strong>Tracing</strong>. Die Gr<strong>und</strong>lagen dazu<br />

waren die Voraussetzungen für dieser Arbeit, weshalb auf diese auch nur kurz eingegangen<br />

wird.<br />

Kapitel 2 behandelt die Berechnung <strong>von</strong> <strong>Freiformflächen</strong>. Besonders in Verbindung mit<br />

<strong>Ray</strong> <strong>Tracing</strong> werden dabei meist stark optimierte Verfahren eingesetzt. Die hier beschriebene<br />

Methode basiert auf einer Arbeit <strong>von</strong> W. Barth <strong>und</strong> W. Stürzlinger.<br />

Kapitel 3 erläutert die wesentlichen Vorgehensweisen zur <strong>Texturierung</strong> in Graphiksystemen.<br />

Insbesondere die Integration in das vorliegende <strong>Ray</strong> <strong>Tracing</strong>-Systeme ist Thema dieses<br />

Abschnittes.<br />

Kapitel 4 stellt Details zur Implementierung <strong>und</strong> den Abläufen während den Berechnungen<br />

dar. Dabei wird auch auf die Modifikationen <strong>und</strong> Erweiterungen des zu Gr<strong>und</strong>e gelegten <strong>Ray</strong><br />

<strong>Tracing</strong>-Systems eingegangen.<br />

v


Kapitel 1<br />

Das <strong>Ray</strong> <strong>Tracing</strong>-Verfahren<br />

Das Verfahren des <strong>Ray</strong> <strong>Tracing</strong> ist ein sehr intuitives Vorgehen zur synthetischen Erzeugung<br />

photorealistischer Szenen. Die ersten Entwicklungen in diese Richtung gehen auf T. Whitted<br />

in den späten 70’er Jahren zurück. Seit dieser Zeit wurden die dabei verwendeten Algorithmen<br />

kontinuierlich verbessert <strong>und</strong> die Berechnungen durch viele Tricks beschleunigt, beruhen<br />

jedoch nach wie vor auf den ursprünglichen Konzepten: Die Farbberechnung durch die Verfolgung<br />

<strong>von</strong> Lichtstrahlen - vom Beobachter aus, zu den Objekten der Szene.<br />

Eine vollständige Beschreibung der Verfahren ist in diesem Rahmen nicht möglich <strong>und</strong> auch<br />

nicht Ziel dieser Arbeit. Da diese auf <strong>Ray</strong> <strong>Tracing</strong> aufbaut, soll hier dennoch ein kleiner Einblick<br />

in die Algorithmen <strong>und</strong> Techniken gegeben werden. Für weitreichendere Informationen<br />

sei hier allerdings auf auf die Diplomarbeit <strong>von</strong> Angela Riepl [7] <strong>und</strong> viele gute Bücher zu<br />

diesem Thema (siehe Literaturverzeichnis im Anhang) verwiesen.


KAPITEL 1. DAS RAY TRACING-VERFAHREN 2<br />

1.1 Gr<strong>und</strong>legende Funktionsweise<br />

Licht ist eine gr<strong>und</strong>legende Voraussetzung für ein sichtbares Bild. In der Realität durchlaufen<br />

Unmengen <strong>von</strong> Lichtstrahlen, ausgehend <strong>von</strong> Lichtquellen den Raum <strong>und</strong> breiten sich gemäß<br />

den Regeln der Physik durch Strahlung, Brechung <strong>und</strong> Reflexion aus.<br />

<strong>Ray</strong> <strong>Tracing</strong> basiert auf der Simulation der Lichtausbreitung nach den Gesetzen der Optik.<br />

Tatsache ist jedoch, dass nur ein winziger Bruchteil des ausgesendeten Lichtes in das Auge des<br />

Beobachters trifft. Es wäre ein unglaublich großer, wenn nicht gar unmöglicher Aufwand alle<br />

Lichtstrahlen der Szene zu verfolgen. Deshalb wird bei diesem Verfahren die gegensätzliche<br />

Richtung des Lichtes verfolgt: Vom Auge des Beobachters ausgehend, durch die Szene.<br />

Für den Aufbau des Bildes wird durch jeden Punkt der Ansichtsebene wenigstens ein Lichtstrahl<br />

in die Szene geschickt. Abbildung 1.1 zeigt diese Situation.<br />

Die während der Verfolgung des Strahles S gesammelten Farbanteile bestimmen die Farbe,<br />

die dieser Pixel im Bild annimmt. Um die entsprechenden Lichtanteile zu finden, werden für<br />

die ausgesendeten Strahlen, im einfachsten Fall, jeweils alle Objekte der Szene auf Treffer<br />

getestet. Bei mehreren Schnittpunkten (wie in der Abbildung P 1, P 2 <strong>und</strong> P 3), entscheidet<br />

die Distanz zum Beobachter <strong>und</strong> es wird der am nächsten liegende Punkt gewählt (hier P 1).<br />

Dadurch löst sich auf elegante <strong>und</strong> einfache Weise das Problem der Hinterschneidungen <strong>und</strong><br />

verdeckten Flächen.<br />

Auch die sehr einfache Berechnung des Schattenwurfes ist eine der Stärken des <strong>Ray</strong> <strong>Tracing</strong>.<br />

Die Entscheidung, ob der Punkt eines getroffenen Objektes im Licht oder Schatten liegt, kann<br />

einfach durch die Verfolgung eines Strahls (hier L1 bzw. L2) in Richtung der Lichtquellen<br />

entschieden werden.<br />

S<br />

P1<br />

L1<br />

Abbildung 1.1: Gr<strong>und</strong>legende Funktionsweise des <strong>Ray</strong> <strong>Tracing</strong><br />

L2<br />

P2<br />

P3


KAPITEL 1. DAS RAY TRACING-VERFAHREN 3<br />

1.2 Beleuchtungsmodelle<br />

Die physikalischen Vorgänge, die beim Auftreffen <strong>von</strong> Licht auf einer Oberfläche stattfinden,<br />

sind entscheidend für das Erscheinungsbild <strong>von</strong> Körpern. Diese Abläufe können durch<br />

komplexe Berechnungsverfahren sehr genau bestimmt werden, erfordern aber auch einen sehr<br />

hohen Aufwand an Rechenkapazität. Daher werden sie meist durch die Näherungsberechnungen<br />

der verschiedenen Beleuchtungsmodelle beschrieben, die das Geschehen mehr oder weniger<br />

gut nachbilden. Auf die dafür durchgeführten Berechnungen sei hier nur in aller Kürze<br />

eingegangen.<br />

1.2.1 Die Phong-Schattierung<br />

Ein einfach <strong>und</strong> sehr schnell zu berechnendes Modell, ist die Schattierung nach Phong 1 . Dabei<br />

handelt es sich um ein rein lokales Beleuchtungsmodell - d.h. in den Berechnungen der<br />

Lichtanteile werden nur die Lichtquellen <strong>und</strong> die Informationen der Oberfläche, wie Material<br />

<strong>und</strong> Farbe berücksichtigt. Berechnungen <strong>von</strong> Transparenz <strong>und</strong> Reflexionen anderer Objekte<br />

sind deshalb nicht möglich. Aus diesem Gr<strong>und</strong> wird das reine Phong-Modell im Bereich des<br />

<strong>Ray</strong> <strong>Tracing</strong>, wenn überhaupt, nur für schnelle Näherungsberechnungen eingesetzt.<br />

Nach Phong wird das Licht dabei in drei Teile aufgespalten:<br />

• Das ambiente Licht hat keinen ausgezeichneten Ursprung oder Richtung. Es stellt vielmehr<br />

eine Art Gr<strong>und</strong>leuchten der Objekte, unabhängig <strong>von</strong> den Lichtquellen dar.<br />

• Der diffuse Lichtanteil berücksichtigt die Lage eines Punktes bezüglich der Lichtquellen.<br />

Daher sind verdunkelte Stellen der Oberfläche durch Schatten unter anderem auf<br />

das Fehlen dieses Lichtanteils zurückzuführen.<br />

• Der spekulare unterscheidet sich vom diffusen Anteil des Lichtes durch zusätzliche<br />

Berücksichtigung des Beobachterstandpunktes. Dadurch entstehen die Glanzeffekte der<br />

Oberflächen. In die Berechnung dieser Komponente fließt auch ein Wert n für die Rauhheit<br />

der Oberfläche mitein.<br />

Dadurch die Summierung <strong>und</strong> Skalierung der einzelnen Lichtkomponenten entsteht die Farbinformation<br />

im berechneten Punkt.<br />

IP hong := kaIa ����<br />

ambient<br />

+ kdId + ksIs(n)<br />

���� � �� �<br />

diffus spekular<br />

(1.1)<br />

Dabei werden durch die Faktoren ka, kd <strong>und</strong> ks sowie dem Wert n, die Eigenschaften des<br />

Materials in die Berechnung miteinbezogen (vgl. Abschnitt 1.2.3).<br />

1 Phong Bui-Tuong: ”Illumination for Computer Generated Pictures”, 1975


KAPITEL 1. DAS RAY TRACING-VERFAHREN 4<br />

1.2.2 Das Whitted-Modell<br />

Das Modell nach Whitted verbessert die Berechnung des sekularen Lichtanteils nach Phong<br />

<strong>und</strong> erweitert die Schattierung um die globalen Lichtanteile der Transparenz <strong>und</strong> Reflexion.<br />

Die Ermittlung dieser Anteile verlangt eine gesonderte Verfolgung der gebrochenen bzw.<br />

reflektierten Lichtstrahlen. Daher lassen sich die dafür notwendigen Berechnungen sehr gut<br />

durch rekursive Verfahren beschreiben <strong>und</strong> implementieren.<br />

Dieses Modell ergänzt die Berechnungen nach Phong durch folgende Lichtanteile:<br />

• Transparente Flächen verursachen durch den Übergang des Lichtstrahls <strong>von</strong> Luft (bzw.<br />

Vakuum) in das transparente Medium, eine Richtungsänderung des Strahls - er wird<br />

gebrochen. Diese Änderung kann durch das Snelliussche Brechungsgesetz, abhängig<br />

vom Brechungsindex η berechnet werden.<br />

• Eine Reflexion des Lichtstrahls tritt bei verspiegelten Oberflächen auf. Dabei wird die<br />

Richtung des einfallenden Strahls gemäß dem Reflexionsgesetz 2 verändert.<br />

Daraus ergibt sich für die erweiterte Berechnung des Whitted-Modells:<br />

IW hitted := kaIa ����<br />

+ kdId ����<br />

ambient diffus<br />

+ ksIs(n)<br />

� �� �<br />

spekular<br />

+ ktIt(η)<br />

� �� �<br />

T ransparenz<br />

+ krIr<br />

����<br />

Reflexion<br />

(1.2)<br />

Die Faktoren kt <strong>und</strong> kr sowie der Brechungsindex η, sind dabei abhängig vom Material.<br />

Das Whitted-Beleuchtungsmodell wird sehr häufig in <strong>Ray</strong> <strong>Tracing</strong>-Systemen eingesetzt. Es<br />

kann schnell ausgewertet werden <strong>und</strong> liefert trotz seiner Einfachheit recht gute Ergebnisse.<br />

1.2.3 Materialien<br />

Die Gewichtung der einzelnen Lichtanteile hängt <strong>von</strong> den Materialeigenschaften der Oberfläche<br />

ab. Deshalb zählen diese bei der Auswertung des Beleuchtungsmodells zu den entscheidenden<br />

Faktoren <strong>und</strong> werden in Form <strong>von</strong> Koeffizienten in den Berechnungen berücksichtigt.<br />

Für die Simulation verschiedener Materialien ist die Wahl der Materialkoeffizienten sehr<br />

wichtig. Besonders an Stellen der Oberfläche an denen Glanzlichter auftreten, beeinflussen<br />

diese Faktoren das Erscheinungsbild des Materials erheblich.<br />

Für gewöhnlich wird bei der Festlegung der Koeffizienten nicht zwischen den Farbkanälen<br />

Rot, Grün <strong>und</strong> Blau unterschieden. Dieses verleiht den Materialien allerdings ein eher plastikartiges<br />

Aussehen.<br />

Durch Unterscheidung der Farbkanäle <strong>und</strong> sorgfältiger Auswahl der Werte, kann eine etwas<br />

realistischere Erscheinung erzielt werden (vgl. [6],[4]).<br />

2 Der Betrag des Einfallswinkels ist gleich dem Betrag des Reflexionswinkels


KAPITEL 1. DAS RAY TRACING-VERFAHREN 5<br />

1.3 Schnittpunkt- <strong>und</strong> Normalenberechnung<br />

Im <strong>Ray</strong> <strong>Tracing</strong> tasten Strahlen die Szene nach darstellbaren Objekten ab. Deshalb sind die<br />

Algorithmen zur Ermittlung <strong>von</strong> Schnittpunkten des Strahls mit den Objekten zentraler Bestandteil<br />

<strong>von</strong> <strong>Ray</strong> <strong>Tracing</strong>-Systemen, denn letztendlich bestimmen diese Berechnungen die<br />

Form, Größe <strong>und</strong> Position <strong>von</strong> allen Objekten der Szene. Um dabei auch die richtigen Berechnungen<br />

durchzuführen, ist natürlich eine Unterscheidung der Körper notwendig. Je nach<br />

Art des Objekts wird ein Algorithmus benötigt, der Schnittpunkte identifizieren <strong>und</strong> berechnen<br />

kann (vgl. [2]).<br />

Eine weitere, sehr wichtige Größe ist die Flächennormale. Diese hat auf die Auswertung des<br />

Beleuchtungsmodells entscheidenden Einfluss. Auch zu deren Ermittlung muss zwischen den<br />

Objekten unterschieden werden <strong>und</strong> erfordert ein jeweils eigenes Verfahren. Dabei ist jeweils<br />

zu beachten, dass die Normale in Richtung des Strahlenursprungs zeigen muss.<br />

1.3.1 Primitive Objekte<br />

Die Verfahren zur Bestimmung <strong>von</strong> Schnittpunkten <strong>und</strong> Normalen einiger Gr<strong>und</strong>körper wurden<br />

aus der Arbeit <strong>von</strong> A. Riepl [7] übernommen, mussten jedoch aufgr<strong>und</strong> einiger Erweiterungen<br />

angepasst werden. Die deshalb veränderten Algorithmen werden im Folgenden kurz<br />

beschrieben.<br />

Die Berechnungen liefern dabei jeweils die Distanz t zum Ursprung o des Strahls 3 .<br />

Daraus errechnet sich der tatsächliche Schnittpunkt durch Einsetzen in die Strahlengleichung<br />

mit dem normierten Vektor d als Richtung des Strahles.<br />

hitP oint := o + td (1.3)<br />

Die Ebene ist eines der am einfachsten zu berechnenden Objekte. In der Normalenform mit<br />

dem Koordinatenursprung (< 0, 0, 0 >) als Aufpunkt <strong>und</strong> dem Basisvektor Z (< 0, 0, 1 >) als<br />

Normale, errechnet sich die Distanz mittels<br />

tplane := −oz<br />

dz<br />

(1.4)<br />

Die Normale ist durch die Annahme bereits festgelegt. Das Vorzeichen <strong>von</strong> t gibt Auskunft<br />

über den Halbraum, in dem der Strahlursprung liegt. Besitzt t ein negatives Vorzeichen, so<br />

muss die Normale negiert werden um in Richtung des Strahlursprungs zu zeigen.<br />

Die Kugel weist bei einem Schnitt in der Regel zwei Schnittpunkte auf. Dabei ist nur der<br />

in positiver Richtung, am nächsten zum Strahlursprung liegende Punkt <strong>von</strong> Bedeutung.<br />

3 Diese zum Teil sehr vereinfachten Rechenschritte gelten nur für die gemachten Annahmen <strong>und</strong> leiten sich<br />

aus den allgemein gültigen Verfahren ab.


KAPITEL 1. DAS RAY TRACING-VERFAHREN 6<br />

Für den Ursprung als Zentrum <strong>und</strong> einem Radius <strong>von</strong> 1 ergeben sich die Distanzen durch<br />

tsphere0,1 := −(d · o) ± � (d · o) 2 − (d · d)(o · o − 1)<br />

d · d<br />

(1.5)<br />

Durch Auswertung des Terms unter der Wurzel - die Diskriminante - können bereits Aussagen<br />

über die Anzahl der Schnittpunkte gemacht werden: Ist die Diskriminante negativ, so verfehlt<br />

der Strahl die Kugel. Bei einem Wert <strong>von</strong> 0 existiert nur eine Lösung <strong>und</strong> der Strahl berührt<br />

die Kugel in einem Punkt. Für alle positiven Werte existieren zwei Lösungen - für den Ein<strong>und</strong><br />

Austrittspunkt des Strahls (vgl. Abbildung 1.2(a)).<br />

Durch die Lage im Ursprung kann zur Bestimmung der Normalen einfach der durch die Strahlengleichung<br />

ermittelte Schnittpunkt als Vektor aufgefasst <strong>und</strong> verwendet werden. Durch den<br />

Radius <strong>von</strong> 1 erübrigt sich auch die Normierung.<br />

Ein Zylinder mit einem Radius <strong>und</strong> einer Höhe <strong>von</strong> 1, wird mit einer Gr<strong>und</strong>stellung auf<br />

der positiven Y -Achse angenommen. (vgl. Abbildung 1.2(b)). Durch einen Schnitt mit dem<br />

Strahl ergeben sich im Allgemeinen die Distanzen zweier Punkte.<br />

tcylinder0,1 := a ± � a2 − b(o2 x + o2 z + 1)<br />

, wobei (1.6)<br />

b<br />

a = −(oxdx + ozdz) <strong>und</strong> b = d 2 x + d 2 z<br />

Auch hier entstehen nur für die reellen Lösungen <strong>von</strong> t, Schnittpunkte mit dem Zylinder.<br />

Diese sind jedoch nur dann gültig, wenn die Y -Koordinate der dadurch festgelegten Punkte<br />

nicht über die Höhe des Zylinders hinausgehen, also o.y + tdy ∈ [0, 1].<br />

Wie in der Abbildung angedeutet, liegt die Normale in einer Ebene parallel zur XZ-Ebene.<br />

Deshalb reicht zu deren Bestimmung die Projektion des Schnittpunktes. Dazu wird der<br />

gewählte Punkt als Vektor behandelt, <strong>und</strong> dessen Y -Koordinate auf 0 gesetzt. Durch einen<br />

Zylinderradius <strong>von</strong> 1 besitzt dieser auch schon die normierte Länge.<br />

Für die Innenseite der Zylinderoberfläche muss die Normale allerdings negiert werden. Dieser<br />

Fall tritt bei unterschiedlichen Vorzeichen der beiden Lösungen (sofern vorhanden) auf.<br />

S<br />

n<br />

P1<br />

−1<br />

(a)<br />

1<br />

Radius = 1<br />

0<br />

−1<br />

P2<br />

1<br />

S<br />

n<br />

P1<br />

y<br />

P2<br />

Radius = 1<br />

Abbildung 1.2: Schnittpunkt- <strong>und</strong> Normalenberechnung bei primitiven Objekten<br />

(a) Kugel. (b) Zylinder.<br />

−1<br />

−1<br />

(b)<br />

1<br />

0<br />

Höhe = 1<br />

1<br />

z<br />

x


KAPITEL 1. DAS RAY TRACING-VERFAHREN 7<br />

1.4 Objekt-Transformationen<br />

Die Algorithmen zur Berechnung <strong>von</strong> Schnittpunkten <strong>und</strong> Normalenvektoren gehen <strong>von</strong> einem<br />

Initialzustand des jeweiligen Objektes aus. Dadurch haben alle definierten Objekte eine<br />

festgelegte Ausrichtung <strong>und</strong> liegen im Ursprung des Koordinatensystems.<br />

Dieser Zustand ist noch nicht optimal. Es soll schließlich möglich sein, die Objekte frei im<br />

Raum anzuordnen, zu drehen <strong>und</strong> zu verzerren. Dazu müssen Objekt-Transformationen in das<br />

System integriert werden.<br />

1.4.1 Instancing<br />

Die Technik des Instancing erlaubt die freie Transformation <strong>von</strong> Objekten. Dazu werden alle<br />

Objekte mit einem eigenen Koordinatensystem ausgestattet. Für die Ermittlung <strong>von</strong> Schnittpunkten<br />

<strong>und</strong> Normalen macht es keinen Unterschied, ob eine Transformation auf das Objekt<br />

oder die dazu inverse Transformation auf den Strahl angewendet wird. Deshalb ist es sinnvoll,<br />

nicht die Punkte der Objektdefinition, sondern den Ursprung <strong>und</strong> den Richtungsvektor des<br />

Strahls zu transformieren. Dadurch wird es auch auf einfache Weise möglich, die Transformationen<br />

auf alle Typen <strong>von</strong> Objekten, völlig unabhängig <strong>von</strong> deren internen Berechnungen,<br />

anzuwenden.<br />

Durch die Transformation des Strahls, werden objektspezifische Berechnungen im standardisierten<br />

Objekt-Koordinatensystem durchgeführt. Dadurch befindet sich die ermittelte Normale<br />

natürlich innerhalb dieses Systems. Die Berechnungen der Schattierung durch das Beleuchtungsmodell<br />

basieren jedoch auf Weltkoordinaten. Aus diesem Gr<strong>und</strong> muss eine Rücktransformation<br />

der Normalen erfolgen (siehe Abbildung 1.3).<br />

Dabei gilt es jedoch einen Punkt zu beachten: Anders als bei Transformation <strong>von</strong> herkömmlichen<br />

Vektoren, muss bei Normalenvektoren die transponierte der dazu inversen Transformation4<br />

angewendet werden:<br />

(1.7)<br />

S<br />

−1<br />

M x S<br />

�nworld = (M −1 ) T �nobject<br />

y<br />

1<br />

0<br />

S<br />

n<br />

1<br />

z<br />

x<br />

−1 T<br />

(M ) x n<br />

Abbildung 1.3: Die Transformation <strong>von</strong> Strahl <strong>und</strong> Normalenvektor<br />

4 Dies ist die gleiche Transformation, mit der bereits der Strahl in das Objekt-System überführt wurde<br />

n


KAPITEL 1. DAS RAY TRACING-VERFAHREN 8<br />

1.5 Optimierungstechniken<br />

<strong>Ray</strong> <strong>Tracing</strong> ist ein recht intuitives Verfahren zur Erzeugung photorealistischer Bilder, das<br />

trotz seiner Einfachheit recht gute Ergebnisse liefern kann. Leider fällt dieses Verfahren allerdings<br />

auch durch seinen enormen Bedarf an Rechenleistung auf. Untersuchungen am Algorithmus<br />

zeigen, dass dabei der weitaus größte Teil, etwa 95%, auf die Suche <strong>und</strong> die Berechnung<br />

<strong>von</strong> Schnittpunkten entfällt. Daher sind Möglichkeiten zur Optimierung des Vorgehens<br />

gefragt.<br />

Es gibt viele Ansätze, die alle mehr oder weniger zur Verbesserung des Laufzeitverhaltens<br />

beitragen. Die dabei am häufigsten eingesetzten Methoden beruhen auf den Prinzipien der<br />

Raumaufteilung <strong>und</strong> Bewertung der Relevanz <strong>von</strong> Farbanteilen.<br />

Im Folgenden wird auf die wichtigsten Verfahren kurz eingegangen: Adaptive Depth Control,<br />

Bo<strong>und</strong>ing Volumes <strong>und</strong> Octrees.<br />

1.5.1 Adaptive Depth Control<br />

Beleuchtungsmodelle ermitteln die Farbinformation eines Punktes durch Aufspaltung <strong>und</strong><br />

getrennte Berechnung der Lichtanteile (vgl. Abschnitt 1.2). Die Anteile durch Transparenz<br />

<strong>und</strong> Reflexion werden dabei durch rekursive Verfolgung der gebrochenen bzw. reflektierten<br />

Strahlen berechnet. Verspiegelte Flächen, zwischen denen Lichtstrahlen unendlich hin- <strong>und</strong><br />

her-reflektiert werden, würden dadurch eine Endlosrekursion verursachen. Deshalb muss eine<br />

Beschränkung der Rekursionstiefe definiert werden, die bei erreichen der Grenze einen<br />

weiteren Abstieg verhindert.<br />

Während der Verfolgung des Strahls geht, aufgr<strong>und</strong> der Materialeigenschaften, durch Streuung<br />

<strong>und</strong> Dämpfung Leuchtkraft verloren. Darum nimmt der Beitrag, den ein weiterverfolgter<br />

Strahl zur Farbinformation des Bildpunktes liefert, stetig ab.<br />

Bei Einsatz <strong>von</strong> Adaptive Depth Control, wird dieser Beitrag überwacht. Sinkt dieser unter<br />

einen festgelegten Schwellwert, ist der Lichtanteil zu gering <strong>und</strong> die Verfolgung des Strahls<br />

wird vorzeitig abgebrochen. Dadurch werden sehr lichtschwache Strahlen nicht bis zur maximalen<br />

Rekursionstiefe verfolgt <strong>und</strong> die dazu notwendigen Berechnungen vermieden.<br />

1.5.2 Bo<strong>und</strong>ing Volumes<br />

Der weitaus größte Teil des Rechenaufwands wird beim <strong>Ray</strong> <strong>Tracing</strong> durch die Suche nach<br />

Schnittpunkten verursacht. Da für jeden Strahl alle Objekte der Szene auf Schnittpunkte getestet<br />

werden müssen, liegt das größte Optimierungspotential im Algorithmus der Schnittpunktsuche.<br />

Der für den Test eines Objektes notwendige Rechenaufwand hängt <strong>von</strong> der Komplexität der<br />

Schnittpunktberechnung ab. Dieser Aufwand unterscheidet sich zwischen den verschiedenen<br />

Objekten zum Teil erheblich. Bei Körpern wie Ebenen oder Kugeln sind Schnittpunkte sehr<br />

einfach <strong>und</strong> schnell zu berechnen. Für komplexere Objekte, wie <strong>Freiformflächen</strong> kann diese<br />

Auswertung jedoch sehr viel mehr Rechenzeit beanspruchen.


KAPITEL 1. DAS RAY TRACING-VERFAHREN 9<br />

Die Technik der Bo<strong>und</strong>ing Volumes basiert auf der Idee, komplexe Objekte in virtuelle, einfach<br />

zu berechnende Körper einzuschließen. Bei der Überprüfung auf Schnittpunkte wird erst<br />

der Hüllkörper auf Treffer getestet. Verläuft dieser Test negativ, verfehlt der Strahl den umgebenden<br />

Körper <strong>und</strong> kann so auch das darin eingeschlossene Objekt nicht schneiden.<br />

Dadurch ist auch bei sehr aufwändig zu berechnenden Körpern eine sehr schnelle Überprüfung<br />

möglich <strong>und</strong> eine vollständige Berechnung des Schnittpunktes muss nur bei Bedarf<br />

durchgeführt werden.<br />

Zur optimalen Ausnutzung dieses Verfahrens, müssen als Bo<strong>und</strong>ing Volume verwendete Objekte<br />

folgende Anforderungen erfüllen:<br />

• Komplette Umhüllung: Das eingeschlossene Objekt muss komplett im Hüllkörper enthalten<br />

sein.<br />

• Enge Umschliessung: Die Hülle sollte möglichst eng am Objekt anliegen. Dadurch werden<br />

unnötige Schnittpunktberechnungen vermieden.<br />

• Schneller Test: Der Hüllkörper sollte möglichst schnell auf Treffer getestet werden<br />

können.<br />

Um diese Forderungen zu erfüllen, sollten die als Bo<strong>und</strong>ing Volume verwendeten Objekte<br />

passend zu den Formeigenschaften der umhüllten Körper gewählt werden. Zu den dazu am<br />

häufigsten eingesetzten Objekten zählen Kugeln, Quader <strong>und</strong> Parallelepipeds.<br />

1.5.3 Hierarchische Bo<strong>und</strong>ing Volumes <strong>und</strong> Octrees<br />

Die Hierarchische Organisation erweitert das Konzept der Bo<strong>und</strong>ing Volumes um die Information<br />

der geometrischen Anordnung <strong>von</strong> Objekten innerhalb der Szene. Darunter ist eine<br />

rekursive Verschachtelung <strong>von</strong> Bo<strong>und</strong>ing Volumes zu verstehen, die es ermöglicht, Objekte,<br />

gruppiert nach ihrer Lage in der Szene, zusammenzufassen. Durch diese Art der Raumunterteilung<br />

ist es möglich, die Körper für die Schnittpunktberechnungen gezielt auszuwählen <strong>und</strong><br />

Objekte, die in nicht betroffenen Bereichen der Szene liegen, <strong>von</strong> vorn herein auszuschließen<br />

<strong>und</strong> bei den Schnittpunkttests zu übergehen.<br />

Eines der Verfahren, die auf diese Strategie aufbauen, ist der Octree. Diese Art der Optimierung<br />

wurde bereits im Rahmen der Diplomarbeit <strong>von</strong> Angela Riepl [7] beschrieben <strong>und</strong><br />

implementiert. Deshalb sei hier nicht näher darauf eingegangen.<br />

Ein zum Octree sehr ähnliches Verfahren wird bei der Schnittpunktberechnung <strong>von</strong> <strong>Freiformflächen</strong><br />

benutzt <strong>und</strong> wird im Kapitel 2, Abschnitt 2.5.2.3 eingehend beschrieben.


Kapitel 2<br />

<strong>Freiformflächen</strong><br />

Bei der Modellierung <strong>von</strong> Szenen, die dem großen Vorbild Realität möglichst nahe kommen<br />

wollen, spielen gekrümmte <strong>und</strong> unregelmäßige Oberflächen eine entscheidende Rolle. Es besteht<br />

die Möglichkeit, diese Art <strong>von</strong> Flächen mittels Approximation durch primitive Objekte<br />

zu konstruieren. Bei umfangreichen Modellen oder stark detaillierten Oberflächen ist dies allerdings<br />

ein sehr schwieriges <strong>und</strong> mühsames Unterfangen. Zudem erfordert dieses Vorgehen<br />

ein hohes Maß an Speicher- <strong>und</strong> Rechenkapazität <strong>und</strong> kann so die Möglichkeiten bei der Gestaltung<br />

<strong>von</strong> Szenen merklich einschränken.<br />

Ein weiterer Nachteil macht sich besonders bei <strong>Ray</strong> <strong>Tracing</strong>-Verfahren bemerkbar: Die stark<br />

erhöhte Anzahl <strong>von</strong> Objekten. Da für die Ermittlung <strong>von</strong> Schnittpunkten des Strahls die<br />

Schnittalgorithmen aller Objekte durchgeführt werden müssen, wirkt sich dieses negativ auf<br />

die Laufzeit aus. So eignet sich dieses Vorgehen nur begrenzt für den Einsatz.<br />

<strong>Freiformflächen</strong> stellen hier eine effiziente Methode zur Lösung der genannten Probleme dar.<br />

Diese, durch Kontrollpunkte definierten Flächen bieten durch ihre Flexibilität erstaunliche<br />

Möglichkeiten bei der Modellierung. Durch Ausnutzung der mathematischen Eigenschaften<br />

dieser Art <strong>von</strong> Flächen ist es dennoch möglich, die hierzu notwendigen Berechnungen schnell<br />

durchzuführen. Dadurch finden <strong>Freiformflächen</strong> auch im sehr rechenintensiven Bereich des<br />

<strong>Ray</strong> <strong>Tracing</strong> Verwendung.


KAPITEL 2. FREIFORMFLÄCHEN 11<br />

2.1 Tensorprodukt-Flächen<br />

Es gibt verschiedene Vorgehensweisen zur Konstruktion <strong>von</strong> <strong>Freiformflächen</strong>. Einen sehr naheliegenden<br />

<strong>und</strong> intuitven Ansatz bildet hier das Tensorprodukt <strong>von</strong> Freiformkurven.<br />

Es seien F <strong>und</strong> G zwei Kurven im dreidimensionalen Raum:<br />

F (t) = �<br />

Ni(t)bi G(t) = �<br />

Mi(t)di<br />

i<br />

i<br />

bi, di ∈ R 3<br />

mit den skalaren Basisfunktionen Ni(t) <strong>und</strong> Mi(t). Daraus ergibt sich für das Tensorprodukt:<br />

P (u, v) := � �<br />

Ni(u)Mk(v)ci,k ci,k ∈ R 3<br />

(2.1)<br />

i<br />

k<br />

Die Eigenschaften der daraus resultierenden, viereckigen Oberflächen werden dabei vom Verhalten<br />

der zu Gr<strong>und</strong>e gelegten Freiformkurven bestimmt. Zu den wichtigsten Anforderungen<br />

an die Interpolationsverfahren dieser Kurven zählen hierbei:<br />

• Kontrollierbarkeit: Die Veränderung <strong>von</strong> Parametern hat voraussagbare <strong>und</strong> kontrollierbare<br />

Auswirkungen auf den Verlauf der Kurve.<br />

• Lokalität: Eine lokale Änderung <strong>von</strong> Kontrolldaten hat im Idealfall nur eine Veränderung<br />

in einem kleinen Bereich des Kurvenverlaufs zur Folge. Die Auswirkungen auf die<br />

globale Gestalt sind gering.<br />

In den folgenden Abschnitten wird die Technik der Tensorprodukt-Flächen anhand Bézierkurven<br />

<strong>und</strong> den sich ergebenden Bézieroberflächen genauer beschrieben.<br />

2.2 Bernstein-Polynome<br />

Freiformkurven entstehen durch Gewichtung <strong>und</strong> Summation ihrer Kontrollpunkte. Die Gewichtung<br />

erfolgt dabei durch die Basis- oder Überblendungsfunktionen. Im Falle <strong>von</strong> Bézierkurven<br />

(vgl. Abschnitt 2.3) werden dazu Bernstein 1 -Polynome verwendet.<br />

Definition <strong>von</strong> Bernstein-Polynomen<br />

Ein Bernstein-Polynom vom Grad n ist definiert durch:<br />

B n i (t) :=<br />

� �<br />

n<br />

t<br />

i<br />

i (1 − t) n−i<br />

Desweiteren wird festgelegt: B n i (t) ≡ 0, falls i < 0 oder i > n.<br />

1 Sergei N. Bernstein, 1912<br />

t ∈ [0, 1] , i = 0, · · · , n (2.2)


KAPITEL 2. FREIFORMFLÄCHEN 12<br />

Eigenschaften <strong>von</strong> Bernstein-Polynomen<br />

Bernstein-Polynome weisen einige interessante Eigenschaften auf, wodurch sie sich sehr gut<br />

als Überblendungsfunktion <strong>von</strong> Freiformkurven eignen:<br />

• Positivität: Für t ∈ [0, 1] gilt:<br />

• Symmetrie: Bernstein-Polynome sind symmetrisch, da<br />

B n i (t) ≥ 0. (2.3)<br />

B n i (t) = B n n−i(1 − t). (2.4)<br />

• Zerlegung der Eins: Wie sich mit Hilfe des Binomischen Lehrsatzes2 leicht zeigen<br />

lässt, gilt:<br />

n�<br />

B n i (t) ≡ 1 ∀t ∈ [0, 1] (2.5)<br />

i=0<br />

Dadurch stellt die Interpolation <strong>von</strong> Punkten eine affine Kombination dar.<br />

• Rekursivität: Die Berechnung der Werte kann rekursiv erfolgen, wegen:<br />

Bn i (t) = (1 − t)B n−1<br />

i (t) + tB n−1<br />

i−1 (t), wobei (2.6)<br />

B0 0(t) = 1, B0 1(t) = 0 <strong>und</strong> B0 −1(t) = 0.<br />

• Extrema: Für B n i (t) ergibt sich in [0, 1] genau ein Maximum. Dieses liegt bei t = i<br />

n .<br />

Abbildung 2.1 zeigt die Graphen der Bernstein-Polynome für den Grad 3. Die genannten<br />

Eigenschaften sind deutlich im Verlauf der Kurven erkennbar.<br />

1<br />

0.9<br />

0.8<br />

0.7<br />

0.6<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

B 3<br />

0<br />

B B<br />

3<br />

1<br />

0<br />

0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1<br />

Abbildung 2.1: Verlauf <strong>von</strong> Bernstein-Polynomen vom Grad 3<br />

2 Binomischer Lehrsatz: (a + b) n = n�<br />

k=0<br />

� � n k n−k<br />

k a b<br />

3<br />

2<br />

B<br />

3<br />

3<br />

t


KAPITEL 2. FREIFORMFLÄCHEN 13<br />

2.3 Bézierkurven<br />

Bézierkurven wurden 1959 <strong>von</strong> Paul de Casteljau bei Citroën <strong>und</strong> unabhängig da<strong>von</strong>, Anfang<br />

der sechziger Jahre, <strong>von</strong> Pierre Bézier bei Rénault für den Entwurf <strong>von</strong> Autokarosserien entwickelt.<br />

Diese Art der Kurvendarstellung revolutionierte die Entwicklung <strong>von</strong> frei geformten<br />

Kurven <strong>und</strong> Oberflächen in CAD-Systemen.<br />

Inzwischen finden Bézierkurven in vielen Bereichen der Computergraphik Verwendung <strong>und</strong><br />

dienen unter anderem als Basis für die Berechnung <strong>von</strong> Zeichen in PostScript-Systemen.<br />

Definition <strong>von</strong> Bézierkurven<br />

Eine Bézierkurve vom Grad n wird definiert durch:<br />

F (t) :=<br />

n�<br />

B n i (t)bi<br />

i=0<br />

t ∈ [0, 1] , bi ∈ R 3<br />

(2.7)<br />

mit dem Bernstein-Polynom B n i (t) (vgl. Abschnitt 2.2) als Basis- oder Überblendungsfunktion.<br />

Daraus ergibt sich für eine kubische Bézierkurve in der parametrierten Form:<br />

F (t) = (1 − t) 3 b0 + 3(1 − t) 2 tb1 + 3(1 − t)t 2 b2 + t 3 b3<br />

(2.8)<br />

Die Punkte bi werden als Kontroll- oder Bézier-Punkte bezeichnet <strong>und</strong> bilden das Kontrollpolygon<br />

P der Kurve (siehe Abbildung 2.2).<br />

y<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

0<br />

b1<br />

Eigenschaften <strong>von</strong> Bézierkurven<br />

b<br />

0<br />

F<br />

0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9<br />

Abbildung 2.2: Eigenschaften <strong>von</strong> Bézierkurven<br />

Bézierkurven erfüllen die bereits erwähnten Anforderungen der Kontrollierbarkeit <strong>und</strong> Lokalität<br />

(vgl. Abschnitt 2.1) <strong>und</strong> besitzen darüberhinaus einige wichtige Formeigenschaften, die<br />

K<br />

b<br />

3<br />

P<br />

H<br />

b<br />

2<br />

x


KAPITEL 2. FREIFORMFLÄCHEN 14<br />

sie für den Bereich der Computergraphik so interessant machen. Diese Eigenschaften sind in<br />

Abbildung 2.2 dargestellt <strong>und</strong> begründen sich durch das Verhalten der Bernstein-Polynome<br />

(vgl. Abschnitt 2.2) als ihre Basisfunktion. Im Einzelnen sind dies:<br />

• Affine Invarianz: Die Kurve verhält sich invariant bei affinen Abbildungen der Kontrollpunkte<br />

- d.h. für eine affine Abbildung ϕ(x) = Ax + b gilt:<br />

ϕ(F (t)) =<br />

n�<br />

B n i (t)ϕ(bi). (2.9)<br />

i=0<br />

• Endpunkt-Interpolation: Die beiden Kontrollpunkte b0 <strong>und</strong> bn liegen auf der Kurve,<br />

was für die dazwischenliegenden Punkte im Allgemeinen nicht gilt.<br />

F (0) = b0 <strong>und</strong> F (1) = bn. (2.10)<br />

• Eigenschaft der konvexen Hülle: Eine Bézierkurve liegt vollständig innerhalb der konvexen<br />

3 Hülle K, die durch ihre Kontrollpunkte definiert wird. Für 0 ≤ t ≤ 1 gilt:<br />

F (t) ∈ K bzw. F ⊆ K. (2.11)<br />

• Tangentenbedingung: In ihren Endpunkten verläuft die Kurve tangential zum Kontrollpolygon.<br />

F ′ (0) = n(b1 − b0) <strong>und</strong> F ′ (1) = n(bn − bn−1) (2.12)<br />

• Variationsreduzierung: Eine Bézierkurve F schwankt in ihrem Verlauf nicht stärker<br />

als ihr Kontrollpolygon P - denn für eine beliebige Hyperebene H in R d (z.B. Gerade<br />

in R 2 ) gilt:<br />

#(H ∩ F ) ≤ #(H ∩ P ). (2.13)<br />

2.3.1 Algorithmus nach de Casteljau<br />

De Casteljau entwickelte 1959 einen einfachen Algorithmus zur geometrischen Konstruktion<br />

der Punkte einer Bézierkurve. Das Verfahren beruht dabei auf der wiederholten Anwendung<br />

der linearen Interpolation <strong>von</strong> Punkten <strong>und</strong> ist definiert als:<br />

b k i (t) = (1 − t)b k−1<br />

i−1 (t) + tbk−1 i (t) i = 1, · · · , n; k = 0, · · · , i (2.14)<br />

Ist n der Grad der Kurve, so stellt b n n(t) einen Kurvenpunkt dar.<br />

Abbildung 2.3 zeigt dieses Vorgehen anhand der Konstruktion des Punktes F (0.6) auf einer<br />

Bézierkurve vom Grad 2.<br />

Für t = 0.6 ergibt sich hier der Punkt b 1 1 durch lineare Interpolation der Kontrollpunkte b 0 0<br />

<strong>und</strong> b 0 1. Äquivalent dazu folgt der Punkt b 1 2, für gleiches t, aus den Kontrollpunkten b 0 1 <strong>und</strong> b 0 2.<br />

Daher können diese Punkte beschrieben werden als:<br />

b 1 1 = (1 − t)b 0 0 + tb 0 1 <strong>und</strong> b 1 2 = (1 − t)b 0 1 + tb 0 2. (2.15)<br />

3 Eine Punktmenge M heißt konvex, wenn für zwei beliebige Punkte p1, p2 ∈ M gilt: p1p2 ∈ M


KAPITEL 2. FREIFORMFLÄCHEN 15<br />

y<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

0<br />

b<br />

0<br />

0<br />

b<br />

1<br />

1<br />

0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9<br />

b<br />

0<br />

1<br />

2<br />

b = F(t)<br />

Abbildung 2.3: Geometrische Punktkonstruktion nach de Casteljau<br />

Die erneute Anwendung der linearen Interpolation auf die Punkte b 1 1 <strong>und</strong> b 1 2 mit gleichem t<br />

liefert den Kurvenpunkt<br />

b 2 2 = (1 − t)b 1 1 + tb 1 2. (2.16)<br />

Durch Substitution der Gleichungen (2.15) in (2.16) <strong>und</strong> anschließender Umformung zeigt<br />

sich die aus den Gleichungen (2.7) <strong>und</strong> (2.2) bekannte Form:<br />

b 2 2 = (1 − t) 2 b 0 0 + 2t(1 − t)b 0 1 + t 2 b 0 2 (2.17)<br />

= 1 · t 0 (1 − t) 2 b 0 0 + 2 · t 1 (1 − t) 1 b 0 1 + 1 · t 2 (1 − t) 0 b 0 2 (2.18)<br />

=<br />

� �<br />

2<br />

0<br />

t 0 (1 − t) 2−0 b 0 0 +<br />

� �<br />

2<br />

1<br />

t 1 (1 − t) 2−1 b 0 1 +<br />

2<br />

b<br />

� �<br />

2<br />

2<br />

1<br />

2<br />

b<br />

0<br />

2<br />

x<br />

t 2 (1 − t) 2−2 b 0 2<br />

(2.19)<br />

Für den allgemeinen Fall lässt sich dieses Verfahren sehr gut durch das de Casteljau-Schema<br />

darstellen:<br />

b0 = b 0 0<br />

↘<br />

b1 = b 0 1 → b 1 1<br />

↘ ↘<br />

b2 = b 0 2 → b 1 2 → b 2 2<br />

.<br />

.<br />

. ..<br />

.<br />

. ..<br />

bn−1 = b 0 n−1 → b 1 n−1 → b 2 n−1 · · · b n−1<br />

n−1<br />

↘ ↘ ↘<br />

bn = b 0 n → b 1 n → b 2 n · · · b n−1<br />

n → b n n<br />

Abbildung 2.4: Berechnungsschema nach de Casteljau<br />

.<br />

. ..


KAPITEL 2. FREIFORMFLÄCHEN 16<br />

2.3.2 Aufteilung <strong>von</strong> Bezierkurven<br />

In den meisten Fällen werden Bézierkurven über dem Intervall 4 [0,1] definiert <strong>und</strong> können für<br />

Parameter innerhalb dieses Bereichs aufgeteilt werden. Dieses wird durch die Unterteilung<br />

der Kontrollpunkte in zwei neue Kontrollpunktmengen erreicht. Abbildung 2.5 zeigt dies am<br />

Beispiel der Unterteilung einer kubischen Bézierkurve an der Stelle c.<br />

y<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

0<br />

l 1<br />

b1<br />

b = l<br />

0<br />

0<br />

L<br />

l 2<br />

l 3 = F(c) = r0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9<br />

b2<br />

b = r<br />

Abbildung 2.5: Aufteilung einer kubischen Bézierkurve<br />

Wie in der Abbildung ersichtlich, können die zu bestimmenden Endpunkte der Teilkurve L<br />

direkt übernommen werden: Der Punkt l0 ist identisch mit dem Punkt b0 der Originalkurve.<br />

Der Endpunkt l3 entsteht aus der Durchführung des de Casteljau-Algorithmus für den Parameter<br />

c. Dies folgt aus der Eigenschaft <strong>von</strong> Bézierkurven (2.10).<br />

Für die Bestimmung der beiden Zwischenpunkte l1 <strong>und</strong> l2 kann die Tangentenbedingung<br />

(2.12) bzw. die Ableitungen der Kurven genutzt werden. Hieraus ergibt sich:<br />

l1 = b0 + c(b1 − b0)<br />

l2 = (1 − c) 2 b0 + 2c(1 − c)b1 + c 2 b2<br />

Wie sich herausstellt, ergeben sich diese Punkte direkt durch die lineare Interpolation bei der<br />

Durchführung des de Casteljau- Algorithmus (vgl. (2.15) <strong>und</strong> (2.17)). Durch die Symmetrie,<br />

die Bézierkurven aufweisen, gelten diese Zusammenhänge auch für die Teilkurve R.<br />

Deshalb können die Kontrollpunkte der beiden Teilkurven durch die Schritte im de Casteljau-<br />

Algorithmus bestimmt werden:<br />

li = b i i(c) <strong>und</strong> ri = b n−i<br />

n i = 0, · · · , n (2.20)<br />

Die entstandenen Kontrollpolygone der Teilkurven L <strong>und</strong> R bilden eine bessere Näherung der<br />

Kurve als das ursprüngliche. So entsteht durch weitere, gleichmäßige Unterteilung der Teilkurven<br />

eine Folge <strong>von</strong> Kontrollpolygonen, die quadratisch gegen die Bézierkurve konvergiert.<br />

4 Das Parameterintervall ist <strong>von</strong> der verwendeten Basisfunktion abhängig. Für das verallgemeinerte Bernstein-<br />

Polynom gilt: ∆ := [a, b] , B ∆,n<br />

i (t) = 1<br />

(b−a) n<br />

� n<br />

i<br />

� (t − a) i (b − t) n−i<br />

r 1<br />

R<br />

3<br />

r 2<br />

3<br />

x


KAPITEL 2. FREIFORMFLÄCHEN 17<br />

2.4 Bezierflächen<br />

Durch den im Abschnitt 2.1 genannten Ansatz des Tensorproduktes, können Bézierkurven zu<br />

Bézierflächen verallgemeinert werden.<br />

Definition <strong>von</strong> Bézierflächen<br />

Für die beiden Bézierkurven<br />

F m (t) =<br />

m�<br />

B m i (t)bi <strong>und</strong> G n (t) =<br />

i=0<br />

vom Grad n bzw. m ergibt sich für das Tensorprodukt:<br />

P m,n (u, v) =<br />

m�<br />

i=0<br />

n�<br />

B n i (t)di<br />

i=0<br />

n�<br />

B m i (u)B n k(v)ci,k u, v ∈ [0, 1] (2.21)<br />

k=0<br />

Analog zum Kontrollpolygon bei Bézierkurven bilden hier die Bézierpunkte ci,k ein charakteristisches<br />

Kontrollpolyeder, das die Form der Oberfläche festlegt. Abbildung 2.6 zeigt dieses<br />

Polyeder für den Fall einer bikubischen Bézieroberfläche.<br />

0.2<br />

0.35<br />

0.3<br />

0.25<br />

0.2<br />

0.15<br />

0.1<br />

0.05<br />

0<br />

0.8<br />

0.4<br />

0.7<br />

0.6<br />

0.6<br />

0.8<br />

0.5<br />

0.4<br />

Abbildung 2.6: Bikubische Bézierfläche mit Kontrollpolyeder<br />

Eigenschaften <strong>von</strong> Bézierflächen<br />

Wie bereits im Abschnitt 2.1 erwähnt, bestimmen die zu Gr<strong>und</strong>e liegenden Kurven die Eigenschaften<br />

der durch das Tensorprodukt konstruierten <strong>Freiformflächen</strong>. Deshalb leiten sich die<br />

meisten Eigenschaften <strong>von</strong> Bézierflächen <strong>von</strong> denen der Bézierkurven (vgl. Abschnitt 2.3) ab.<br />

Zu den wichtigsten zählen hierbei:<br />

0.3<br />

0.2


KAPITEL 2. FREIFORMFLÄCHEN 18<br />

• Affine Invarianz: Auch Bézierflächen verhalten sich invariant unter affinen Transformationen.<br />

Daher gilt<br />

ϕ(P m,n m� n�<br />

(u, v)) = B m i (u)B n k(v)ϕ(ci,k) (2.22)<br />

i=0<br />

• Eckpunkt-Interpolation: Die vier Kontrollpunkte an den Ecken c0,0, c0,m, cn,0 <strong>und</strong> cn,m<br />

liegen auf der Oberfläche:<br />

k=0<br />

P m,n (0, 0) = c0,0, P m,n (0, 1) = c0,n, P m,n (1, 0) = cm,0, P m,n (1, 1) = cm,n (2.23)<br />

Wie bei Bézierkurven, gilt dies für die anderen Punkte im Allgemeinen nicht.<br />

• Eigenschaft der konvexen Hülle: Eine Bézierfläche ist vollständig in der konvexen<br />

Hülle K enthalten, die <strong>von</strong> ihrem Kontrollpolyeder aufgespannt wird.<br />

P m,n (u, v) ∈ K{ci,k|0 ≤ i ≤ m, 0 ≤ k ≤ n} u, v ∈ [0, 1] (2.24)<br />

• Eigenschaft der Schnittkurven: Die sich für einen Parameter als konstant ergebenden<br />

Kurven sind Bézierkurven.<br />

P m,n (c, v) bzw. P m,n (u, c) ist Bézierkurve, c ∈ [0, 1] konstant, v ∈ [0, 1]<br />

(2.25)<br />

Insbesondere gilt dies für die vier Randkurven P m,n (0, v), P m,n (1, v), P m,n (u, 0) <strong>und</strong><br />

P m,n (u, 1).<br />

2.4.1 Flächennormalen<br />

Die Flächennormale ist eine entscheidende Größe für die Berechnungen des Beleuchtungsmodells.<br />

Für Bézierflächen kann diese mit Hilfe der Tangentenvektoren im entsprechenden Punkt<br />

berechnet werden. Diese Vektoren ergeben sich aus den partiellen Ableitungen der Flächenfunktion<br />

(2.21) nach u <strong>und</strong> v.<br />

∂<br />

∂uP m,n (u, v) = m m−1 �<br />

n�<br />

i=0 k=0<br />

mit ∆ 1,0 ci,k = ci+1,k − ci,k<br />

∂<br />

∂v P m,n (u, v) = n m�<br />

n−1 �<br />

i=0 k=0<br />

mit ∆0,1ci,k = ci,k+1 − ci,k<br />

B m−1<br />

i (u)B n k (v)∆1,0 ci,k (2.26)<br />

B m i (u)B n−1<br />

k (v)∆ 0,1 ci,k (2.27)<br />

Die Tangentenvektoren bilden die Basisvektoren der Tangentialebene im Punkt (u, v). So ergibt<br />

sich die Flächennormale durch das Vektorprodukt dieser Vektoren <strong>und</strong> anschließender<br />

Normierung.<br />

�n(u, v) = ∂<br />

∂u P m,n (u, v) × ∂<br />

∂v P m,n (u, v)<br />

�n 0 (u, v) =<br />

�n(u, v)<br />

|�n(u, v)|


KAPITEL 2. FREIFORMFLÄCHEN 19<br />

g v<br />

g u<br />

Abbildung 2.7: Normalenberechnung bei Bézierflächen<br />

2.4.2 Aufteilung <strong>von</strong> Bezierflächen<br />

Um Bézierflächen aufzuteilen, ist es notwendig, die Kontrollpunkte der entstehenden Teilflächen<br />

zu finden. Dazu sind folgende Überlegungen hilfreich:<br />

Eine Bézierfläche der Grade m <strong>und</strong> n kann als Überlagerung <strong>von</strong> m + 1 Kurven mit jeweils<br />

konstantem Parameter v <strong>und</strong> n + 1 Kurven mit jeweils konstantem Parameter u interpretiert<br />

werden. Die Kontrollpunkte dieser Kurven ergeben sich aus den Zeilen <strong>und</strong> Spalten der Kontrollpunktmatrix<br />

der Fläche (siehe Abbildung 2.8(a)). Da sich für einen konstanten Parameter<br />

aus einer Bezierfläche eine Bezierkurve ergibt (vgl. (2.25)), kann auf jede dieser Kurven der<br />

Algorithmus zur Aufteilung <strong>von</strong> Bezierkurven angewandt werden. Die Kontrollpunkte der<br />

daraus entstehenden Teilkurven (siehe Abbildung 2.8(b)) können wieder zusammengefasst<br />

werden <strong>und</strong> bilden die Kontrollpunktmatrizen der Teilflächen.<br />

(a) (b)<br />

Abbildung 2.8: Unterteilung <strong>von</strong> Bézierflächen<br />

(a) Kontrollpolygone der vier Kurven. (b) Kontrollpolygone nach Aufteilung.<br />

Durch die so entstandenen Teilflächen verbessert sich die Näherung des Kontrollpolyeders an<br />

die Bézierfläche. Weitere Unterteilungsschritte führen zu immer flacher werdenden Kontrollpolyedern<br />

deren Folge gegen die ursprüngliche Bézierfläche konvergiert.<br />

n


KAPITEL 2. FREIFORMFLÄCHEN 20<br />

2.5 Methoden zur Schnittpunktberechnung<br />

Bézieroberflächen sind polynomiale Funktionen, die <strong>von</strong> den Parametern u <strong>und</strong> v abhängen<br />

<strong>und</strong> erlauben deshalb keine explizite Berechnung <strong>von</strong> Schnittpunkten des Strahls mit der<br />

Oberfläche. Daher muss die Auswertung über iterative Näherungsverfahren erfolgen. Abgesehen<br />

da<strong>von</strong>, dass die Berechnungen mit Hilfe dieser Verfahren sehr rechenintensiv sind, setzen<br />

diese Algorithmen für gewöhnlich Oberflächen voraus, die frei <strong>von</strong> Singularitäten <strong>und</strong> starken<br />

Krümmungen sind. Diese Bedingung stellt allerdings eine sehr große Einschränkung für die<br />

Modellierung dar. Um auch Schnittpunkte mit derartigen Flächen in akzeptabler Zeit berechnen<br />

zu können, sind einige Schritte als Vorarbeit zu leisten.<br />

Die im Folgenden beschriebene Methode basiert auf der Arbeit <strong>von</strong> W. Barth <strong>und</strong> W.Stürzlinger<br />

[1] ” Efficient <strong>Ray</strong> <strong>Tracing</strong> for Bézier and B-Spline Surfaces “.<br />

2.5.1 Das Newton Näherungs-Verfahren<br />

Das Iterationsverfahren nach Newton ist eine Methode zur Lösung nichtlinearer Gleichungssysteme,<br />

die quadratische Konvergenz5 erreicht.<br />

Die geometrische Deutung dieses Verfahrens ist in Abbildung 2.9 dargestellt:<br />

Für den Startpunkt P0, der sich aus Gleichung (2.21) mit den Parametern u0 <strong>und</strong> v0 ergibt,<br />

werden die Gradienten gu0 <strong>und</strong> gv0 der Richtungen u <strong>und</strong> v ermittelt. Durch das Vektorprodukt<br />

dieser beiden Vektoren entsteht die Flächennormale n0. Der Schnitt des Strahls mit der<br />

dadurch definierten Tangentialebene legt den Punkt P1 fest. Der Vektor −−→<br />

P0P1, der aus diesen<br />

beiden Punkten gebildet wird, stellt eine Linearkombination der Gradienten gu0 <strong>und</strong> gv0 dar<br />

<strong>und</strong> kann durch das Lösen des sich ergebenden Gleichungssystems in seine Komponenten<br />

zerlegt werden. Die so bestimmten Gewichte ∆u <strong>und</strong> ∆v stellen die Werte für die Parameteränderung<br />

in Richtung u <strong>und</strong> v dar. Für die aktualisierten Parameter u1 <strong>und</strong> v1 ergibt sich<br />

ein Punkt der Fläche, der viel näher beim Schnittpunkt liegt, als der Startpunkt P0.<br />

Δv<br />

P1<br />

g v0<br />

Δu<br />

n 0<br />

P 0 (u 0 ,v 0 )<br />

g u0<br />

Abbildung 2.9: Die Newton-Methode<br />

Durch wiederholte Durchführung dieser Berechnungsschritte, ist der Schnittpunkt sehr genau<br />

bestimmbar. Die Iteration kann beendet werden, falls die Schrittweiten der Aktualisierung<br />

einen festgelegten Betrag ɛ unterschreiten.<br />

5 Bei quadratischer Konvergenz verdoppelt sich die Anzahl der gültigen Dezimalstellen pro Iterationsschritt


KAPITEL 2. FREIFORMFLÄCHEN 21<br />

2.5.1.1 Konvergenzprobleme<br />

Das soeben beschriebene Verfahren konvergiert in der Regel sehr schnell zum Schnittpunkt<br />

des Strahls mit der Fläche. Durch die Flexibilität, die <strong>Freiformflächen</strong> bei der Modellierung<br />

bieten, können hier jedoch Probleme auftreten. Im Allgemeinen ist die Konvergenz für iterative<br />

Verfahren nicht garantiert. Aufgr<strong>und</strong> der Flächenbeschaffenheit ist für einige Fälle nicht<br />

sichergestellt, dass die Iteration zu den richtigen Schnittpunkten führt oder überhaupt konvergiert.<br />

Abbildung 2.5.1.1 zeigt Situationen in denen die Iteration falsche Ergebnisse liefern<br />

kann.<br />

n2<br />

P2<br />

P1<br />

(a)<br />

n1<br />

Abbildung 2.10: Problemfälle bei der Schnittpunktapproximation<br />

(a) Hinterschneidung (b) Zu starke Krümmung.<br />

Durch Hinterschneidungen der Fläche können sich für einen Strahl mehrere Schnittpunkte<br />

ergeben (siehe Abbildung 2.10(a)). Für diesen Fall existieren Lösungen in den Punkten P1<br />

<strong>und</strong> P2 <strong>und</strong> die Iteration konvergiert unter Umständen zum (hier falschen) Schnittpunkt P2.<br />

Da die Flächennormale (n2) in diesem Punkt eine völlig andere Ausrichtung aufweist als die<br />

erwartete (n1), ergeben sich sichtbare Fehler in den Berechnungen des Beleuchtungsmodells.<br />

Wie in Abbildung 2.10(b) angedeutet, führen zu starke Krümmungen der Fläche wie auch<br />

Singularitäten bei ungünstig gewählten Startpunkten zur Divergenz der Iteration. Der Algorithmus<br />

schließt daraus, dass kein Schnittpunkt existiert <strong>und</strong> der Strahl die Fläche verfehlt -<br />

Löcher in der Oberfläche sind die Folge.<br />

Eine häufig angewandte Methode diesen Problemen zu begegnen ist die Aufteilung der<br />

Fläche, um so die Suche nach dem Schnittpunkt auf kleine Teilbereiche zu beschränken.<br />

Der Gr<strong>und</strong> für dieses Vorgehen ist die Tatsache, dass sich durch wiederholte Unterteilung<br />

auch komplexe, stark gekrümmte Oberflächen auf einfache <strong>und</strong> annähernd planare Segmente<br />

zurückführen lassen (vgl. Abschnitt 2.4.2), auf welchen die Iteration sehr zuverlässig arbeitet.<br />

n 0<br />

o<br />

P0 o<br />

t<br />

o<br />

(b)<br />

*


KAPITEL 2. FREIFORMFLÄCHEN 22<br />

2.5.2 Vorverarbeitung der Flächen<br />

Im <strong>Ray</strong> <strong>Tracing</strong> ist es die Regel, dass ein Körper sehr oft auf Schnittpunkte mit Strahlen getestet<br />

<strong>und</strong> diese Punkte bei einem Treffer exakt berechnet werden müssen. Im Fall <strong>von</strong> <strong>Freiformflächen</strong><br />

werden diese Berechnungen anhand einfacher Teilflächen durchgeführt, die mit Hilfe<br />

des Algorithmus nach de Casteljau einfach erstellt werden können. Dieser ist zwar schnell,<br />

jedoch wäre es viel zu aufwändig, eine Fläche bei jedem Test erneut in möglichst planare<br />

Bereiche zu zerlegen. Daher bietet es sich an, diesen Schritt in einer Vorverarbeitungsphase<br />

durchzuführen <strong>und</strong> die daraus resultierenden Teilflächen während der <strong>Ray</strong> <strong>Tracing</strong>-Phase in<br />

einer geeigneten Datenstruktur bereitzuhalten.<br />

Trotz dieser Vorbereitungen bleiben Schnittpunktberechnungen für <strong>Freiformflächen</strong> aufwändig<br />

<strong>und</strong> rechenintensiv <strong>und</strong> sollten auf die nötigsten reduziert bzw. unnötige <strong>von</strong> vorn herein vermieden<br />

werden. Deshalb lohnt sich auch hier der Einsatz <strong>von</strong> Bo<strong>und</strong>ing Volumes.<br />

Wie bereits in Kapitel 1 Abschnitt 1.5 erwähnt, gehören die möglichst enge Umhüllung des<br />

Objekts <strong>und</strong> der schnelle Schnittpunkttest zu den wichtigsten Anforderungen an die Hüllkörper.<br />

Bei <strong>Freiformflächen</strong> kommt darüberhinaus ein weiterer Aspekt hinzu: Für den Startpunkt<br />

der Iteration müssen entsprechende Parameter u0 <strong>und</strong> v0 der Flächenfunktion (2.21)<br />

ermittelt werden. Hierbei erfüllt die Gr<strong>und</strong>fläche des Hüllkörpers eine wichtige Funktion <strong>und</strong><br />

sollte deshalb das umschlossene Flächenstück in seiner Form bestmöglich annähern. Bei unzureichender<br />

Approximation kommt es zu Fehlberechnung der Startparameter. Auf dieses<br />

Problem wird im Abschnitt 2.5.3.4 genauer eingegangen.<br />

2.5.2.1 Das Parallelepiped als Hüllkörper<br />

Eine sehr gute Approximation <strong>von</strong> Flächen wird im Allgemeinen durch ein Parallelogramm<br />

erreicht, das durch die beiden Vektoren �v1 <strong>und</strong> �v2 definiert wird (siehe Abbildung 2.11(a)).<br />

Diese ergeben sich jeweils aus dem arithmetischen Mittel der gegegenüberliegenden Vektoren,<br />

gebildet durch die vier Eckpunkte E1, E2, E3 <strong>und</strong> E4. Bei linearer Abhängigkeit der<br />

errechneten Vektoren, müssen hierfür alternative gef<strong>und</strong>en werden. Für diesen Fall bietet es<br />

sich an, die Basisvektoren des Koordinatensystems zu verwenden.<br />

Um die Lage des Parallelogramms im Raum zu fixieren, wird zusätzlich ein Basispunkt E<br />

benötigt. Dieser Punkt wird dadurch festgelegt, dass die am weitesten vom Parallelogramm<br />

entfernten Kontrollpunkte bei verschiedenen Vorzeichen - d.h. sie liegen auf unterschiedlichen<br />

Seiten - die gleiche Distanz zum Parallelogramm aufweisen. Räumlich gesehen wird<br />

dabei das Parallelogramm orthogonal zu den beiden Basisvektoren �v1 <strong>und</strong> �v2 ins Zentrum des<br />

Kontrollpolyeders verschoben.<br />

Ausgehend <strong>von</strong> diesem Parallelogramm als Gr<strong>und</strong>fläche, kann ein Parallelepiped als Hüllkörper<br />

für die Freiformfläche definiert werden (siehe Abbildung 2.11(b)). Bei der Wahl des<br />

dritten, dafür erforderlichen Basisvektors �v3 ist es möglich, weitere Formeigenschaften des<br />

Kontrollpolyeders zu berücksichtigen. Im einfachsten Fall kann dafür ein Vektor senkrecht<br />

zur Gr<strong>und</strong>fläche, gebildet durch das Vektorprodukt der Basisvektoren �v1 <strong>und</strong> �v2, angenommen<br />

werden. Für stark entartete Flächen folgt daraus eine etwas großzügigere Umhüllung. Da dies<br />

allerdings nur einen sehr kleinen Bruchteil der Flächen betrifft, sind die daraus resultierenden


KAPITEL 2. FREIFORMFLÄCHEN 23<br />

E3(u,v)<br />

−<br />

−<br />

v 2<br />

v 2<br />

E<br />

v 1<br />

E1(u,v)<br />

− −<br />

E<br />

v 3<br />

v 1<br />

(a)<br />

(b)<br />

− −<br />

E4(u,v)<br />

−<br />

E2(u,v)<br />

−<br />

Abbildung 2.11: Flächenumhüllung durch Parallelepiped<br />

(a) Flächenapproximation durch Parallelogramm. (b) Daraus entstandenes Parallelepiped.<br />

Einbußen durch zusätzliche Durchläufe der Schnittpunktsuche vernachlässigbar.<br />

Im Allgemeinen entsteht dadurch das schiefwinklige Parallelepiped<br />

P = E + α1�v1 + α2�v2 + α3�v3<br />

(2.28)<br />

mit dem Basispunkt E, den Basisvektoren �v1 bis �v3 <strong>und</strong> den zugehörigen Intervallen α1 bis α3.<br />

Aufgr<strong>und</strong> der Eigenschaften <strong>von</strong> Bézierflächen, speziell (2.24), ist durch die Umhüllung aller<br />

Kontrollpunkte sichergestellt, dass die zugehörige Fläche vollständig innerhalb dieser Hülle<br />

liegt. Dazu ist es notwendig, die Gleichung (2.28) für alle Kontrollpunkte zu lösen <strong>und</strong> die<br />

Intervallgrenzen entsprechend zu erweitern. Liegen alle errechneten Koeffizienten innerhalb<br />

der Intervalle α1, α2 <strong>und</strong> α3, so ist die Menge der Kontrollpunkte <strong>und</strong> somit die gesamte<br />

Bézierfläche im Parallelepiped enthalten.<br />

Für die Basisvektoren in normierter Form, empfiehlt es sich für die Ermittlung der Intervallgrenzen,<br />

die Intervalle α1, α2 <strong>und</strong> α3 als [0, 0] anzunehmen <strong>und</strong> diese nach <strong>und</strong> nach zu<br />

erweitern. Durch dieses Vorgehen entsteht ein Hüllkörper, der die Bézierfläche sehr eng umschließt.


KAPITEL 2. FREIFORMFLÄCHEN 24<br />

2.5.2.2 Kriterien zur Flächenaufteilung<br />

<strong>Freiformflächen</strong> können unter Umständen sehr bizarre Gestalt annehmen. Durch die Zurückführung<br />

auf einfache Flächensegmente können die, im Abschnitt 2.5.1.1 dargestellten Probleme<br />

bei der Berechnung der Schnittpunkte vermieden werden.<br />

Dazu bedarf es der Unterteilung der entsprechenden Flächen. Ob <strong>und</strong> entlang welcher Achse<br />

eine Fläche aufgeteilt werden sollte, muss anhand der Anordnung der Kontrollpunkte entschieden<br />

werden. Wie bei der Aufteilung <strong>von</strong> Bézierflächen (vgl. Abschnitt 2.4.2) ist es<br />

auch hier hilfreich, die Zeilen <strong>und</strong> Spalten der Kontrollpunktmatrix zu Kontrollpolygonen<br />

<strong>von</strong> Bézierkurven zusammenzufassen. Durch die Bewertung der daraus entstehenden Kurven<br />

kann die Gestalt der Fläche abgeschätzt werden. Dazu können folgende Kriterien herangezogen<br />

werden:<br />

b0<br />

• Krümmung: Die Krümmung lässt sich über das Verhältnis der Länge des Kontrollpolygons<br />

P zum Abstand d der beiden Endpunkte b0 <strong>und</strong> b3 abschätzen (vgl. Abbildung<br />

2.12(a)). Dieser Wert sollte eine vordefinierte Schwelle nicht überschreiten.<br />

• Vorzeichenwechsel der Krümmung: Innere Kontrollpunkte 6 - hier die Punkte b1 <strong>und</strong><br />

b2 - liegen auf unterschiedlichen Seiten der Geraden g, die durch die beiden Endpunkte<br />

definiert wird (vgl. Abbildung 2.12(b)).<br />

• Überschreitende Kontrollpunkte: Für den Fall, dass innere Kontrollpunkte außerhalb<br />

der Endpunkte liegen, können starke Krümmungen entstehen, die mit der Abschätzung<br />

des ersten Kriteriums nicht erkennbar sind. Diese Kontrollpunkte sind dadurch identifizierbar,<br />

dass sie mit einem der Endpunkte einen Winkel α > 90 ◦ bilden (vgl. Abbildung<br />

2.12(c)).<br />

P<br />

d<br />

(a)<br />

b3<br />

b0<br />

b1<br />

P<br />

g<br />

(b)<br />

Abbildung 2.12: Bewertungskriterien für Bézierkurven<br />

(a) Krümmungsabschätzung. (b) Vorzeichenwechsel der Krümmung. (c) Überschreitende<br />

Kontrollpunkte.<br />

Damit die Achse, entlang derer eine mögliche Unterteilung erfolgen soll, bestimmt werden<br />

kann, werden die Kurven aus den Zeilen <strong>und</strong> Spalten der Kontrollpunktmatrix getrennt analysiert.<br />

Erfüllen nicht alle Kurven einer Achse diese Kriterien, so muss die Fläche entlang<br />

6 Die zwischen den Endpunkten liegenden Kontrollpunkte werden hier als Innere Kontrollpunkte bezeichnet.<br />

b2<br />

b3<br />

b0<br />

(c)<br />

b1<br />

α<br />

b3<br />

b2


KAPITEL 2. FREIFORMFLÄCHEN 25<br />

der entgegengesetzten Achse aufgespalten werden, um die Aufteilung der Problemkurven zu<br />

erreichen. Die daraus entstehenden Teilkurven kommen diesen Flachheitskriterien sehr viel<br />

näher, sofern die diese dann nicht bereits erfüllen.<br />

Zusätzlich zur Planarität einer Fläche, ist eine weitere Größe <strong>von</strong> Bedeutung: Die Längendifferenz<br />

gegenüberliegender Randkurven sollte einen festgelegten Wert nicht überschreiten.<br />

Dadurch kann geprüft werden, ob das Flächensegment durch die Gr<strong>und</strong>fläche des Hüllkörpers<br />

ausreichend approximiert werden kann.<br />

2.5.2.3 Organisation der Teilflächen<br />

Um die im Voraus berechneten Teilflächen schnell durchsuchen zu können, ist die Art ihrer<br />

Organisation <strong>von</strong> entscheidender Bedeutung. Die dazu benutzte Datenstruktur sollte die Eigenschaften<br />

der gespeicherten Daten berücksichtigen.<br />

Die Unterteilung einer Fläche spaltet diese in zwei Teile etwa gleicher Größe. Werden dadurch<br />

die für den Ablauf der Iteration geforderten Kriterien noch nicht erfüllt, müssen diese<br />

Teile erneut aufgespalten werden. Aufgr<strong>und</strong> der Eigenschaft der konvexen Hülle <strong>von</strong> Bézierflächen<br />

sind die Hüllkörper der Teilflächen in der Umhüllung des gespaltenen Flächensegmentes<br />

vollständig enthalten. Dadurch ist es möglich, aus den Hüllkörpern eine ineinander<br />

verschachtelte Hierarchie rekursiv aufzubauen.<br />

Eine Datenstruktur, die diese Art der Aufspaltung <strong>und</strong> die rekursiv verschachtelte Anordnung<br />

sehr gut organisiert, ist der Binärbaum.<br />

§¡§¡§ ¨¡¨¡¨<br />

¨¡¨¡¨ §¡§¡§<br />

£¡£¡£ ¤¡¤¡¤<br />

¤¡¤¡¤ £¡£¡£<br />

©¡©¡©¡© �¡�¡�¡�<br />

�¡�¡�¡� ©¡©¡©¡©<br />

¥¡¥¡¥ ¦¡¦¡¦<br />

¦¡¦¡¦ ¥¡¥¡¥<br />

¡ ¡ ¢¡¢¡¢<br />

¢¡¢¡¢ ¡ ¡<br />

Abbildung 2.13: Binärbaum zur Teilflächenorganisation<br />

Die Daten, die für die Iteration sowie zum Durchsuchen des Baumes erforderlich sind, werden<br />

in den Knoten wie folgt abgelegt:<br />

• Die Inneren Knoten repräsentieren die Teile der Fläche, die aufgr<strong>und</strong> ihrer Beschaffenheit<br />

weiter unterteilt wurden. Die hier gespeicherten Informationen steuern den Verlauf<br />

der Suche <strong>und</strong> werden benötigt um den Pfad zu den richtigen Blattknoten <strong>und</strong> so zu den<br />

passenden Teilflächen zu finden. Dazu sind nur die Daten des Hüllkörpers notwendig.<br />

Hierzu zählen die Basisvektoren v1, v2 <strong>und</strong> v3, der Basispunkt E <strong>und</strong> die Intervalle α1<br />

bis α3.


KAPITEL 2. FREIFORMFLÄCHEN 26<br />

• Die Blattknoten stellen die Flächensegmente dar, die die Kriterien für die Iteration<br />

erfüllen. Deshalb werden hier zusätzlich zu den Daten des Hüllkörpers, die Werte gespeichert,<br />

die für die Iteration wichtig sind. Konkret sind dies die Intervallgrenzen der<br />

Parameter u <strong>und</strong> v des Flächensegmentes. Da das Verfahren mit den Kontrollpunkten<br />

der gesamten Fläche arbeitet, ist es nicht nötig, die Kontrollpunkte der zum Blattknoten<br />

gehörenden Teilfläche zu speichern. Deshalb besteht die Aufgabe des Suchbaumes<br />

lediglich darin, mit Hilfe der Hüllkörper, den bestmöglichen Startpunkt für das Iterationsverfahren<br />

zu finden.<br />

2.5.2.4 Aufbau des Suchbaumes<br />

Der Binärbaum zur Organisation der Flächensegmente kann sehr einfach durch einen rekursiven<br />

Ablauf aufgebaut werden. Die Bearbeitung beginnt mit der Gesamtfläche, die im Baum<br />

durch den Wurzelknoten repräsentiert wird. Hierbei gilt für die Parameter u <strong>und</strong> v ein initialer<br />

Bereich <strong>von</strong> [0, 1] × [0, 1].<br />

Da die Hüllkörper ungeachtet der Flächenbeschaffenheit ermittelt werden, erfolgen die dazu<br />

erforderlichen Berechnungen für alle Knoten gleich. Dies geschieht anhand des in Abschnitt<br />

2.5.2.1 beschriebenen Verfahrens. Die daraufhin folgende Prüfung der Flachheitskriterien entscheidet,<br />

ob das aktuell bearbeitete Flächensegment für den Ablauf der Iteration geeignet ist<br />

oder weiter unterteilt werden muss. Erfüllt das Flächensegment die Kriterien, so können die<br />

ermittelten Daten als Blattknoten in den Baum eingefügt werden.<br />

Macht die Flächenbeschaffenheit eine weitere Unterteilung notwendig, so werden die Informationen<br />

über den Hüllkörper als innerer Knoten in den Baum eingefügt <strong>und</strong> das Flächensegment<br />

wird entlang der, durch die Analyse ermittelten Achse halbiert. Für die sich ergebenden<br />

Teilflächen wird der Bereich der Parameter u <strong>und</strong> v bestimmt <strong>und</strong> der Ablauf rekursiv fortgesetzt.<br />

addToSearchTree( surface, uvDomain )<br />

hull := getBo<strong>und</strong>ingVolume( surface ) // berechne Hüllkörper<br />

// teste auf ausreichende Planarität <strong>und</strong> bestimme ggf. Achse für Aufteilung<br />

if( isFlat( surface, &divAxis ) )<br />

else<br />

addLeafNode( hull, uvDomain ) // erstelle Blattknoten<br />

addInnerNode( hull ) // erstelle inneren Knoten<br />

// halbiere Fläche entlang der ermittelten Achse<br />

[partLeft, partRight] := divide( surface, divAxis, 0.5 )<br />

// bestimme Parameterbereiche der Teilflächen<br />

[domainLeft, domainRight] := splitDomain( uvDomain, divAxis, 0.5 )<br />

// fahre fort für beide Teilflächen ...<br />

call addToSearchTree( partLeft, domainLeft )<br />

call addToSearchTree( partRight, domainRight )<br />

Abbildung 2.14: Pseudocode zum Aufbau des Suchbaumes


KAPITEL 2. FREIFORMFLÄCHEN 27<br />

2.5.3 Schnittpunktermittlung<br />

Der in der Vorverarbeitungsphase aufgebaute Suchbaum wird während der <strong>Ray</strong> <strong>Tracing</strong>-Phase<br />

genutzt, um Strahlen so nah wie möglich bis zum Schnittpunkt mit der Fläche zu verfolgen<br />

um so den bestmöglichen Startpunkt für die Iteration zu ermitteln. Dadurch ist es möglich, die<br />

Anzahl der Schritte, die zum Erreichen des Schnittpunktes erforderlich sind, deutlich zu verringern<br />

<strong>und</strong> Probleme durch zu starke Krümmungen oder Hinterschneidungen zu vermeiden.<br />

2.5.3.1 Durchsuchen der Flächensegmente<br />

Das Ziel der Suche ist das schnelle Auffinden aller Flächenbereiche, die die geforderten Kriterien<br />

erfüllen <strong>und</strong> Schnittpunkte mit dem Strahl aufweisen könnten. Aufgr<strong>und</strong> des Binärbaums<br />

als Datenstruktur <strong>und</strong> der verschachtelten Hüllkörper-Hierarchie kann diese Suche sehr effizient<br />

durchgeführt werden. Für alle gef<strong>und</strong>enen Bereiche muss durch iterative Approximation<br />

(vgl. Abschnitt 2.5.3.5) ermittelt werden, ob <strong>und</strong> an welcher Stelle es tatsächlich zu Schnittpunkten<br />

mit der Fläche kommt.<br />

Der Suchdurchlauf beginnt in der Wurzel des Binärbaums <strong>und</strong> wird bis in die Blattknoten<br />

fortgesetzt. Der Weg durch den Baum, den der Suchalgorithmus dabei durchläuft, wird durch<br />

die, in den Knoten gespeicherten Hüllkörper bestimmt. Wird der Hüllkörper eines Knotens<br />

vom Strahl getroffen, muss dieser Bereich des Baumes näher untersucht werden. Handelt es<br />

sich hierbei um einen inneren Knoten, wird der Test für beide Nachfolgerknoten rekursiv<br />

fortgesetzt, um dadurch die Bereiche für die Iteration weiter einzuschränken.<br />

Wurde durch diesen Ablauf ein Blattknoten erreicht, erfüllt der zugehörige Flächenbereich die<br />

geforderten Kriterien <strong>und</strong> der Startpunkt der Iteration kann anhand des Hüllkörpers bestimmt<br />

werden. Der daraufhin folgende Iterationsablauf zeigt, ob für diesen Abschnitt tatsächlich ein<br />

Schnittpunkt existiert oder der Strahl die Fläche dennoch verfehlt.<br />

searchNode( node )<br />

// teste auf Schnittpunkt mit Hüllkörper<br />

if( intersectBo<strong>und</strong>ingVolume( node ) )<br />

else<br />

if( isInnerNode( node ) // teste auf inneren Knoten<br />

else<br />

// führe Suche für beide Nachfolger fort ...<br />

call searchNode( node.left )|<br />

call searchNode( node.right )|<br />

// Blattknoten wurde erreicht<br />

start := determineStartPoint() // bestimme Startpunkt<br />

hit := doIteration( node, start ) // führe Iteration durch<br />

return // Abbruch der Suche im aktuellen Teilbaum<br />

Abbildung 2.15: Pseudocode zum rekursiven Durchlauf des Suchbaumes<br />

Infolge der hierarchischen Verschachtelung der Hüllkörper, kann beim Erreichen eines Knotens,<br />

dessen Hüllkörper nicht getroffen wird, die Suche im aktuellen Ast des Baumes abgebrochen<br />

werden, da alle direkten <strong>und</strong> indirekten Nachfolger unmöglich Treffer aufweisen können.


KAPITEL 2. FREIFORMFLÄCHEN 28<br />

Dadurch können Flächenbereiche, die vom Strahl nicht geschnitten werden, sehr schnell identifiziert<br />

<strong>und</strong> übergangen werden.<br />

Häufig ergeben sich während des Durchlauf eines Suchbaumes mehrere Flächensegmente,<br />

die der Iteration unterzogen werden müssen. Dies gilt vor allem bei mehrfach auftretenden<br />

Schnittpunkten <strong>und</strong> Strahlen, die die Hüllkörper der Flächensegmente sehr flach schneiden.<br />

Da nur der am nächsten zum Strahlursprung liegende Schnittpunkt benötigt wird, muss die<br />

Iteration allerdings nicht zwangsläufig für alle ermittelten Bereiche durchgeführt werden.<br />

Aufgr<strong>und</strong> der Eigenschaft der konvexen Hülle kann ein Flächenschnittpunkt nicht näher am<br />

Ursprung des Strahles liegen, als der Schnittpunkt mit dem Hüllkörper. Deshalb kann durch<br />

einen Vergleich der Distanz eines bereits ermittelten Schnittpunktes mit der Distanz des<br />

Hüllkörperschnittpunktes entschieden werden, ob durch das zu prüfende Flächensegment ein<br />

näher liegender Schnittpunkt erreichbar ist. Dadurch können unnötige Durchläufe der Iteration<br />

leicht eingespart werden. Um die laufzeittechnischen Nachteile einer rekursiven Prozedur<br />

zu vermeiden, empfiehlt es sich diesen Suchalgorithmus als iterativen Ablauf zu implementieren.<br />

2.5.3.2 Schnittberechnung der Hüllkörper<br />

Um den richtigen Pfad durch den Suchbaum zu finden, müssen die abgelegten Hüllkörper auf<br />

Schnittpunkte mit dem Strahl getestet werden. Im Fall <strong>von</strong> Parallelepipeds gestalten sich diese<br />

Berechnungen recht einfach <strong>und</strong> können schnell durchgeführt werden.<br />

Der Strahl mit dem Ursprung A <strong>und</strong> der Richtung � d kann beschrieben werden durch die Geradengleichung:<br />

A + λ � d (2.29)<br />

Für die Berechnung der Schnittpunkte können die Eigenschaften des Parallelepiped ausgenutzt<br />

werden. Dieses zeichnet sich dadurch aus, dass gegenüberliegende Flächen parallel zueinander<br />

sind. So kann für jedes der drei Ebenenpaare ein Parameterintervall λi berechnet<br />

werden, in welchem der Strahl jeweils zwischen den beiden Ebenen verläuft. Ausgehend <strong>von</strong><br />

der Schnittberechnung des Strahls mit einer Ebene 7 , ergibt sich durch Substitution der Gleichungen<br />

(2.29) <strong>und</strong> (2.28) für diese Intervalle:<br />

λi = �ni · (E − A) + αi(�vi · �ni)<br />

�ni · � d<br />

i = 1, 2, 3 (2.30)<br />

mit ni als den Normalenvektoren der Flächen des Parallelepipeds. Der Schnitt der daraus<br />

folgenden drei Intervalle<br />

[λP ] = [λ1] ∩ [λ2] ∩ [λ3] (2.31)<br />

7 Der Parameter für den Schnittpunkt einer Geraden mit einer Ebene ergibt sich aus λ = �n·(O−A)<br />

�n· � d


KAPITEL 2. FREIFORMFLÄCHEN 29<br />

stellt den Bereich des Parameters λ dar, für den der Strahl innerhalb des Parallelepipeds<br />

verläuft. Entsteht durch diese Schnittberechnung ein leeres Intervall, so verfehlt der Strahl<br />

das Parallelepiped.<br />

Ein Strahl, der parallel zu einem Ebenenpaar verläuft, führt in der Berechnung des entsprechenden<br />

Intervalls zu einem Nullwert im Nenner der Gleichung (2.30). Anstatt diesen kritischen<br />

Fall durch Unterscheidungen zu behandeln, empfiehlt es sich, den Nenner vor der Division<br />

auf einen sehr kleinen Wert abzuändern. Dadurch ergibt sich ein sehr großes Intervall<br />

für den Strahl zwischen den Ebenen oder ein leeres Intervall für einen außerhalb verlaufenden<br />

Strahl.<br />

Diese Berechnungen werden während des Baumdurchlaufs sehr häufig durchgeführt <strong>und</strong> sollten<br />

daher sorgfältig auf vermeidbare Rechenschritte überprüft werden. So enthält die Gleichung<br />

(2.30) Teile, die nicht <strong>von</strong> der Richtung des Strahls abhängig sind. Diese können bereits<br />

während der Vorverarbeitungsphase ausgewertet <strong>und</strong> im Suchbaum abgelegt werden. Vor<br />

allem bietet es sich hier an, die Flächennormalen ni aus den Basisvektoren vi vorab zu berechnen.<br />

Eine weitere Möglichkeit zur Optimierung bietet sich für die Schnittberechnung mit Primärstrahlen.<br />

Da diese alle den Standpunkt des Beobachters als Ursprung haben, ist für diesen Fall<br />

der Zähler der Gleichung (2.30) vollständig im Voraus berechenbar <strong>und</strong> kann im Suchbaum<br />

gespeichert werden.<br />

2.5.3.3 Ermittlung des Iterationsstartpunkts<br />

Zur Durchführung der Iteration wird eine initialer Punkt benötigt. Um aufwändige Schritte<br />

der Iteration einzusparen, sollte dieser so nah wie möglich am tatsächlichen Schnittpunkt <strong>von</strong><br />

Strahl <strong>und</strong> Fläche liegen.<br />

Durch die Kriterien zur Unterteilung <strong>von</strong> Flächen wird bereits in der Vorverarbeitungsphase<br />

sichergestellt, dass die Flächensegmente durch die Gr<strong>und</strong>fläche ihres Hüllkörpers ausreichend<br />

angenähert werden können. Deshalb ergibt sich durch den Schnittpunkt des Strahls mit dieser<br />

Gr<strong>und</strong>fläche in der Regel ein sehr guter Startpunkt.<br />

Für das Parallelepiped mit einem Parallelogramm als Gr<strong>und</strong>fläche, entsteht aus Gleichung<br />

(2.30), durch Setzen der Parameter i = 3 <strong>und</strong> α3 = 0<br />

λ0 = �n3 · (E − A)<br />

�n3 · � d<br />

(2.32)<br />

Die Substitution dieses Parameters λ0 in die Strahlengleichung (2.29) ergibt den Schnittpunkt<br />

D0. Durch das Lösen des Gleichungssystems<br />

D0 = E + γ1 �v1 + γ2 �v2<br />

(2.33)<br />

entstehen die beiden Koeffizienten γ1 <strong>und</strong> γ2. Diese können dazu verwendet werden um die<br />

Distanz des Schnittpunktes zum Basispunkt in den u, v-Raum zu übertragen. Da die Basisvek-


KAPITEL 2. FREIFORMFLÄCHEN 30<br />

toren v1 <strong>und</strong> v2 in normalisierter Form vorliegen, ist für die berechneten Werte eine Skalierung<br />

mit den Seitenlängen der Gr<strong>und</strong>fläche nötig. Daraus ergeben sich die Initialschritte der Iteration<br />

1<br />

∆u = γ1 ·<br />

(α1 − α1)<br />

1<br />

bzw. ∆v = γ2 ·<br />

(α2 − α2)<br />

Aufgr<strong>und</strong> der annähernden Planarität der Fläche <strong>und</strong> der Annahme, dass E ≈ P (u, v), können<br />

aus diesen Werten <strong>und</strong> den Grenzen des u, v-Bereiches der Fläche die Startparameter wie folgt<br />

errechnet werden:<br />

u0 = u + ∆u(u − u) bzw. v0 = v + ∆v(v − v) (2.34)<br />

Für Strahlen, die das Flächensegment annähernd tangential schneiden (siehe Abbildung 2.16),<br />

kann jedoch dieses Vorgehen zu Problemen führen. In diesem Fall schneidet der Strahl die<br />

Gr<strong>und</strong>fläche möglicherweise weit außerhalb des Hüllkörpers oder - für einen Strahl parallel<br />

zur Gr<strong>und</strong>fläche - gar nicht. Außerdem ist es sehr wahrscheinlich, dass aufgr<strong>und</strong> einer Hinterschneidung<br />

innerhalb dieser Teilfläche, mehrere Schnittpunkte existieren. Deshalb ist hier<br />

nicht sichergestellt, dass die Iteration den richtigen Schnittpunkt liefert. Die Berechnungsfehler,<br />

die sich hieraus ergeben können, sind in Abschnitt 2.5.1.1 dargestellt.<br />

h<br />

b<br />

Abbildung 2.16: Annähernd tangential schneidender Strahl<br />

Diese Situation ist mit Hilfe des Kriteriums<br />

φ<br />

tan φ > b<br />

h<br />

l<br />

(2.35)<br />

einfach identifizierbar. Es ist unter diesen Umständen nicht möglich, den Startpunkt über den<br />

Schnittpunkt der Gr<strong>und</strong>fläche zu bestimmen. Um hier fehlerhafte Berechnungen zu vermeiden,<br />

kann stattdessen der, auf die Gr<strong>und</strong>fläche projizierte Eintrittspunkt des Strahls in den<br />

Hüllkörper verwendet werden. Allerdings kann dieser weit vom tatsächlichen Schnittpunkt<br />

entfernt liegen <strong>und</strong> erfordert dadurch einige Iterationsschritte mehr.


KAPITEL 2. FREIFORMFLÄCHEN 31<br />

2.5.3.4 Die Bedeutung der Gr<strong>und</strong>fläche<br />

Für das im vorhergehenden Abschnitt beschriebene Verfahren, aus dem Startpunkt die entsprechenden<br />

Initialparameter u0 <strong>und</strong> v0 zu bestimmen, ist die Form der Gr<strong>und</strong>fläche des<br />

Hüllkörpers <strong>von</strong> entscheidender Bedeutung. Wird durch die Gr<strong>und</strong>fläche nur eine unzureichende<br />

Approximation des Flächensegmentes erreicht, so können sich daraus schwerwiegende<br />

Probleme ergeben. Diese werden durch die geometrische Darstellung des Verfahrens für<br />

den Fall eines stark verformten Flächensegments deutlich.<br />

Für eine rechteckige Gr<strong>und</strong>fläche stehen die beiden Basisvektoren �v1 <strong>und</strong> �v2 senkrecht aufeinander.<br />

Dadurch kann die in Abbildung 2.17 dargestellte Teilfläche nur sehr schlecht angenähert<br />

werden. Da die Aufspaltung <strong>von</strong> <strong>Freiformflächen</strong>, die auf dem Tensorprodukt basieren,<br />

nur entlang der Achsen der Parameter u <strong>und</strong> v erfolgen kann, ist die entartete Form dieser<br />

Teilfläche auch durch weitere Unterteilung nicht korrigierbar.<br />

v 2<br />

u,v<br />

E<br />

v<br />

v1<br />

Abbildung 2.17: Unzureichende Approximation durch Gr<strong>und</strong>fläche<br />

Durch das Lösen des Gleichungssystems (2.33) für den Startpunkt D0 wird der Vektor −−→<br />

ED0<br />

in seine Komponenten bzgl. der Basisvektoren �v1 <strong>und</strong> �v2 zerlegt. Weicht die Ausrichtung der<br />

Flächenränder, wie in diesem Beispiel, stark <strong>von</strong> der der Basisvektoren �v1 <strong>und</strong> �v2 ab, entstehen<br />

dadurch Initialschritte ∆u <strong>und</strong> ∆v, die zu einem extrem verschobenen Initialpunkt P (u0, v0)<br />

führen. Dieser kann unter Umständen weit außerhalb der gültigen Intervalle liegen. Für diese<br />

Bereiche ist nicht sichergestellt, dass die für die Iteration erforderlichen Flächeneigenschaften<br />

erfüllt werden. So können hier starke Krümmungen oder Richtungswechsel in der Krümmung<br />

auftreten, die die Iteration divergieren lassen.<br />

Ein Parallelogramm, das sich für die allgemein gehaltene Form der Gleichung (2.28) als<br />

Gr<strong>und</strong>fläche ergibt, liefert auch für stark verzerrte Flächen eine sehr gute Approximation.<br />

Dennoch reicht dies nicht aus, um Schnittpunkte in allen Situationen sicher zu ermitteln. Deshalb<br />

sind im Ablauf der Iteration weitere Maßnahmen zu treffen um die Wahrscheinlichkeit<br />

<strong>von</strong> Fehlberechnungen zu minimieren. Diese sind im nächsten Abschnitt genauer beschrieben.<br />

u<br />

D0<br />

u,v


KAPITEL 2. FREIFORMFLÄCHEN 32<br />

2.5.3.5 Der Iterationsablauf<br />

Wird während des Baumdurchlaufs ein Blattknoten erreicht, dessen Hüllkörper vom Strahl<br />

getroffen wird, so muss das entsprechende Flächensegment der Iteration unterzogen werden.<br />

Dabei wird der exakte Schnittpunkt des Strahls mit der Fläche mit Hilfe der im Abschnitt<br />

2.5.1 beschriebenen Newton-Methode ermittelt. Diese liefert für die beiden Parameter u <strong>und</strong><br />

v Werte für deren Veränderung um im nächsten Schritt der Iteration eine bessere Näherung<br />

des Schnittpunktes zu erreichen. Die dafür benötigten Initialwerte werden anhand der Gr<strong>und</strong>fläche<br />

des Hüllkörpers bestimmt (vgl. Abschnitt 2.5.3.3). Da dieses Verfahren dabei auf der<br />

vollständigen Fläche arbeitet, bedarf es dazu auch nur der Kontrollpunkte der Gesamtfläche.<br />

Durch wiederholte Durchführung ist der Schnittpunkt sehr genau bestimmbar. Als Abbruchkriterium<br />

kann dabei der Betrag der Schrittweiten herangezogen werden. Unterschreitet dieser<br />

den festgelegten Wert ɛ, so wurde die geforderte Genauigkeit erreicht.<br />

doIteration( node, start )<br />

[u, v] := convertPointToUV( node.hull, start ) // bestimme Startparameter<br />

// wiederhole bis maximale Anzahl Schritte ...<br />

while( #steps < maxSteps )<br />

// berechne Parameterschritte<br />

[∆u, ∆v] := getNewtonUpdate( u, v )<br />

// prüfe erreichte Genauigkeit<br />

if( |∆u| < ɛ and |∆v| < ɛ )<br />

else<br />

hit := pickHitData()<br />

return hit // Schnittpunkt gef<strong>und</strong>en<br />

// aktualisiere Parameter ...<br />

u := u + ∆u<br />

v := v + ∆v<br />

// teste auf Bereichsüberschreitung<br />

if( exceedingBo<strong>und</strong>s( node.uvDomain, u, v ) )<br />

// beende Iteration bei zweitem Versuch<br />

if( isSecondTry )<br />

exitLoop<br />

doNextStep // nächster Schritt<br />

return noHit // kein Schnittpunkt gef<strong>und</strong>en<br />

Abbildung 2.18: Pseudocode des Iterationsablaufs<br />

Wie bereits im vorhergehenden Abschnitt erwähnt, sind zusätzliche Maßnahmen nötig, um<br />

die Zuverlässigkeit des Algorithmus’ zu erhöhen. Besonders in Randbereichen der Flächensilhouette<br />

entsteht oft die Situation, dass zwar der Hüllkörper Schnittpunkte aufweist, jedoch<br />

das entsprechende Flächensegment vom Strahl verfehlt wird. Für diesen Fall kann die Iteration<br />

nicht konvergieren. Wurde deshalb nach einer vorgegebenen maximalen Anzahl <strong>von</strong><br />

Schritten noch kein Schnittpunkt gef<strong>und</strong>en, so muss die Iteration abgebrochen werden, mit<br />

dem Ergebnis, dass kein Schnittpunkt existiert.<br />

Ein weiteres Kriterium dafür, dass der Strahl das Flächensegment nicht trifft, ist das Über-


KAPITEL 2. FREIFORMFLÄCHEN 33<br />

schreiten der Grenzen des u, v-Bereiches während der Iteration. Ein Gr<strong>und</strong> dafür kann sein,<br />

dass der Schnittpunkt innerhalb eines benachbarten Segments liegt. Um jedoch die Iteration<br />

nicht wegen eines zu groß geratenen Initialschrittes vorschnell abzubrechen (vgl. Abbildung<br />

2.19(a)), sollte der überschreitende Parameter auf den Grenzwert gesetzt <strong>und</strong> so dem Algorithmus<br />

eine zweite Chance gegeben werden. Führt auch der nächste Schritt über den gültigen<br />

Bereich hinaus, kann da<strong>von</strong> ausgegangen werden, dass der Schnittpunkt tatsächlich außerhalb<br />

liegt. Insbesondere bei Schnittpunkten im Randbereich einer Teilfläche, können diese zusätzlichen<br />

Versuche mehrmals in Anspruch genommen werden <strong>und</strong> erhöhen die Anzahl der Iterationsschritte.<br />

Deshalb müssen bei der Wahl der maximalen Schrittanzahl einige Durchläufe<br />

mehr eingeplant werden.<br />

P 1<br />

St<br />

(a)<br />

St<br />

P 1 P 2<br />

Abbildung 2.19: Fehlerhafte Überschreitung der Flächengrenzen<br />

(a) Zu große Schrittweite. (b) Schnittpunkte benachbarter Teilflächen.<br />

Trotz dieses recht zuverlässigen Vorgehens, kann es zu Situationen kommen, in denen der<br />

Algorithmus falsche Ergebnisse liefert. Diese entstehen meist in Fällen, in denen der Strahl<br />

zwei benachbarte Flächensegmente schneidet. Wie in Abbildung 2.19(b) dargestellt, ist es<br />

möglich, dass der Startpunkt St näher am Schnittpunkt P2 des Nachbarsegmentes liegt als an<br />

dem des aktuellen Segmentes (P1). Unter diesen Umständen konvergiert die Iteration gegen<br />

den Schnittpunkt P2 des benachbarten Flächenstückes <strong>und</strong> wird deshalb die Bereichsgrenzen<br />

auch beim zweiten Versuch überschreiten. Das bedeutet, dass der Algorithmus so für die<br />

aktuelle Teilfläche keinen Schnittpunkt ermitteln kann, obwohl dieser eindeutig existiert.<br />

Damit diese Schnittpunkte nicht übergangen werden, muss die Iteration nach einem erfolglosen<br />

Durchlauf mit verändertem Initialpunkt erneut gestartet werden. Dafür eignet sich der<br />

Eintrittspunkt des Strahls in den Hüllkörper sehr gut. Führt auch dieser Versuch zu keinem<br />

Schnittpunkt, so macht es ein dritter Start der Iteration - diesmal mit dem Austrittspunkt des<br />

Strahls als Initialpunkt - beinahe unmöglich, in dieser Situation einen Schnittpunkt zu verlieren.<br />

(b)


KAPITEL 2. FREIFORMFLÄCHEN 34<br />

2.6 Fazit<br />

Allen Bemühungen zum Trotz, ist es durch dieses doch recht aufwändigen Verfahrens nicht<br />

immer möglich, Schnittpunkte korrekt zu berechnen. Nicht zuletzt wegen der hohen Flexibilität<br />

<strong>von</strong> <strong>Freiformflächen</strong>, kommt es immer wieder zu Situationen, für die das beschriebene<br />

Verfahren versagt. Insbesondere in den schmalen Randbereichen der Objektsilhouette schneidet<br />

ein Strahl die Fläche sehr flach. Dadurch entstehen Schnittpunkte, die sehr nah beisammen<br />

liegen. Selbst mit dem Eintrittspunkt des Strahls in den Hüllkörper ist nicht zu vermeiden, dass<br />

sich daraus für den ein oder anderen Fall der falsche Schnittpunkt ergibt. Abhängig <strong>von</strong> den<br />

Verhältnissen der Beleuchtung <strong>und</strong> der Bildauflösung werden diese Fehlberechnungen in vereinzelten<br />

Bildpunkten sichtbar.<br />

Abhilfe schafft hier die Technik des Supersampling, indem für einen Bildpunkt mehrere, in<br />

der Richtung leicht unterschiedliche Strahlen ausgewertet <strong>und</strong> die ermittelten Farben anteilig<br />

vermischt werden. Dadurch werden solche Fehler - solange diese nur für vereinzelte Strahlen<br />

auftreten - unsichtbar.<br />

(a)<br />

Abbildung 2.20: Ein berühmtes Beispiel: Der Utah Teapot<br />

(a) Drahtgittermodell. (b) Gerendert mit Gold als Material.<br />

(b)


Kapitel 3<br />

<strong>Texturierung</strong><br />

Synthetisch erzeugte Bilder sollen, so echt <strong>und</strong> natürlich wie möglich wirken. Die Modellierung<br />

solcher Szenen erfordert große Liebe zum Detail, denn die wenigsten Gegenstände<br />

haben eine glatte <strong>und</strong> einheitliche Oberfläche. Alle Feinheiten <strong>und</strong> Unregelmäßigkeiten, die<br />

in einer natürlichen Umgebung selbstverständlich vorhanden sind, müssen für einen realistischen<br />

Eindruck mühsam nachgebildet werden.<br />

Die Verwendung <strong>von</strong> Texturen ist eine sehr elegante Methode, die den dazu erforderlichen<br />

Aufwand deutlich zu reduzieren vermag <strong>und</strong> Szenen dennoch ein sehr realistisches Aussehen<br />

verleiht.<br />

Im Allgemeinen versteht man unter <strong>Texturierung</strong> das Überziehen <strong>von</strong> Oberflächen mit Mustern<br />

oder Bildern. Zur Erzeugung <strong>von</strong> Texturen gibt es erstaunlich viele Ansätze <strong>und</strong> Algorithmen.<br />

Neben dem klassischen ”Aufkleben” <strong>von</strong> Bildern auf Objekte, stellt die Klasse der<br />

so genannten Prozeduralen Texturen den größten Teil der heute verwendeten Texturen dar.<br />

Diese werden praktisch ausschließlich durch mathematische Funktionen beschrieben, die die<br />

Eigenschaften der Oberfläche, wie z.B. die Farbe, in den einzelnen Punkten bestimmen.<br />

Die Möglichkeiten <strong>von</strong> Texturen beschränken sich allerdings nicht auf Farb- <strong>und</strong> Materialverläufe.<br />

Durch gezielte Veränderung der Flächennormalen ist es auch möglich, Oberflächen<br />

mit Unregelmäßigkeiten oder feinen Strukturen zu versehen, die allein aus den Informationen<br />

der Textur berechnet werden. Dadurch kann eine natürliche Oberflächenbeschaffenheit<br />

erreicht werden ohne diese aufwändig geometrisch modellieren zu müssen.<br />

Da sich die Algorithmen zur Erzeugung diese Effekte größtenteils mit sehr wenig Aufwand<br />

an Speicher <strong>und</strong> Rechenkapazität begnügen, kommen heute Texturen in den meisten Graphiksystemen<br />

zum Einsatz.


KAPITEL 3. TEXTURIERUNG 36<br />

3.1 Arbeitsweise <strong>von</strong> <strong>Texturierung</strong>ssystemen<br />

Das Erscheinungsbild eines Objekts hängt stark <strong>von</strong> den Eigenschaften seiner Oberfläche ab.<br />

Dazu zählen die Farbe, die Materialkoeffizienten für die lokalen Lichtanteile, Werte für die<br />

Berechnung der Transparenz sowie die Flächennormale in dem zu berechnenden Punkt des<br />

Objekts. Gr<strong>und</strong>sätzlich ist es möglich, durch Texturen alle dieser Eigenschaften beeinflussen<br />

zu lassen. Viele Systeme beschränken sich allerdings auf die Farbe <strong>und</strong> die Flächennormale<br />

als die wichtigsten dieser Attribute.<br />

Texturen arbeiten losgelöst <strong>von</strong> den Objekten <strong>und</strong> bestimmen Punkt für Punkt die Größen für<br />

die weiteren Berechnungen. Da in Graphiksystemen die Schattierung <strong>von</strong> Flächen letztendlich<br />

auch punktweise berechnet werden muss, sind <strong>Texturierung</strong>ssysteme meist ohne großen<br />

Aufwand einzubinden.<br />

Im Gr<strong>und</strong>e werden für die Auswertung der Textur als Parameter nur die Koordinaten des<br />

Oberflächenpunktes <strong>und</strong> die Flächennormale benötigt. Durch diese unabhängige Arbeitsweise<br />

ergeben sich für die Implementierung <strong>von</strong> Texturen folgende Vorteile:<br />

• komplett vom Objekt trennbarer Algorithmus<br />

• i.d. Regel ohne oder mit nur geringer Anpassung auf alle Objekte anwendbar<br />

• durch Objektorientierte Programmierung einfach erweiterbar.<br />

Durch die Einbindung der <strong>Texturierung</strong> in ein <strong>Ray</strong> <strong>Tracing</strong>-System ergibt sich für die Berechnung<br />

der Schattierung der in Abbildung 3.1 dargestellte Ablauf:<br />

Schattierung<br />

Schnittpunktberechnung<br />

Schnittdaten setzen<br />

Objektdaten holen<br />

Berechnungen des<br />

Beleuchtungsmodells<br />

Objekt<br />

Transformationen &<br />

Normalen−Berechnung<br />

Objektdaten setzen<br />

Textur auswerten<br />

Farbe & Material holen<br />

# Normale & Ambience<br />

Textur<br />

Transformationen &<br />

Turbulenzberechnung<br />

Texturspezifische<br />

Berechnungen<br />

# Farbe & Material<br />

Abbildung 3.1: Integration eines <strong>Texturierung</strong>ssystems


KAPITEL 3. TEXTURIERUNG 37<br />

3.2 Arten <strong>von</strong> Texturen<br />

Durch die Freiheiten der Vorgehensweise <strong>und</strong> Algorithmen bei der Generierung <strong>von</strong> Texturen<br />

ergibt sich eine Vielzahl <strong>von</strong> Mustern <strong>und</strong> Oberflächenstrukturen. Durch die Art der Verfahren<br />

lassen sich die Texturen in Klassen einteilen. Die dabei gr<strong>und</strong>legenden <strong>und</strong> sehr häufig<br />

verwendeten Techniken werden im Folgenden beschrieben <strong>und</strong> Texturbeispiele vorgestellt, in<br />

denen diese zum Einsatz kommen. Große Teile da<strong>von</strong> basieren auf der Arbeit <strong>von</strong> O. Groth<br />

[3]. Deshalb sei im Folgenden nur ein Einblick in die Texturarten <strong>und</strong> deren Besonderheiten<br />

gegeben.<br />

3.2.1 2D Texture Mapping<br />

Die Darstellung <strong>von</strong> Bildern sowie flachen Mustern auf Objektoberflächen erfordert die Zuordnung<br />

jedes Punktes der Oberfläche zu einem Punkt innerhalb des Bildes oder Musters.<br />

Diese Zuordnung wird mittels einer mathematischen Abbildung der Punktkoordinaten auf<br />

das Intervall [0, 1] × [0, 1] erreicht. Um Verzerrungen des Bildes zu vermeiden, sollte die Art<br />

Abbildung (linear, sphärisch oder extern) passend zur Form der Oberfläche gewählt werden.<br />

Lineare Abbildung: Diese Art der Projektion kommt bei geraden Achsen zum Einsatz (vgl.<br />

Abbildung 3.2(a)) <strong>und</strong> wird durch folgendes Verhältnis ausgedrückt:<br />

lx := Px − Xa<br />

Xb − Xa<br />

bzw. ly := Py − Ya<br />

Yb − Ya<br />

(3.1)<br />

Mercator-Projektion: Die sphärische Abbildung oder Mercator-Projektion wird bei kreisförmig<br />

gekrümmten Flächenachsen verwendet (vgl. Abbildung 3.2(b)). Hier werden erst die<br />

Polarkoordinaten (θ, φ) des Punktes P bzgl. des Zentrums Z <strong>und</strong> dem Radius R berechnet.<br />

Wegen den unterschiedlichen Intervallen ((θ, φ) ∈ [0, π] × [−π, π]) muss dabei zwischen den<br />

X- <strong>und</strong> Y -Achsen unterschieden werden.<br />

θ = arccos Py − Zy<br />

R<br />

φ = arctan Pz − Zz<br />

Px − Zx<br />

(3.2)<br />

Für die Berechnung des arctan a sind aufgr<strong>und</strong> des Quotienten <strong>und</strong> der Punktsymmetrie des<br />

b<br />

Arcus Tangens einige Fallunterscheidungen zu beachten. Deshalb empfiehlt sich für diese Berechnung<br />

die Benutzung der atan2(a,b)- Funktion der math-Bibliothek, die diese Fälle korrekt<br />

berücksichtigt. Aus den Polarkoordinaten ergibt sich für die Abbildung auf das Intervall [0, 1]:<br />

sx := φ<br />

2π<br />

bzw. sy :=<br />

π − θ<br />

π<br />

(3.3)<br />

Externe Abbildungen: Projektionen auf <strong>Freiformflächen</strong> (siehe Kapitel 2), verlangen hier<br />

eine Sonderbehandlung. Diese Objekte bestimmen schon während der Schnittpunktberechnung<br />

die nötigen Werte <strong>und</strong> müssen diese nur der <strong>Texturierung</strong> zur Verfügung stellen.


KAPITEL 3. TEXTURIERUNG 38<br />

X /Y<br />

b b<br />

P<br />

X /Y<br />

a a<br />

(a)<br />

y<br />

1.0<br />

Abbildung 3.2: Arten des Texture Mapping<br />

(a) Lineare Abbildung. (b) Sphärische Abbildung (Mercator-Projektion).<br />

P<br />

Durch die Multiplikation der so errechneten Faktoren mit den Bildabmessungen in X- <strong>und</strong><br />

Y -Richtung, ergeben sich die Pixelkoordinaten des Bildpunktes, dessen Farbe der dargestellte<br />

Oberflächenpunkt annehmen soll.<br />

Diese Abbildungen haben gemein, das verwendete Bild auf die gesamte Oberfläche zu strecken<br />

bzw. zu stauchen. Durch das Einbeziehen der Abmessungen des zu projizierenden Bildes<br />

oder Musters, kann dieses Verhalten beeinflusst <strong>und</strong> eine gekachelte Anordnung erzielt werden.<br />

Dies wird mittels einer zusätzlich durchgeführten Restwert-Division (modulo) durch die<br />

Bildabmessungen erreicht.<br />

3.2.1.1 Die Bitmap-Textur<br />

Die Bitmap-Textur ist eine sehr einfache Textur, die nach den beschriebenen Verfahren das<br />

Motiv einer Bilddatei auf den Oberflächen <strong>von</strong> Objekten darstellt. Das Beispiel der Weltkarte,<br />

die auf eine Kugel projiziert wird (vgl. Abbildung 3.3), zeigt, dass Bilder für die Mercator-<br />

Projektion entsprechend vorverzerrt werden müssen, um eine nahtlose <strong>und</strong> gleichmäßige<br />

”Ummantelung” der Oberfläche zu erreichen.<br />

Abbildung 3.3: Die Mercator-Projektion<br />

Umwickeln einer Kugel mit einer Weltkarte<br />

1.0<br />

x<br />

(b)<br />

y<br />

Z<br />

P<br />

R<br />

z<br />

x


KAPITEL 3. TEXTURIERUNG 39<br />

3.2.1.2 Environment Mapping<br />

Environment Mapping ist ein recht einfaches <strong>und</strong> schnelles Verfahren, Reflexionen der Umgebung<br />

in der verspiegelten Oberfläche eines Objektes nachzubilden. Hierbei liegt die umgebende<br />

Szene bereits als Bild vor. Da die Ausrichtung dieses Bildes unabhängig vom Ort <strong>und</strong><br />

der Lage der Oberfläche ist, bleibt die gespiegelte Kulisse auch bei bewegtem Objekt stabil.<br />

Daher sorgt diese Textur besonders bei Animationen für sehr interessante <strong>und</strong> realitätsnahe<br />

Effekte.<br />

Abbildung 3.4: Environment Mapping bei Animationen<br />

Zur Berechnung des Environment Mapping, wird die Umgebung des Objekts durch die Projektion<br />

des Bildes auf einen virtuellen Hüllkörper simuliert. Für die Bestimmung der Farbinformation<br />

wird die Richtung der Reflexion im Schnittpunkt des Sichtstrahls S berechnet<br />

<strong>und</strong> dadurch der Reflexionsstrahl R festgelegt (vgl. Abbildung 3.5). Der Schnittpunkt dieses<br />

Strahls mit dem Hüllkörper identifiziert den Farbpunkt der Umgebung, der auf der Oberfläche<br />

des Objekts sichtbar wird. Für die Abbildung des Hüllkörperpunktes P auf das zweidimensionale<br />

Intervall des Umgebungsbildes, werden auch hier die oben beschriebenen Verfahren<br />

verwendet.<br />

S<br />

Abbildung 3.5: Environment Mapping mit Kugel als Hüllkörper<br />

Sehr einfach gestalten sich diese Berechnungen für eine Kugel als Hüllkörper. Da für die<br />

Mercator-Projektion letztendlich nur die Richtung des Vektors Zentrum − Schnittpunkt<br />

entscheidend ist, bleibt der Radius frei wählbar. Daher kann der Reflexionsvektor ohne weitere<br />

Berechnung direkt als Punkt auf der Kugel für die Projektion verwendet werden. Allerdings<br />

R<br />

P


KAPITEL 3. TEXTURIERUNG 40<br />

besteht auch hier der Nachteil, dass das Umgebungsbild passend vorverzerrt sein muss, um<br />

eine gleichmäßige Spiegelung zu simulieren. Leider ist die Erzeugung solcher Bilder recht<br />

aufwändig <strong>und</strong> hohe Qualität dementsprechend rar.<br />

Für einen Würfel als Hüllkörper bereitet die Aufnahme der Kulisse sehr viel weniger Probleme.<br />

Zwar werden hier natürlich für alle sechs Seiten Bilder benötigt, jedoch müssen diese<br />

nicht vorverzerrt werden. Die Kehrseite dieser Methode ist die um einiges aufwändigere Bestimmung<br />

des Farbpunktes, da hierzu eine vollständige Schnittpunktberechnung des Reflexionsstrahls<br />

mit der Würfelhülle erforderlich wird.<br />

Dennoch besitzt Environment Mapping auch einen entscheidenden Nachteil: Hinterschneidungen<br />

der Objektoberfläche werden nicht berücksichtigt <strong>und</strong> die Spiegelungen für diesen<br />

Fall falsch berechnet (vgl. Abbildung 3.6). Da es bei <strong>Ray</strong> <strong>Tracing</strong> allerdings auf wirklichkeitsgetreue<br />

Bilder ankommt, werden Spiegelungen möglichst direkt berechnet <strong>und</strong> diese Texturen<br />

meist nur für schnelle Näherungsberechnungen verwendet.<br />

Abbildung 3.6: Grenzen des Environment Mapping<br />

Fehlerhafte Spiegelungen bei Hinterschneidungen


KAPITEL 3. TEXTURIERUNG 41<br />

3.2.2 Prozedurale Texturen<br />

Prozedurale Texturen sind in der <strong>Texturierung</strong> sehr weit verbreitet <strong>und</strong> werden dazu verwendet,<br />

Strukturen sowie Farbverläufe <strong>von</strong> natürlichen Materialien nachzubilden. Holz, Marmor<br />

<strong>und</strong> Steine sind nur ein paar Beispiele aus dem schier endlosen Repertoire der dadurch simulierten<br />

Oberflächenstrukturen.<br />

Allen Texturen gemein ist die Eigenschaft, die veränderlichen Punktkoordinaten als Eingangsgröße<br />

zu verarbeiten um daraus Werte bzw. Mischverhältnisse für Farbe <strong>und</strong> Material zu generieren.<br />

Durch eine Vielzahl <strong>von</strong> mathematische Algorithmen beschrieben, erzeugen diese<br />

Texturen so eine breite Palette an Farb- <strong>und</strong> Material-Verläufen.<br />

3.2.2.1 Noise - Der Rauschgenerator<br />

Unregelmäßigkeiten in Struktur <strong>und</strong> Farbverlauf sind oft entscheidend für die realistische Wirkung<br />

Prozeduraler Texturen. Eine der wichtigsten Komponenten, die bei deren Erzeugung<br />

zum Einsatz kommen, ist Noise. Dieser Rauschgenerator ordnet jedem Punkt im 3D-Raum<br />

einen scheinbar zufälligen Wert zu. Die Anforderungen, die dabei an das erzeugte Rauschen<br />

gestellt werden, sind:<br />

• Der Wiederholungszyklus der Zahlenfolgen sollte möglichst lang sein, damit sich<br />

daraus generierte Muster nicht zu offensichtlich wiederholen <strong>und</strong> der Pseudo-Zufall<br />

auffällt.<br />

• Die Werte müssen alle in einem bestimmten Intervall, meist [−1, 1], liegen. Dadurch<br />

können Algorithmen, die das Rauschen verwenden, mit standardisierten Wertebereichen<br />

arbeiten.<br />

• Die generierten Zufallswerte sollten möglichst gleichverteilt sein, damit Zahlen aus dem<br />

Zufallsintervall alle gleich wahrscheinlich auftreten.<br />

• Der Zufall muss reproduzierbar sein: Für die gleichen Eingangsparameter muss die<br />

Funktion immer die gleichen Ergebnisse liefern. Der Gr<strong>und</strong> dafür ist die gewünschte<br />

Reproduzierbarkeit der daraus generierten Muster. Auch aus Gründen der Parallelisierung<br />

<strong>von</strong> Berechnungen, wofür sich <strong>Ray</strong> <strong>Tracing</strong> hervorragend eignet, ist dies entscheidend.<br />

• Das erzeugte Rauschen muss eine maximale Auflösung besitzen - das bedeutet, für eine<br />

genügend kleine Parameteränderung, können die ermittelten Werte zueinander in Beziehung<br />

gebracht werden. Diese Eigenschaft ist insbesondere für Anitaliasing-Techniken,<br />

wie Supersampling wichtig.<br />

Rauschgeneratoren, die all diese Forderungen erfüllen, basieren meist auf lattice noise (Gitterrauschen),<br />

das in einem dreidimensionalen Raster, dem so genannten integer lattice (vgl.<br />

Abbildung 3.7(a)), jedem Raumpunkt ganzzahliger Koordinaten, einen festen, aber zufälligen<br />

Wert aus dem Intervall [−1, 1] zuweist.


KAPITEL 3. TEXTURIERUNG 42<br />

Implementiert wird dies meist durch das Speichern dieser Werte in einer Rauschwert-Tabelle.<br />

Dies wird bereits dem Anspruch auf begrenzte Auflösung, Reproduzierbarkeit <strong>und</strong> dem standardisierten<br />

Werteintervall gerecht.<br />

Ein langer Wiederholungszyklus <strong>und</strong> eine wenigstens annähernde Gleichverteilung der Werte<br />

kann durch das Verwenden <strong>von</strong> guten Zufallsgeneratoren sichergestellt werden. Diese erzeugen<br />

die Zufallsfolgen meist recht aufwändig. Da die Tabelle aber nur einmal initialisiert werden<br />

muss, ist dabei die Geschwindigkeit nicht ausschlaggebend.<br />

Natürlich ist es nicht möglich, eine dreidimensionale Tabelle zu generieren, die tatsächlich<br />

für jeden Raumpunkt einen Rauschwert bereithält. Deshalb besitzen solche Tabellen verhältnismäßig<br />

geringe Ausmaße. Für durchaus gute Ergebnisse reicht bereits eine Größe <strong>von</strong> 1024<br />

Einträgen aus. Der Zugriff auf die Werte erfolgt dabei über eine Indexfunktion, die die Raumkoordinaten<br />

auf die verfügbare Größe der Tabelle abbildet.<br />

y<br />

z x<br />

(a)<br />

y<br />

1.0<br />

0.9<br />

0.8<br />

0.7<br />

0.6<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

0<br />

0<br />

0.1<br />

0.2 0.3 0.4<br />

0.5<br />

0.6 0.7 0.8 0.9<br />

Abbildung 3.7: Generierung <strong>von</strong> Rauschen<br />

(a) Zufallswerte im integer lattice. (b) Polynom als Glättungsfunktion.<br />

Für Punkte im Raum, deren Koordinaten rational sind, also irgendwo zwischen den Punkten<br />

im Gitter liegen, werden die Rauschwerte der umliegenden Punkte interpoliert. Mit lineare<br />

Interpolation treten in den ermittelten Werten allerdings schnelle Richtungswechsel auf, die<br />

in daraus generierten Mustern zu sichtbaren Kanten führen.<br />

Eine bessere Möglichkeit zur Interpolation bietet ein Polynom mit einem Wertebereich <strong>von</strong><br />

[0, 1] als Glättungsfunktion (vgl. Abbildung 3.7(b)). Diese liefert streng monoton steigende<br />

Funktionswerte aus dem Intervall [0, 1] <strong>und</strong> sorgt mit der Steigung 0 in den Grenzen des Wertebereichs<br />

für fließende Übergänge.<br />

Dennoch ist das dadurch erzeugte Rauschen noch sehr grob. Durch die Interpolation entstehen<br />

zwischen den Rauschwerten der Tabelle monotone Bereiche. Diese werden bei ausreichender<br />

Vergrößerung deutlich sichtbar.<br />

Die Spektralsynthese ist eine Methode, die es ermöglicht, auch die Gebiete zwischen den<br />

Zufallswerten der Tabelle zu ”verrauschen” ohne dafür die Auflösung des Rauschens erhöhen<br />

(b)<br />

1.0 x


KAPITEL 3. TEXTURIERUNG 43<br />

zu müssen. Dazu werden mehrere Rauschanteile mit steigender Frequenz, jedoch abnehmender<br />

Amplitude aufaddiert. Die ansteigende Frequenz der Rauschbänder wird dabei durch<br />

größer werdende Schrittweiten bei der Abtastung des Rauschraumes erreicht. Durch die Skalierung<br />

mit ihrer immer geringer werdenden Amplitude, liefern diese allerdings immer kleinere<br />

Beiträge. Dabei reichen bereits 3 Frequenzen aus, um dem Rauschen auch in Bereichen<br />

oberhalb der maximalen Auflösung einen sehr unregelmäßigen Werteverlauf zu geben.<br />

3.2.2.2 Die Karo-Textur<br />

(a) (b)<br />

Abbildung 3.8: 2D-Plot des generierten Rauschens<br />

(a) Interpoliert. (b) Spektralsynthese mit 3 Frequenzen.<br />

Die Karo-Textur ist eine sehr einfache, aber dennoch sehr oft genutzte Textur. Sie liefert eine<br />

karierte Anordnung zweier Gr<strong>und</strong>einstellungen <strong>von</strong> Farbe <strong>und</strong> Material. Da es sich dabei<br />

um eine zweidimensionale Anordnung handelt, werden auch hier die, in Abschnitt 3.2.1 beschriebenen<br />

Verfahren benutzt, um die 3D-Punktkoordinaten auf das Intervall ([0, 1] × [0, 1])<br />

des Karo-Musters abzubilden. Deshalb ergeben sich für diese Textur auch dieselben Verzerrungen<br />

auf der Oberfläche (vgl. Abbildung 3.9(a)).<br />

Für die, in einem Punkt zu wählenden Werte, kann mit einer XOR-Verknüpfung <strong>und</strong> den errechneten<br />

Faktoren mx <strong>und</strong> my ein sehr einfacher Ausdruck als Kriterium formuliert werden:<br />

boolV alue := mx > qx ⊕ my > qy<br />

(3.4)<br />

Um daraus ein schachbrettartiges Muster zu erhalten, reicht es aus, den beiden Zuständen<br />

(wahr <strong>und</strong> falsch) jeweils Farbe <strong>und</strong> Material zuzuordnen. Die beiden Vergleichswerte qx<br />

<strong>und</strong> qy geben dabei das Größenverhältnis nebeneinander liegender Karos in X- bzw. Y -<br />

Richtung an. Für die Werte qx = 0.5 <strong>und</strong> qy = 0.5 ergibt sich also ein klassisches Schachbrett.<br />

Da sich dieser Ausdruck nur für eine 2 × 2-Aufteilung einer Fläche eignet, muss die Größe<br />

der Karos über die Punkt- Abbildung gesteuert werden. Dazu kommt die, im Abschnitt 3.2.1<br />

erwähnte, gekachelte Anordnung zum Einsatz. Dabei werden die Ausdehnungen des zu projizierenden<br />

Musters (hier eine 2 × 2 Kachel) berücksichtigt. Dadurch wird die Karogöße bestimmbar.


KAPITEL 3. TEXTURIERUNG 44<br />

3.2.2.3 Die Klotz-Textur<br />

Auch die Klotz-Textur erzeugt eine karierte Anordnung zweier Farben bzw. Materialien. Im<br />

Unterschied zur Karo-Textur füllt die dadurch entstehende Struktur den Raum des texturierten<br />

Objektes vollständig aus. Deshalb zählt diese Textur zur Klasse der 3D-Texturen. Diese<br />

zeichnen sich dadurch aus, dass sie ohne Abbildung auf jeden Punkt im Raum anwendbar sind<br />

<strong>und</strong> jedem eine Farbe bzw. Material zuordnen.<br />

Auch hier kann diese Zuordnung für einen Punkt P über einen einfachen Ausdruck gesteuert<br />

werden:<br />

boolV alue := |⌊Px/Size.x⌋ + ⌊Py/Size.y⌋ + ⌊Pz/Size.z⌋| mod 2 (3.5)<br />

Wichtig ist hierbei das Abr<strong>und</strong>en der Summanden. Dadurch wird eine Spiegelung der Anordnung<br />

an den Koordinatenachsen verhindert.<br />

(a) (b)<br />

Abbildung 3.9: Texturbeispiele: Karo- <strong>und</strong> Klotz-Textur<br />

(a) An den Polen der Kugel ergeben sich deutliche Verzerrungen. (b) Klötze in Kugelform.<br />

3.2.2.4 Die Marmor-Textur<br />

Eine sehr eindrucksvolle <strong>und</strong> breit einsetzbare Textur ist die Marmorierung. Verschiedene<br />

Schichten aus Kalkgestein, die durch Druck <strong>und</strong> hohe Temperaturen in Marmor umgewandelt<br />

wurden, bilden die typische verwirbelte Maserung, die hellere Schichten durchzieht. Diese<br />

Verwirbelungen <strong>von</strong> Materialien versucht die Marmor-Textur nachzuahmen.<br />

Um Marmor zu simulieren, ist ein recht intuitives Vorgehen, dessen natürliche Entstehung aus<br />

mehreren Schichten zu imitieren. Diese Schichten können durch das Mischen der Gr<strong>und</strong>werte<br />

entlang einer Koordinate, z.B. X, entstehen. Das Mischungsverhältnis bestimmt dabei eine,<br />

über dem Intervall [0, 1] oszillierende Funktion.<br />

Durch eine Spline-Kurve im Intervall [−1, 1] [8][4], die die Werte aus dem gewünschten Bereich<br />

durchläuft, kann die Charakteristik der Marmorierung sehr gut beschrieben werden. Die


KAPITEL 3. TEXTURIERUNG 45<br />

Oszillation zur Abtastung der Spline-Werte, kann mittels einer Sinus-Funktion, mit der X-<br />

Koordinate als Parameter, erfolgen.<br />

y<br />

1.0<br />

0.9<br />

0.8<br />

0.7<br />

0.6<br />

0.5<br />

0.4<br />

0.3<br />

0.2<br />

0.1<br />

0.0<br />

−1.0<br />

−0.8<br />

−0.6<br />

−0.4<br />

−0.2<br />

0.0 0.2 0.4 0.6 0.8 1.0 x<br />

(a) (b) (c)<br />

Abbildung 3.10: Generierung der Marmor-Textur<br />

(a) Spline-Kurve. (b) Streifen-Textur ohne Turbulenz. (c) Verrauschte Streifen-Textur.<br />

Um nun für die typischen Verwirbelungen in den so entstandenen Schichten zu sorgen, reicht<br />

es aus, fraktales Rauschen als Phase auf den Sinus zu geben. Den Mischwert für die Farb- <strong>und</strong><br />

Materialanteile im Punkt P liefert dann folgender Ausdruck:<br />

3.2.2.5 Die Holz-Textur<br />

marble := spline(sin(Px + fractalNoise)) (3.6)<br />

Holz ist ein sehr vielseitig eingesetztes Material. Als Bau- oder Werkstoff verwendet, ist es<br />

fast überall zu finden. Darum wird die Holz-Textur in praktisch jedem <strong>Texturierung</strong>ssystem<br />

integriert. Die typische Holzzeichnung basiert auf den Jahresringen <strong>von</strong> Bäumen. Abhängig<br />

<strong>von</strong> der Richtung, in der diese Ringe beim Zuschneiden durchtrennt werden, entstehen die<br />

charakteristischen Muster.<br />

Die Funktion dieser Textur lässt sich auf das Erzeugen konzentrischer Kreise entlang einer<br />

festen Koordinatenachse zurückführen. Diese Kreise entstehen aus dem Abstand des Punktes<br />

zum Objektursprung. Entlang des berechneten Radius muss die gewählte Funktion dazu zwischen<br />

zwei Farbwerten alternieren.<br />

Eine einfache Möglichkeit für diesen Wechsel, ist die Anwendung der Modulo-Funktion auf<br />

den abger<strong>und</strong>eten Radius. Wird die X-Koordinate als feste Achse gewählt, ergibt sich für den<br />

Punkt P :<br />

��<br />

rings := Py 2 + Pz 2<br />

�<br />

mod 2<br />

Jedoch werden dadurch die Ränder der Ringe sehr scharfkantig <strong>und</strong> sehen damit nicht sonderlich<br />

natürlich aus (vgl. Abbildung 3.11(a)). Weichere Übergänge, zumindest für die inneren


KAPITEL 3. TEXTURIERUNG 46<br />

Ränder, versprechen hier Besserung. Dieser Effekt kann durch das Verwerfen des Ganzzahlanteils<br />

des Radius erzielt werden. Die weichen Farb-Verläufe an den Innenrändern der Ringe<br />

sind dadurch allerdings noch zu breit. Eine Korrektur wird durch Potenzierung mit einem<br />

Wert für die Ringschärfe möglich. Dadurch werden die Übergänge schmaler (vgl. Abbildung<br />

3.11(b)).<br />

�<br />

smoothRings(Py, Pz) :=<br />

Py 2 + Pz 2 −<br />

��<br />

Py 2 + Pz 2<br />

wood := smoothRings(Py + fractalNoise, Pz + fractalNoise) sharpness (3.7)<br />

Natürlich wachsen Bäume nicht immer gleichmäßig. Dies wird an den Jahresringen durch<br />

Schwankungen in Dicke <strong>und</strong> Form sichtbar. Aus diesem Gr<strong>und</strong> wird auf die variablen Koordinaten<br />

Y <strong>und</strong> Z fraktales Rauschen addiert, um diese Schwankungen zu simulieren (vgl.<br />

Abbildung 3.11(c)).<br />

(a) (b) (c)<br />

Abbildung 3.11: Generierung der Holz-Textur<br />

(a) Scharfkantige Jahresringe. (b) Ringe mit weicheren Übergängen. (c) Ringe mit Turbulenz.<br />


KAPITEL 3. TEXTURIERUNG 47<br />

3.2.3 Bump Mapping<br />

Die Wahrnehmung <strong>von</strong> Flächen wird wesentlich vom Spiel mit Licht <strong>und</strong> Schatten beeinflusst.<br />

Rauheit <strong>und</strong> kleine Strukturen an der Oberfläche prägen das Erscheinungsbild durch<br />

Veränderung des Schattenwurfs <strong>und</strong> der Reflexionsmuster. Deshalb ist es für einen realistischen<br />

Eindruck <strong>von</strong> simulierten Objekten sehr wichtig auch diese Feinheiten nachzubilden.<br />

Es ist klar, dass die geometrische Konstruktion dieser Details eine sehr aufwändige, wenn<br />

nicht gar unmögliche Vorgehensweise darstellt. Der damit verb<strong>und</strong>ene Aufwand an Speicher<br />

<strong>und</strong> Rechenzeit setzt einfach zu enge Schranken.<br />

Bump Mapping ist ein <strong>von</strong> Blinn 1 entwickeltes Verfahren, das es ermöglicht, einer Oberfläche<br />

ein rauhes oder verbeultes Aussehen zu verleihen, ohne diese Details mühsam konstruieren<br />

zu müssen.<br />

Der Effekt des Bump Mapping beruht auf der gezielte Veränderung der Flächennormalen <strong>und</strong><br />

beeinflusst dadurch natürlich die Berechnung der da<strong>von</strong> abhängigen Lichtanteile, während<br />

der Schattierung. Abbildung 3.12 zeigt diese Perturbation - eine ”Störung” konstruktiver Art<br />

- des Normalenvektors:<br />

u<br />

u ’<br />

n<br />

Abbildung 3.12: Perturbation der Flächennormale<br />

Wie die Abbildung zeigt, entsteht die veränderte Normale �n ′ durch Aufaddieren des ”Störungsvektors”<br />

� d, der sich aus den beiden Vektoren �u ′ <strong>und</strong> �v ′ zusammensetzt. Diese wiederum errechnen<br />

sich aus den zwei Vektoren �u <strong>und</strong> �v sowie der Bump-Funktion F , wie folgt:<br />

v ’<br />

d<br />

n ’<br />

v<br />

�u ′ = ∂F<br />

�u<br />

∂u<br />

(3.8)<br />

�v ′ = ∂F<br />

�v<br />

∂v<br />

(3.9)<br />

�d = �u ′ + �v ′<br />

(3.10)<br />

�n ′ = �n + � d (3.11)<br />

Hierbei stellen die beiden Vektoren �u <strong>und</strong> �v zwei beliebige, aber zueinander senkrechte Vektoren<br />

aus der Tangentialebene im Punkt P dar. Um diese zu konstruieren muss ein Hilfsvektor<br />

� h gef<strong>und</strong>en werden, der nicht parallel zu �n ist. Dabei hilft eine Analyse des Normalenvektors<br />

1 Blinn, J.F.: ”Simulation of Wrinkled Surfaces”, SIGGRAPH ’78


KAPITEL 3. TEXTURIERUNG 48<br />

�n: Handelt es sich dabei um ein Vielfaches des Vektors (1, 0, 0), kann dazu beispielsweise der<br />

Vektor (0, 1, 0) verwendet werden, andernfalls (1, 0, 0). Mit Hilfe des Vektorproduktes <strong>und</strong><br />

anschließender Normierung ergeben sich daraus �u <strong>und</strong> �v:<br />

�u = �n × � h, �u 0 = 1<br />

|�u| �u<br />

�v = �n × �u, �v 0 = 1<br />

|�v| �v<br />

Die Bump-Funktion F (siehe nächster Abschnitt) ermittelt die Steigung der Textur im Punkt<br />

P , die sich durch das Punktprodukt in ihre Anteile der Richtungen �u <strong>und</strong> �v aufspalten lässt:<br />

3.2.3.1 Die Bump-Funktion<br />

∂F<br />

∂u<br />

∂F<br />

∂v<br />

−−−→<br />

= F (P ) · �u<br />

−−−→<br />

= F (P ) · �v<br />

Das Ausmaß der Veränderung der Flächennormalen durch Perturbation, wird durch die Steigung<br />

der Textur festgelegt. Aufgabe der Bump-Funktion ist es, diese Steigung für jeden Punkt<br />

der Textur zu ermitteln. Diese kann sich aus den Beiträgen mehrerer Komponenten zusammensetzen:<br />

• Die Bump Map ist der Klassiker <strong>und</strong> daher auch eine der bekanntesten Methoden, die<br />

im Bump Mapping zum Einsatz kommen. Hierbei wird eine Bilddatei verwendet, um<br />

aus der Differenz der Grauwerte ihrer Pixel, Steigungen in den einzelnen Punkten zu ermitteln<br />

(vgl. Abbildung 3.13(a)). Im Gr<strong>und</strong>e kann dazu zwar jedes Bild als Bump Map<br />

verwendet werden, die besten Ergebnisse lassen sich allerdings nur mit speziell dafür<br />

präparierten Graustufenbildern erzielen.<br />

Ähnlich zur Bitmap-Textur muss die verwendete Map auch hier passend auf die Oberfläche<br />

projiziert werden. Für diese Abbildung werden die im Abschnitt 3.2.1 beschriebenen<br />

Algorithmen verwendet.<br />

• Auch die Textur kann <strong>von</strong> sich aus einen Beitrag zur Steigung liefern. Dies ist natürlich<br />

<strong>von</strong> der Art der Textur <strong>und</strong> deren Vorgehen bei der Ermittlung <strong>von</strong> Farbe <strong>und</strong> Material<br />

abhängig. Am Beispiel der Karo-Textur wird dies ersichtlich:<br />

Die ermittelte Steigung in einem Punkt, wird auf die Ränder der Karos abgestimmt.<br />

So liefert diese Textur in den Randbereichen der Primärfarbe ein Gefälle <strong>von</strong> 45 ◦ (vgl<br />

Abbildung 3.13(b)).<br />

• Turbulenzen durch fraktales Rauschen ermöglichen durch Simulation <strong>von</strong> Unebenheiten<br />

<strong>und</strong> Rauheit den Eindruck realistischer Oberflächen. Die Steigung in den einzelnen<br />

Punkten wird durch Spektralsynthese - d.h. durch Aufaddieren <strong>von</strong> Steigungen immer<br />

höher werdender Frequenz aber abnehmender Amplitude - entwickelt.<br />

Die Kombination der ermittelten Steigungen erfolgt mittels Addition <strong>und</strong> ergibt die Steigung<br />

für die weitere Berechnung nach Gleichung (3.8) bzw. (3.9).


KAPITEL 3. TEXTURIERUNG 49<br />

Die Anteile beeinflussen sich durch die Kombination natürlich gegenseitig <strong>und</strong> können durch<br />

Verstärken oder Auslöschen einzelner Steigungsanteile unerwartete Ergebnisse hervorrufen.<br />

Aus diesem Gr<strong>und</strong> sollten die einzelnen Komponenten mit Bedacht kombiniert <strong>und</strong> deren<br />

Einstellungen entsprechend vorsichtig gewählt werden, um die gewünschten Resultate zu erzielen.<br />

(a) (b) (c)<br />

Abbildung 3.13: Texturbeispiel: Bump Mapping<br />

(a) Bild mit Mauerwerk als Bump Map. (b) Bump mittels Karo-Textur. (c) Oberflächenturbulenz.<br />

3.2.3.2 Grenzen des Bump Mapping<br />

Gewiss bringt auch Bump Mapping Nachteile mit sich <strong>und</strong> stößt dadurch an die Grenzen seiner<br />

Einsetzbarkeit. Die durch Bump Mapping simulierten Details der Oberfläche, beeinflussen<br />

nur die Berechnungen der Schattierung. Die Schnittberechnungen, die ja zum Zeitpunkt der<br />

<strong>Texturierung</strong> längst abgeschlossen sind, werden dadurch natürlich nicht beeinflusst. Darum<br />

bleiben die ursprünglichen Konturen <strong>und</strong> der Schattenwurf der Objekte unverändert (vgl. Abbildung<br />

3.14). Diese Fehler treten vor allem bei kontrastreichem Hintergr<strong>und</strong> oder größerer<br />

Bump-Tiefe auf sehr deutlich hervor.<br />

Abbildung 3.14: Grenzen des Bump Mapping<br />

Fehler in Kontur <strong>und</strong> Schattenwurf


KAPITEL 3. TEXTURIERUNG 50<br />

3.3 Das Textur-System<br />

Texturen sind den Oberflächen <strong>von</strong> Objekten in der Regel fest zugeordnet. Daran sollte auch<br />

eine Transformation des Objektes nichts ändern. Eine auf das Objekt angewandte Rotation<br />

muss sich demnach auch auf die Textur auswirken - sie dreht sich mit. Mit Ausnahme <strong>von</strong><br />

Texturen, die explizit mit den Weltkoordinaten arbeiten (z.B. Environment Mapping), gilt<br />

dies für alle Texturen.<br />

Dennoch sollte es möglich sein, die Lage <strong>und</strong> Ausrichtung einer Textur auf der Oberfläche<br />

zu beeinflussen. Darum macht es Sinn, Texturen mit einem eigenen Koordinatensystem auszustatten.<br />

Da dieses System relativ zu den Objektkoordinaten orientiert ist, muss die entsprechende<br />

Transformation <strong>von</strong> Schnittpunkten <strong>und</strong> Flächennormalen nach der Objekt-Transformation<br />

stattfinden.<br />

Für die Entwicklung <strong>von</strong> Texturen ergeben sich daraus klare Vorteile:<br />

• Textur-Algorithmen können sehr einfach gehalten werden, da standardisierte Koordinatenräume<br />

zu Gr<strong>und</strong>e gelegt werden können.<br />

• Transformationen der Texturen können sehr einfach, schnell <strong>und</strong> zentral durchgeführt<br />

werden.<br />

• Die implementierten Transformationen können ohne Modifikation auf alle Texturen angewendet<br />

werden. Ob dies auch sinnvoll ist, muss natürlich <strong>von</strong> Fall zu Fall entschieden<br />

werden.<br />

3.4 Fazit<br />

Texturen machen modellierte Szenen erst lebendig. Meist ist der zusätzliche Berechnungsaufwand<br />

auch recht gering. Wie die vorgestellten Algorithmen zeigen, eröffnen dabei schon<br />

einfache Methoden, sehr interessante Möglichkeiten für die Gestaltung <strong>von</strong> Oberflächen.<br />

Auch die Implementierung kann zum Teil sehr einfach <strong>und</strong> kompakt gehalten werden. Details<br />

werden dazu in Kapitel 4 beschrieben.


Kapitel 4<br />

Implementierung<br />

Die in den vorhergehenden Kapiteln beschriebenen Funktionen <strong>und</strong> Algorithmen wurden in<br />

der Programmiersprache C++ implementiert <strong>und</strong> in einen bestehenden <strong>Ray</strong> Tracer integriert.<br />

Als Gr<strong>und</strong>lage wurden hierfür die Systeme aus den Arbeiten <strong>von</strong> A. Riepl [7] <strong>und</strong> O. Groth<br />

[3] verwendet. Auch fanden dabei viele, der für <strong>Freiformflächen</strong> erforderlichen Berechnungen<br />

aus der Arbeit <strong>von</strong> U. Lugauer [5] Verwendung.<br />

Ein Ziel war, die ursprünglichen Systeme so weit wie möglich zu erhalten. Aufgr<strong>und</strong> <strong>von</strong> erforderlichen<br />

Änderungen an Abläufen <strong>und</strong> den bestehenden Klassenhierarchien war es meist<br />

jedoch notwendig, die übernommenen Algorithmen unter anderen Gesichtspunkten zu strukturieren<br />

<strong>und</strong> Funktionsgruppen anders zu organisieren. Daraus ergab sich eine, zum Teil erheblich<br />

veränderte Aufteilung.<br />

Nicht zuletzt durch die Integration <strong>von</strong> <strong>Freiformflächen</strong> in die Objekthierarchie, mussten viele<br />

bereits bestehende Bereiche überarbeitet <strong>und</strong> angepasst werden. Im Zuge dieser Veränderungen<br />

ergaben sich einige Ergänzungen des Systems, die Erweiterungen der Definitionssprache<br />

für den Szenenentwurf nötig machten.


KAPITEL 4. IMPLEMENTIERUNG 52<br />

4.1 Allgemeine Funktionsweise<br />

Aufgr<strong>und</strong> der erfolgten Veränderungen am System, ist es sinnvoll zunächst einen Überblick<br />

über die aktuelle Aufteilung <strong>und</strong> Funktionsweise des <strong>Ray</strong> <strong>Tracing</strong>-Systems zu geben. Der<br />

Ablauf des <strong>Ray</strong> <strong>Tracing</strong>-Prozesses ist im Anhang A, Abbildung A.1 als Sequenzdiagramm<br />

dargestellt, auf welches sich die folgende Erläuterung bezieht.<br />

4.1.1 Vorbereitungen <strong>und</strong> Parsing<br />

Das Programm startet im Modul <strong>Ray</strong>trace mit der Auswertung der Parameterliste. Diese bestimmt<br />

die zu verarbeitende Datei zur Definition der Szene sowie einiger weiterer, für den<br />

Ablauf wichtiger Einstellungen. Detaillierte Angaben zu den Optionen der Parameterliste finden<br />

sich im Anhang B.<br />

Der erste Schritt der Vorbereitungen ist die Instanzierung der Klasse Model. Diese ist für<br />

die Verwaltung aller Daten verantwortlich, die während des <strong>Ray</strong> <strong>Tracing</strong>-Durchlaufs benötigt<br />

werden. Dazu zählen vor allem die interne Szenendefinition, bestehend aus allen Objekten <strong>und</strong><br />

Lichtquellen, die Konfiguration des Beobachterstandpunktes sowie ein optional definierter<br />

Octree zur Optimierung der Berechnungen.<br />

Der Aufbau der benötigten Datenstrukturen erfolgt durch den Parser. Mit Hilfe der Klasse<br />

Scanner liest dieser die Szenenbeschreibung der zu verarbeitenden Datei ein <strong>und</strong> wandelt die<br />

darin festgelegten Definitionen in die interne Darstellung durch die verschiedenen Strukturen<br />

um. Die für die Spezifikation zu beachtende Syntax der Beschreibungs-Sprache ist im Anhang<br />

C mittels EBNF 1 beschrieben.<br />

Der Parser arbeitet nach dem Recursive-Descent-Verfahren. Deshalb ist der Ablauf sehr stark<br />

an der verwendeten Syntax orientiert. Aus diesem Gr<strong>und</strong> wird auch der Parsing-Prozess bereits<br />

durch die abstrakte Sprach-Beschreibung der EBNF übersichtlich dokumentiert <strong>und</strong> soll<br />

deshalb an dieser Stelle nicht detaillierter beschrieben werden.<br />

Die finale Initialisierung des Modells (buildModel()) stellt den Abschluss der Parsing-Phase<br />

dar. Dazu gehören der Aufbau eines eventuell spezifizierten Octrees sowie das Einhängen der<br />

passenden Funktionen für die Schnittpunktsuche <strong>und</strong> das verwendete Beleuchtungsmodell.<br />

Nach dem erfolgreichen Durchlauf des Parsers <strong>und</strong> Konstruktion des internen Modells erfolgt<br />

das Anlegen eines Image-Objekts als Einheit für die Ausgabe der berechneten Bilddaten.<br />

Der genaue Typ der Instanzierung wird dabei durch die Optionen der Parameterliste<br />

festgelegt. Im aktuellen Stand umfasst die Implementierung eine Window-Klasse, basierend<br />

auf dem XWindow-System für die Darstellung während der Bildberechnung sowie das TGA-<br />

Bildformat für die Ausgabe als Datei.<br />

1 Extended Backus Naur Form


KAPITEL 4. IMPLEMENTIERUNG 53<br />

4.1.2 Ablauf der Bildberechnung<br />

Die Berechnungen der Bilddaten startet mit dem Aufruf der zur Model-Klasse gehörenden<br />

Methode renderModel(). Hier findet die Iteration über alle Bildpunkte der Ansicht statt. Das<br />

Ziel ist es dabei, für jeden Pixel die Farbanteile zu bestimmen <strong>und</strong> diese durch die Methode<br />

setPixelColor() des Image-Objektes in das Bild eintragen zu lassen.<br />

Zur Bestimmung der Farbe eines Bildpunktes wird in die Funktion sample() aus dem Modul<br />

Shade verzweigt. In dieser Funktion erfolgen die Berechnungen des Adaptive Supersampling<br />

2 . Die Rekursion des Sampling wird dabei durch einen Schwellwert für die Farbdifferenz<br />

<strong>und</strong> einem maximalen Level des Abstiegs begrenzt.<br />

Von hier aus startet auch die Verfolgung der Strahlen durch die Szene. Wie im Diagramm<br />

dargestellt, werden diese erst mittels new<strong>Ray</strong>Direction() bestimmt <strong>und</strong> dann der Funktion<br />

trace() als Parameter übergeben. Da die Verfolgung der Strahlen vom Algorithmus <strong>und</strong> den<br />

Berechnungen des gewählten Beleuchtungsmodells 3 abhängt, wurde trace() als Funktionspointer<br />

realisiert, der vom Parser auf die zu verwendende Funktion gesetzt wird.<br />

Für die Berechnungen der Schattierung, wird der Schnittpunkt des Strahls mit dem naheliegensten<br />

Objekt gesucht. Diese Suche kann dabei durch einen normalen Durchlauf <strong>und</strong> Test<br />

aller Objekte oder über den optimierten Algorithmus des Octrees erfolgen. Aus diesem Gr<strong>und</strong><br />

wird auch dieser Aufruf über einen Funktionspointer gesteuert, der durch den Parser entweder<br />

auf die Standardimplementierung in der Model-Klasse oder, bei aktiviertem Octree, auf<br />

dessen Suchfunktion zeigt.<br />

Die tatsächliche Berechnung <strong>von</strong> Schnittpunkten während dieser Suche muss natürlich passend<br />

zum Typ des Objekts durchgeführt werden. Die Objekt-Hierarchie wurde rein durch<br />

klassische Vererbung aufgebaut. Aus diesem Gr<strong>und</strong> kann die Unterscheidung <strong>und</strong> Auswahl<br />

der Schnittalgorithmen getrost dem Polymorphismus überlassen werden. Für eine detaillierte<br />

Beschreibung des Aufbaus der Object-Hierarchie sei hier auf den Abschnitt 4.2 verwiesen.<br />

Liefert die Schnittpunktsuche ein positives Ergebnis, werden für die weiteren Berechnungen<br />

des Beleuchtungsmodells die genauen Daten des naheliegendsten Objekts benötigt. Dazu<br />

gehören das Ausmaß der ambienten Beleuchtung des Objekts sowie die Flächennormale, die<br />

Farbe <strong>und</strong> das Material im Punkt des Treffers. Diese Daten können über die Schnittstellen der<br />

Object-Klasse angefordert werden.<br />

Damit das getroffene Objekt allerdings diese Werte liefern kann, müssen ihm vorher die Daten<br />

des Treffers - Strahl <strong>und</strong> Schnittpunkt - für eine Auswertung zur Verfügung gestellt werden.<br />

Dies erfolgt über setHitData() der Object-Klasse.<br />

Dabei führt diese Methode die erforderlichen Transformationen der Daten durch <strong>und</strong> veranlasst<br />

die Berechnung aller wichtigen Größen <strong>und</strong> Werte, die für die Schattierung benötigt<br />

werden.<br />

2 Rekursives Anti-Aliasing-Verfahren, das dazu dient, den Stufeneffekt an Kanten <strong>und</strong> harten Farbübergängen<br />

zu reduzieren. Dies erfolgt durch Mischen der Farbbeiträge mehrerer Strahlen (vgl. [7]).<br />

3 Im aktuellen Stand sind die Modelle nach Whitted <strong>und</strong> Phong implementiert


KAPITEL 4. IMPLEMENTIERUNG 54<br />

Wie das Diagramm andeutet, werden die Daten des Schnittpunktes auch zur Berechnung der<br />

Textur verwendet <strong>und</strong> deshalb an die zugeordnete Texturinstanz weitergegeben. Der Ablauf,<br />

der im Objekt <strong>und</strong> in den Klassen der Textur während dieser Auswertung stattfindet, wird in<br />

den Abschnitten 4.2 <strong>und</strong> 4.3 genauer beschrieben.<br />

Anhand der dadurch ermittelten Informationen über die Objektoberfläche, erfolgt die Schattierung<br />

des Punktes. Abhängig <strong>von</strong> den Eigenschaften des Materials, konkret sind dies die<br />

Transparenz <strong>und</strong> die Reflektivität, wird die Strahlverfolgung mittels trace() rekursiv fortgesetzt.<br />

Der Abstieg wird dabei durch die Überprüfung eines Schwellwertes für den Beitrag zur<br />

Farbinformation sowie einen Test auf maximale Rekursionstiefe kontrolliert.


KAPITEL 4. IMPLEMENTIERUNG 55<br />

4.2 Die Objekte<br />

Zur Implementierung der Objekte, wurde die bereits bestehende Klassen-Hierarchie weitgehend<br />

übernommen. Allerdings musste diese zwecks Integration <strong>von</strong> <strong>Freiformflächen</strong> (siehe<br />

auch nächster Abschnitt) überarbeitet werden. Ein Hauptgr<strong>und</strong> dafür war die Art der Definition<br />

dieses Flächentyps: Die dazu benötigten Kontrollpunkte müssen absolut, d.h. in Weltkoordinaten<br />

spezifiziert werden. Zur Anordnung dieser Objekte innerhalb der Szene war es deshalb<br />

notwendig, erweiterte Transformationen zu ermöglichen <strong>und</strong> ein Objekt-Koordinatensystem<br />

einzuführen (vgl. auch Kapitel 1, Abschnitt 1.4).<br />

Desweiteren mussten hinsichtlich der Veränderungen an der Textur-Hierarchie, die Schnittstellen<br />

zur Kommunikation mit dem <strong>Texturierung</strong>ssystem (vgl. Abschnitt 4.3) angepasst werden.<br />

Leider ist es nicht möglich, die Texturen ganz unabhängig vom Objekt einzusetzen. Für<br />

die 2D-Abbildungen (vgl. Kapitel 3, Abschnitt 3.2.1) müssen die Abmessungen der Oberfläche<br />

<strong>und</strong> die Art der Projektion bekannt sein. Um diese Einstellungen spezifisch anpassen<br />

zu können, werden die Methoden zu den Schnittstellen matchMappingMode() <strong>und</strong> matchSurfaceDimension()<br />

<strong>von</strong> den Objekten implementiert. Diese werden vom Parser dazu verwendet,<br />

die passenden Einstellungen zu ermitteln <strong>und</strong> angewendete Texturen auf die Art der Objekte<br />

abzustimmen.<br />

Abbildung 4.1 zeigt den Aufbau der abstrakten Basisklasse Object. Ein Diagramm der gesamten<br />

Objekt-Hierarchie findet sich im Anhang A.<br />

Object<br />

+ setHitData(hitPoint) : void<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ getColor() : Color<br />

+ getNormal() : Vector<br />

+ getAmbience() : Color<br />

+ getMaterial() : Material<br />

# toObject : Matrix<br />

# toWorld : Matrix<br />

# ambience : Color<br />

# texture : Texture *<br />

4.2.1 Objekt-Transformation<br />

Abbildung 4.1: Die Basisklasse der Objekte<br />

Es sprechen einige Punkte für die Implementierung des Objekt-Systems:<br />

• Es ergeben sich vereinfachte Berechnungen für Schnittpunkte <strong>und</strong> Normalenvektoren,<br />

da diese nur die Gr<strong>und</strong>stellung der Objekte berücksichtigen müssen.


KAPITEL 4. IMPLEMENTIERUNG 56<br />

• Die Transformation des Strahls kann mittels Transformationsmatrizen schnell <strong>und</strong> einheitlich<br />

durchgeführt werden.<br />

• Die implementierten Transformationen können leicht kombiniert werden <strong>und</strong> sind auf<br />

alle Objekte anwendbar.<br />

Zur Implementierung der Objekt-Koordinaten wurde die Object-Basisklasse mit zwei Transformationsmatrizen<br />

ausgestattet. Eine der beiden (toObject) dient zur Transformation des<br />

Strahles in Objekt-Koordinaten <strong>und</strong> wird bereits während des Parser-Durchlaufs für die nötigen<br />

Transformationen vorbereitet. Bei Kombination <strong>von</strong> mehreren Einzeltransformationen<br />

werden diese mit Hilfe der Matrizenmultiplikation zusammengefasst.<br />

Um Punkte aus dem Objekt-System in Weltkoordinaten zu überführen, benötigen die Algorithmen<br />

des Octree die inverse der Objekt-Transformation. Diese wird mit Hilfe des Gauss-<br />

Jordan-Algorithmus berechnet <strong>und</strong> in der zweiten Transformationsmatrix (toWorld) abgelegt.<br />

4.2.2 Ablauf objektinterner Berechnungen<br />

Die Suche nach Treffern des Strahls erfordert die Schnittpunktberechnungen der Objekte. Dazu<br />

bietet die abstrakte Basisklasse Object die Schnittstelle intersect(). Diese Methode ist rein<br />

virtuell <strong>und</strong> muss in den Objekten, die sich <strong>von</strong> Object ableiten, implementiert werden. Dadurch<br />

erfolgt eine Typenüberprüfung zur Laufzeit <strong>und</strong> die Algorithmen für die Berechnungen<br />

werden automatisch passend gewählt.<br />

Der Aufruf erwartet den Strahl als Parameter <strong>und</strong> liefert einen Wahrheitswert sowie, im Fall<br />

eines Treffers, die Distanz des Schnittpunktes zum Strahlursprung.<br />

Für das Objekt, das den am nächsten zum Strahlursprung liegenden Treffer aufweist, müssen<br />

die vom Beleuchtungsmodell geforderten Daten der Oberfläche ermittelt werden. Die hierzu<br />

notwendigen Auswertungen werden in der Methode setHitData() koordiniert <strong>und</strong> typspezifische<br />

Berechnungen, wie die Normalenermittlung, veranlasst. Hier erfolgt auch der Datenaustausch<br />

mit dem <strong>Texturierung</strong>ssystem (Texture::setHitData()), das die Werte für Farbe, Material<br />

<strong>und</strong> die Perturbation des Normalenvektors liefert (siehe Abschnitt 4.3).<br />

Da dieses Vorgehen für die meisten Objekte einheitlich ist, wurde die Behandlung bereits in<br />

Object implementiert. Objekte, wie Box <strong>und</strong> FreeformSurface bilden hier allerdings die Ausnahme,<br />

weshalb die Methode auch virtual <strong>und</strong> damit überschreibbar gehalten wurde.<br />

Nach Ablauf <strong>von</strong> setHitData(), können die ermittelten Daten vom Objekt angefordert werden.<br />

Dazu stellt die Klasse Object die Schnittstellen getColor(), getMaterial(), getNormal()<br />

<strong>und</strong> getAmbience() bereit. Die Farbe <strong>und</strong> das Material werden <strong>von</strong> der Textur bereitgehalten,<br />

wodurch die entsprechenden Aufrufe direkt an diese weitergeleitet werden.


KAPITEL 4. IMPLEMENTIERUNG 57<br />

4.3 Die Texturen<br />

Die Klassen-Hierarchie des <strong>Texturierung</strong>ssystems wurde neu entworfen <strong>und</strong> implementiert.<br />

Die dabei verwendeten Texturalgorithmen wurden zu großen Teilen aus der Arbeit <strong>von</strong> O.<br />

Groth [3] übernommen. Diese wurden jedoch unter anderen Gesichtspunkten strukturiert <strong>und</strong><br />

gegliedert. Daher ergaben sich zum Teil erhebliche Veränderungen hinsichtlich Aufteilung<br />

<strong>und</strong> Funktionsweise.<br />

Der komplette Aufbau der Textur-Hierarchie wird aus dem Diagramm A.3 im Anhang A<br />

ersichtlich. An dieser Stelle werden die Eigenschaften <strong>und</strong> Besonderheiten des Systems<br />

erläutert. Dabei wird auf Abbildung 4.2 Bezug genommen, die dazu die wichtigsten Daten<br />

<strong>und</strong> Methoden der Basisklasse Texture <strong>und</strong> der da<strong>von</strong> abgeleiteten Klasse BumpTexture zeigt.<br />

Texture<br />

+ setHitData(hitPoint) : void<br />

+ set<strong>Ray</strong>AndNormal(ray,normal) : void<br />

+ is2D() : bool<br />

+ needs<strong>Ray</strong>AndNormal() : bool<br />

+ detectTextureData() : void<br />

+ getColor() : Color<br />

+ getMaterial() : Material<br />

# toTexture : Matrix<br />

# fromTexture : Matrix<br />

# baseColor : Color<br />

# baseMaterial : Material<br />

− getCoordTurbulence(point) : Vector<br />

− turbulence : Turbulence<br />

4.3.1 Das Textur-System<br />

BumpTexture<br />

+ getBumpNormal(Vector) : Vector<br />

+ is2D() : bool<br />

+ needs<strong>Ray</strong>AndNormal() : bool<br />

+ detectTextureData() : void<br />

# getTextureSlope()<br />

− getMapSlope()<br />

− getNoiseSlope()<br />

− bumpSize : Real<br />

− turbulence : Turbulence<br />

− mapper : PointMapper<br />

− bumpMap : TGAImage *<br />

Abbildung 4.2: Die Spitze der Textur-Klassenhierarchie<br />

Ähnlich zum Koordinatensystem der Objekte, wurden die Texturen mit einem eigenen Transformations-System<br />

ausgestattet (siehe auch Kapitel 3, Abschnitt 3.3). In der Implementierung<br />

werden auch hier zueinander inverse Transformationsmatrizen (toTexture <strong>und</strong> fromTexture)<br />

für die Überführung zwischen den Koordinatensystemen verwendet. Diese werden bereits<br />

während des Parser-Durchlaufs entsprechend der Texturtransformationen berechnet.<br />

Da sich die Textur mit dem Objekt bewegen muss, werden die Texturtransformationen auf den<br />

Strahl angewendet nachdem dieser bereits in das Objekt-System transformiert wurde.<br />

Probleme bei der Implementierung ergaben sich durch R<strong>und</strong>ungsfehler bei den Transformationen.<br />

Diese beeinflussen das Erscheinungsbild vor allem bei Texturen, deren Algorithmen<br />

die Texturinformationen wie Farbe <strong>und</strong> Material aufgr<strong>und</strong> diskreter Schwellwerte


KAPITEL 4. IMPLEMENTIERUNG 58<br />

auswählen, wie dies bei der Karo- <strong>und</strong> Klotz-Textur geschieht. Besonders auffällig sind diese<br />

Fehler bei Objekten mit ebenen Flächen in den Koordinatenebenen, wie der Box. Hier entsteht<br />

ein sichtbares Rauschen in einzelnen Abschnitten der Textur.<br />

Zur Lösung dieses Problems müssen in diesen Objekten, nach der Transformation, die R<strong>und</strong>ungsfehler<br />

durch Korrektur der Schnittpunktkoordinaten ausgeglichen werden.<br />

4.3.2 Ablauf interner Textur-Berechnungen<br />

Zur Bestimmung <strong>von</strong> Farbe <strong>und</strong> Material brauchen die Texturen einige Daten. Diese werden<br />

<strong>von</strong> der Methode setHitData() der Object-Klasse aus an die Textur übergeben. Dies geschieht<br />

mit Hilfe der Schnittstellen der Texture-Basisklasse sowie der Klasse BumpTexture.<br />

Eine der wichtigsten Informationen ist der Schnittpunkt des Strahls mit dem Objekt. Dieser<br />

muss dazu bereits in Objekt-Koordinaten vorliegen <strong>und</strong> kann der Textur durch die Methode<br />

setHitData() bekannt gemacht werden. Unter Umständen ist dabei für das Objekt die Dimension<br />

der Textur entscheidend 4 . Ob eine 2D-Textur vorliegt, kann durch die Schnittstelle is2D()<br />

ermittelt werden.<br />

Der Verlauf <strong>von</strong> Farbe <strong>und</strong> Material kann, unabhängig <strong>von</strong> der Art der Textur, einer Turbulenz<br />

unterworfen werden. Die dazu notwendigen Schritte zum ”Verrauschen” der Koordinaten<br />

werden auch in dieser Methode, unmittelbar vor der Transformation in Textur-Koordinaten<br />

durchgeführt.<br />

Bei aktiviertem Bump Mapping benötigt die Textur außerdem die Flächennormale im Schnittpunkt.<br />

Diese wird durch die Methode getBumpNormal() der Klasse BumpTexture entsprechend<br />

der Einstellungen perturbiert <strong>und</strong> direkt zurückgegeben.<br />

Für Environment Mapping benötigen die Berechnungen zusätzlich den Strahl <strong>und</strong> die Normale<br />

in Weltkoordinaten. Dabei kann der Normalenvektor bereits durch Bump Mapping<br />

verändert worden sein. Bei Bedarf werden diese Daten vom Objekt mittels set<strong>Ray</strong>AndNormal()<br />

an die Textur übergeben. Ob dieser Datentransfer nötig ist, wird dabei durch das Prädikat<br />

needs<strong>Ray</strong>AndNormal() entschieden.<br />

Nachdem so alle erforderlichen Daten der Textur mitgeteilt wurden, veranlasst der Aufruf der<br />

Methode detectTextureData() die Berechnung der Texturinformationen. Genau wie is2D() <strong>und</strong><br />

needs<strong>Ray</strong>AndNormal, ist diese Schnittstelle rein virtuell <strong>und</strong> erfordert die Implementierung in<br />

den Klassen des jeweiligen Texturtyps.<br />

Wie aus Abbildung 4.2 ersichtlich ist, geschieht dieses bereits in der <strong>von</strong> Texture abgeleiteten<br />

Klasse BumpTexture. Dadurch entsteht die Standardtextur ohne Änderungen in Farbe <strong>und</strong><br />

Material, aber mit allen Voraussetzungen für Bump Mapping. Diese ist dem <strong>Texturierung</strong>s-<br />

System auch als PlainTexture bekannt.<br />

4 Dies ist bei 2D-Texturen, wie der Karo- oder Bildtextur, auf Objekten mit nicht durchgehender Oberfläche<br />

(z.B. Box) der Fall.


KAPITEL 4. IMPLEMENTIERUNG 59<br />

4.4 Änderungen am Gesamtsystem<br />

Durch die Überarbeitung <strong>und</strong> die Erweiterungen in den Klassen der Objekte <strong>und</strong> Texturen,<br />

waren auch viele Änderungen am Gesamtsystem erforderlich. Die wichtigsten der dadurch<br />

entstandenen Modifikationen <strong>und</strong> Ergänzungen werden in den folgenden Abschnitten kurz<br />

beschrieben.<br />

4.4.1 Die Szenendefinition<br />

Zur Definition <strong>von</strong> Szenen, existierten bereits Routinen, die das Einlesen aus Dateien <strong>und</strong><br />

die Umsetzung in interne Datenstrukturen ermöglichten. Die dabei zu Gr<strong>und</strong>e liegende Sprache<br />

musste natürlich an die neuen Gegebenheiten angepasst werden. Durch die Notwendigkeit<br />

sehr viele neue Schlüsselwörter <strong>und</strong> Definitionsabschnitte in die Sprache aufnehmen zu<br />

müssen, erschien es sinnvoll, die Symbolerkennung <strong>und</strong> die Abläufe der Syntaxüberprüfung<br />

<strong>von</strong> einander zu trennen <strong>und</strong> in eigenständigen Klassen zu kapseln. Dadurch entstanden aus<br />

den rein prozeduralen Funktionen, die beiden Klassen Scanner <strong>und</strong> Parser.<br />

4.4.1.1 Die Syntax<br />

Einige Definitionen führten durch ihre Aufnahme in die Sprache zu doppeldeutigen Konstruktionen,<br />

die in den Parsingroutinen zum Teil nicht erkannt <strong>und</strong> aufgelöst werden konnten. Um<br />

diese Mehrdeutigkeiten zu beseitigen, wurde der Sprachaufbau zu einer an VRML 5 angelehnten<br />

Syntax hin verändert. Da<strong>von</strong> abweichend wurden zusätzlich Blockkommentare im Stil <strong>von</strong><br />

C/C++ mit in die Sprache integriert.<br />

Eine der auffälligsten Veränderungen an der Sprache ist wohl die Klammerung <strong>von</strong> Definitionsblöcken.<br />

Die dadurch vorhandenen Endekennungen <strong>von</strong> zusammengehörigen Bereichen<br />

erleichtern das Parsing erheblich <strong>und</strong> Fehler in den Definitionen können auf sehr kleine Bereiche<br />

eingegrenzt werden. Deshalb werden auch sehr viel exaktere Fehlermeldungen möglich.<br />

Eine genaue Spezifikation der Syntax findet sich in Backus Naur Form im Anhang C.<br />

4.4.1.2 Definitionen<br />

Zur Festlegung <strong>von</strong> Farben, Materialien <strong>und</strong> Flächenkontrollpunkten müssen häufig sehr viele<br />

Werte in der Szenenbeschreibung eingetragen werden. Sehr oft werden auch die selben Definitionen<br />

mehrfach in einer Szene verwendet. Die Beschreibung <strong>von</strong> umfangreicheren Szenen<br />

in denen Definitionen <strong>von</strong> <strong>Freiformflächen</strong> vorkommen, wird dadurch schwer <strong>und</strong> absolut<br />

unübersichtlich.<br />

Um den Entwurf <strong>von</strong> Modellen zu erleichtern, wurde der Parser mit der Möglichkeit ausgestattet,<br />

Spezifikationsblöcke außerhalb der Szene zu definieren <strong>und</strong> auf diese später in den<br />

Objektbeschreibungen zu verweisen.<br />

5 Virtual Reality Modeling Language


KAPITEL 4. IMPLEMENTIERUNG 60<br />

Damit wird es möglich, Farben, Materialien, komplexe Texturspezifikationen sowie Kontrollpunkte<br />

<strong>von</strong> <strong>Freiformflächen</strong> <strong>und</strong> deren Flächenzugehörigkeit unter einem frei wählbaren Namen<br />

zu deklarieren <strong>und</strong> diese mehrfach zu verwenden.<br />

Für diese Funktion wurden die beiden neuen Schlüsselwörter def <strong>und</strong> use eingeführt.<br />

4.4.1.3 Include-Dateien<br />

Vielfach werden in verschiedenen Szenen die gleichen Definitionen <strong>von</strong> Farben, Materialien<br />

oder auch <strong>Freiformflächen</strong> verwendet. Include-Dateien erweitern die Möglichkeiten der Vorabdeklaration<br />

<strong>von</strong> mehrfach verwendeten Spezifikationsblöcken <strong>und</strong> ersparen bei der Modellierung<br />

oft die Eingabe <strong>von</strong> Zahlenkolonnen. So können selbst ganze Teilbereiche <strong>von</strong> Szenen<br />

zusammengefasst, ausgelagert <strong>und</strong> gesondert konstruiert werden.<br />

Vor allem Materialkoeffizienten sollten für einen realistischen Eindruck sehr sorgfältig ausgewählt<br />

werden. Für Standardmaterialien existieren dazu Zusammenstellungen <strong>von</strong> experimentell<br />

gemessenen Werten, womit recht gute Ergebnisse erzielt werden können. Durch die<br />

Verwendung <strong>von</strong> eigenen Definitionsdateien können diese Werte zentral abgelegt werden <strong>und</strong><br />

sind trotzdem in allen Szenen verfügbar.<br />

4.4.2 Die Bildformate<br />

Die Ausgabe der berechneten Bildinformationen kann als Bilddatei im TGA-Format erfolgen.<br />

Eine Klasse mit den Möglichkeiten zum Laden <strong>und</strong> Speichern dieses Formates konnte aus der<br />

Arbeit <strong>von</strong> O. Groth [3] übernommen werden. Diese dient gleichzeitig zum Laden der Bilddateien,<br />

die für die <strong>Texturierung</strong> benötigt werden.<br />

Durch die direkte Ausgabe des Bildes in eine Datei bleibt das berechnete Bild allerdings<br />

erstmal unsichtbar <strong>und</strong> kann erst nach Beendigung der Berechnungen mit Hilfe <strong>von</strong> Bildbetrachtern<br />

begutachtet werden. Aus diesem Gr<strong>und</strong> sollte das System um die Möglichkeit zur<br />

Darstellung, während den Berechnungen, erweitert werden.<br />

Für die Darstellung des Bildes wurde die auf das XWindow-System basierende Klasse Gfx-<br />

Window implementiert. Diese wurde mit einer Event-verarbeitenden Hauptschleife <strong>und</strong> einer<br />

Möglichkeit zur Registrierung <strong>von</strong> Callback-Funktionen ausgestattet, um auf Ereignisse, wie<br />

z.B. Tastatureingaben, reagieren zu können.<br />

Zur nahtlosen Integration dieser Erweiterung, wurden die Klassen der Ausgabeformate hierarchisch<br />

zusammengefasst. Abbildung A.4 im Anhang A zeigt die dafür entworfene Hierarchie.<br />

Wie im Diagramm ersichtlich, leiten sich beide Ausgabeformate <strong>von</strong> der abstrakten Klasse<br />

Image ab. Deren Aufgabe ist die Verwaltung des Puffers für die berechneten Bilddaten.<br />

Aufgr<strong>und</strong> dieser Anordnung der Klassen wird es möglich, ein bestehendes Ausgabeformat<br />

allein durch einen cast in ein anderes zu konvertieren. Dadurch kann eine Bilddatei, die durch<br />

eine TGAImage-Instanz geladen wurde, ohne Aufwand in einem Fenster dargestellt werden.<br />

Umgekehrt dazu ist es möglich, ein im Fenster berechnetes Bild, z.B. auf Tastendruck, in ein<br />

TGAImage zu konvertieren <strong>und</strong> als Bilddatei abzuspeichern.


Anhang A<br />

Diagramme<br />

Im Folgenden finden sich die, in den Kapiteln referenzierten Diagramme. Diese sind meist<br />

zu groß für die Integration in den Textfluss <strong>und</strong> werden aus Gründen der Übersicht in diesem<br />

Teil des Anhangs dargestellt.<br />

61


ANHANG A. DIAGRAMME 62<br />

<strong>Ray</strong>trace<br />

getArguments()<br />

main()<br />

newModel<br />

:Model<br />

newParser<br />

newScanner<br />

:Parser<br />

#DefinitionFile<br />

:Scanner<br />

getInclude<br />

:Parser<br />

setDefinition()<br />

Scene<br />

getChar()<br />

getSymbol()<br />

getScene()<br />

getLight()<br />

:Light<br />

getView()<br />

:View<br />

getObjects()<br />

newObject<br />

:Object<br />

getObjectData() / getTextureData()<br />

buildTexture()<br />

:Texture<br />

getOctree()<br />

Octree<br />

buildModel()<br />

buildOctree() getNodeList()<br />

split()<br />

newImage<br />

Shade<br />

:Image<br />

sample()<br />

new<strong>Ray</strong>Direction()<br />

renderModel()<br />

trace()<br />

getNormal()<br />

getColor()<br />

getAmbience()<br />

getMaterial()<br />

setHitData()<br />

findPoint()<br />

setHitData()<br />

getBumpNormal()<br />

detectTextureData()<br />

Abbildung A.1: Ablauf des <strong>Ray</strong> <strong>Tracing</strong>-Prozesses<br />

findNode()<br />

intersect<br />

getColor()<br />

getMaterial()<br />

octreeIntersect()<br />

intersect()<br />

intersect<br />

shadowTest()<br />

¡ ¢¡¢<br />

¢¡¢ ¡<br />

nextPoint()<br />

trace()<br />

test()<br />

findNode()<br />

sample()<br />

Model<br />

setPixelColor()<br />

defaultIntersect()<br />

intersect()


ANHANG A. DIAGRAMME 63<br />

Object<br />

+ setHitData(hitPoint) : void<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ getColor() : Color<br />

+ getNormal() : Vector<br />

+ getAmbience() : Color<br />

+ getMaterial() : Material<br />

Bezier<br />

SimpleList<br />

# toObject : Matrix<br />

# toWorld : Matrix<br />

# ambience : Color<br />

# texture : Texture *<br />

$ factorial(x) : unsigned int<br />

$ bernstein(i,j,t) : Real<br />

+ append() : type *<br />

+ delete(index) : bool<br />

+ begin() : iterator<br />

+ operator[](index) : type &<br />

Sphere<br />

Box<br />

FreeformSurface<br />

BezierCurve<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ setHitData(hitPoint) : void<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ setHitData(hitPoint) : void<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ buildSearchTree() : void<br />

+ newControlPoint() : Point *<br />

+ deleteControlPoint(index) : bool<br />

+ curvePoint(t) : Point<br />

+ devide(t) : BezierCurve[2]<br />

− searchTreeRoot : TreeNode *<br />

Cone Plane<br />

Cylinder<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

+ intersect() : bool<br />

− detectNormal() : void<br />

+ matchMappingMode() : MappingMode[2]<br />

+ matchSurfaceDimension() : Real[2]<br />

BezierSurface<br />

+ surfacePoint(u,v) : Point<br />

+ surfaceGradients(u,v) : Vector[2]<br />

+ isFlat() : Axis, bool<br />

+ devide(axis,t) : FreeformSurface[2]<br />

Abbildung A.2: Die Klassenhierarchie der Objekte


ANHANG A. DIAGRAMME 64<br />

Texture<br />

Noise (Singleton)<br />

$ getNoise() : Noise *<br />

+ getFractalNoise(Point,int,Real) : Real<br />

+ getFractalGradient(Point,int,Real) : Vector<br />

+ setHitData(hitPoint)<br />

+ detectTextureData()<br />

+ getMaterial() : Material<br />

+ getColor() : Color<br />

− getSmoothNoise(Point) : Real<br />

− getSmoothGradient(Point) : Vector<br />

− noiseTable : Real[ ]<br />

− indexTable : usigned int[ ]<br />

# baseMaterial : Material<br />

# baseColor : Color<br />

− turbulence : Turbulence<br />

− noise : Noise *<br />

BumpTexture<br />

TGAImage<br />

PointMapper<br />

+ setPixelColor(color,x,y) : void<br />

+ getPixelColor(x,y) : Color<br />

+ loadTGA() : void<br />

+ saveTGA24() : void<br />

+ getBumpNormal(Vector) : Vector<br />

+ detectTextureData()<br />

+ mapPoint(Point) : Point<br />

+ getMapDirections(Point) : Vector[2]<br />

# getTextureSlope()<br />

− getMapSlope()<br />

− getNoiseSlope()<br />

− bumpSize : Real<br />

− turbulence : Turbulence<br />

− mapper : PointMapper<br />

− bumpMap : TGAImage *<br />

− mode : MappingMode[3]<br />

− layout : MappingLayout<br />

− mapDim : Real[3]<br />

ProceduralTexture<br />

BitmapTexture<br />

+ getColor() : Color<br />

+ getMaterial() : Material<br />

Abbildung A.3: Die Klassenhierarchie des <strong>Texturierung</strong>ssystems<br />

+ detectTextureData()<br />

− mapper : PointMapper<br />

# procColor : Color *[2]<br />

# procMaterial : Material *[2]<br />

− extColor : Color<br />

− extMaterial : Material<br />

ReflectionTexture<br />

+ detectTextureData()<br />

WoodTexture<br />

MarbleTexture<br />

BrickTexture<br />

CheckerTexture<br />

+ detectTextureData()<br />

+ getColor() : Color<br />

+ getMaterial() : Material<br />

+ detectTextureData()<br />

+ getColor() : Color<br />

+ getMaterial() : Material<br />

+ detectTextureData()<br />

+ detectTextureData()<br />

− brickSize : Real<br />

− checkerSize : Real<br />

− mapper : PointMapper<br />

− turbulence : Turbulence<br />

− sharpness : Real<br />

− woodColor : Color<br />

− woodMaterial : Material<br />

− turbulence : Turbulence<br />

− marbleColor : Color<br />

− marbleMaterial : Material


ANHANG A. DIAGRAMME 65<br />

+ setPixelColor(color,x,y) : void<br />

+ getPixelColor(x,y) : Color<br />

+ loadTGA() : void<br />

+ saveTGA24() : void<br />

− imgFile : FILE *<br />

Image<br />

+ setPixelColor(color,x,y) : void<br />

+ getName() : const char *<br />

+ getDim(coord) : unsigned int<br />

# getPixelBuffer(x,y) : bool<br />

# name : char[]<br />

# dimension : unsigned int[]<br />

# buffer : Pixel *<br />

TGAImage GfxWindow<br />

+ setPixelColor(color,x,y) : void<br />

+ displayWindow() : void<br />

+ redraw() : void<br />

+ registerCallback(event,void(*func)()): void<br />

# display : Display *<br />

# window : Window<br />

# callback : void(*func)(XEvent *)[]<br />

Abbildung A.4: Die Klassenhierarchie der Bildformate


Anhang B<br />

Parameterliste für den Programmstart<br />

Die Funktionsweise des implementierten <strong>Ray</strong> <strong>Tracing</strong>-Systems kann durch die Parameterliste<br />

beim Aufruf <strong>von</strong> der Konsole aus gesteuert werden. Die dabei möglichen Parameter <strong>und</strong><br />

Optionen werden im Folgenden beschrieben, können jedoch auch in der Hilfeseite des Programms<br />

eingesehen werden.<br />

Gr<strong>und</strong>legend hat ein Aufruf folgendes Format:<br />

<strong>Ray</strong>trace [-h|-v] scenefile [-o name] [-f format] [-x width] [-y height]<br />

Optionale Angaben:<br />

-h Zeigt die Hilfeseite an <strong>und</strong> beendet das Programm<br />

-v Veranlasst die Anzeige <strong>von</strong> Schnittinformationen pro berechneter Zeile<br />

Weitere Parameter:<br />

scenefile Dateiname der Szenenbeschreibung<br />

-o name Name des erzeugten Bildes. Standardmäßig wird der Dateiname der<br />

Szenendefinition verwendet <strong>und</strong> mit der Formatkennung erweitert.<br />

-f format Erzeugtes Bildformat.<br />

Implementierte Formate: TGA (default), X11<br />

-x width Bildbreite in Pixel. Der Defaultwert ist 600.<br />

-y height Bildhöhe in Pixel. Der Defaultwert ist 600.<br />

66


Anhang C<br />

Szenen-Definition<br />

In diesem Teil des Anhangs finden sich detailierte Beschreibungen zur Definition <strong>von</strong> Modellen.<br />

Die Darstellung der Sprachsyntax erfolgt dabei in Extended Backus Nour Form, kurz<br />

EBNF. Ein Beispiel zur Anwendung findet sich im nächsten Abschnitt.<br />

Lexikalische Elemente<br />

char ::= A..Z | a..z | _<br />

digit ::= 0..9<br />

block_comment ::= /* {} */<br />

line_comment ::= # {}<br />

string_ident ::= "{}"<br />

keyword ::= ambience | ambient | angle | aspectratio | bezier |<br />

bitmap | blue | box | brick | bump | camera | checker |<br />

color | cone | csg | cylinder | def | diffuse |<br />

direction | exponent | false | file | frequencies |<br />

green | include | index | lights | location | lookat |<br />

marble | material | move | objects | octree | patch |<br />

plain | plane | point | red | reflect | reflection |<br />

right | rotate | scale | scene | shear | size |<br />

specular | specularexp | sphere | spot | stretch |<br />

supersample | texture | tile | transform |<br />

transparency | true | turbulence | up | use | vertex |<br />

view | wood | x | y | z | zoom<br />

number ::= {}<br />

real ::= [.][(e|E) [+|-] ]<br />

Modell-Definition<br />

model ::= { | | }<br />

include ::= include <br />

define ::= def {definable block}<br />

scene ::= scene { { | | | } }<br />

view ::= view { { | | | | } }<br />

object_list ::= objects { { | | | | | } }<br />

octree ::= octree { {(size ) | } }<br />

light_list ::= lights { { | } }<br />

sphere ::= sphere { }<br />

cone ::= cone { size }<br />

67


ANHANG C. SZENEN-DEFINITION 68<br />

cylinder ::= cylinder { }<br />

box ::= box { }<br />

plane ::= plane { }<br />

bezier ::= bezier { }<br />

point_light ::= point { { | } }<br />

spot_light ::= spot { { | | | } }<br />

object_data ::= {(ambience ) | | }<br />

transformation ::= transform { (move ) |<br />

(rotate ) |<br />

(scale ) |<br />

(shear ) }<br />

definable ::= color | material | patch | texture | vertex<br />

color ::= (use color ) | (color { { } })<br />

material ::= (use material ) |<br />

(material { {(ambient ) |<br />

(diffuse ) |<br />

(specular ) |<br />

(specularexp ) |<br />

(reflect ) |<br />

(transparency ) |<br />

(index )} })<br />

patch ::= (use patch ) |<br />

(patch { size (file | ) })<br />

texture ::= (use texture ) |<br />

(texture { { | | | |<br />

| } })<br />

vertex ::= (use vertex ) |<br />

(vertex { { | (file )} })<br />

turbulence ::= turbulcene { {(size ) |<br />

(scale ) |<br />

(frequencies ) |<br />

(exponent )} }<br />

texture_mode ::= { | | | | | }<br />

bump ::= bump { size }<br />

bitmap ::= bitmap { }<br />

reflection ::= reflection { }<br />

checker ::= checker { size x y }<br />

brick ::= brick { size }<br />

marble ::= marble { }<br />

wood ::= wood { size }<br />

map ::= file {scale } (stretch | tile)<br />

location ::= location <br />

direction ::= direction <br />

up ::= up <br />

right ::= right <br />

supersample ::= supersample <br />

vector ::= <br />

angle ::= angle <br />

boolean ::= true | false<br />

color_channel ::= red | green | blue<br />

coordinate ::= x | y | z


ANHANG C. SZENEN-DEFINITION 69<br />

Die Modellierung einer Beispiel-Szene<br />

Um die Möglichkeiten der Modellierungssprache sowie einen möglichst großen Funktionsumfang<br />

an einem Beispiel darzustellen, wird im Folgenden der Aufbau eines etwas größeren<br />

Modells in Auszügen beschrieben. Die dabei verwendeten Einstellungen sind in der Definition<br />

in Form <strong>von</strong> Kommentar erklärt. Im Anschluss daran findet sich das daraus berechnete<br />

Bild.<br />

/* Beispiel-Szene */<br />

include "defines/colors.def" # einbinden <strong>von</strong> Farb-Definitionen<br />

include "defines/materials.def" # einbinden <strong>von</strong> Material-Definitionen<br />

# Einlesen der Punktdaten für <strong>Freiformflächen</strong> aus Dateien<br />

def vertex "teacup_vertex" { file "defines/utah_teacup.vertex" }<br />

def vertex "teaspoon_vertex" { file "defines/utah_teaspoon.vertex" }<br />

def vertex "teapot_vertex" { file "defines/utah_teapot.vertex" }<br />

# Einlesen der Punktzuordnung für die Flächen aus Dateien<br />

def patch "teacup_patches" { size 4 4 file "defines/utah_teacup.patch" }<br />

def patch "teaspoon_patches" { size 4 4 file "defines/utah_teaspoon.patch" }<br />

# Manuelle Definition <strong>von</strong> Punktzuordnungen für Flächen der Größe 4x4<br />

def patch "teapot_body_patches" {<br />

size 4 4 <br />

size 4 4 <br />

...<br />

size 4 4 <br />

# Manuelle Definition mehrfach verwendeter Farben, Materialien <strong>und</strong> Texturen<br />

def color "marble_color1" { red 0.85 green 0.86 blue 0.78 }<br />

def color "marble_color2" { red 0.397 green 0.275 blue 0.216 }<br />

def material "marble_material" {<br />

ambient 0.85 0.85 0.85<br />

diffuse 0.47 0.47 0.47<br />

specular 0.77 0.77 0.77<br />

specularExp 95<br />

}<br />

def texture "chinaware" {<br />

use color "white"<br />

material {<br />

ambient 0.94 0.94 0.94 # Koeffizienten für die einzelnen<br />

diffuse 0.35 0.35 0.35 # Farbkanäle Rot, Grün, Blau<br />

specular 0.43 0.43 0.43<br />

specularExp 93<br />

reflect 0.1<br />

}<br />

}<br />

def texture "sugar" {<br />

use color "white"<br />

use material "polished silver"<br />

bump {<br />

size 0.5<br />

turbulence {<br />

size 0.4<br />

scale 0.05<br />

frequencies 2<br />

}<br />

}<br />

}<br />

...<br />

# Beginn eines Szenenabschnittes<br />

scene {<br />

# Definition der Ansicht<br />

view {<br />

location # Beobachterstandpunkt


ANHANG C. SZENEN-DEFINITION 70<br />

}<br />

direction # Blickrichtung<br />

up # Up-Richtung<br />

right # Right-Richtung für Verhältnis 4:3<br />

supersample true # aktiviere Supersampling<br />

objects {<br />

# teapot #<br />

bezier {<br />

use vertex "teapot_vertex" # Benutze vordefinierte Punktmenge<br />

use patch "teapot_body_patches" # <strong>und</strong> Flächenzuordnungen<br />

ambience 0.2 0.2 0.2 # Gr<strong>und</strong>leuchten des Objekts<br />

texture {<br />

use color "marble_color1" # Benutze vordefinierte Farben ...<br />

use color "marble_color2"<br />

use material "marble_material" # ... <strong>und</strong> Materialien<br />

use material "marble_material"<br />

marble { # Art der Textur<br />

turbulence { # Die <strong>von</strong> Marmor benötigte Turbulenz<br />

size 4.4 # Stärke<br />

scale 0.35 # Auflösung<br />

frequencies 3 # Anzahl verwendeter Frequenzen<br />

exponent 2.9 # Schärfe<br />

}<br />

}<br />

transform { # Transformation der Textur relativ zum Objekt<br />

scale # Skalierung in X Y Z-Richtung<br />

rotate # Rotation um X Y Z-Achse (in Grad) ...<br />

rotate # ... in dieser Reihenfolge<br />

}<br />

}<br />

transform { # Transformation des Objektes<br />

rotate # Rotation<br />

move # Verschiebung<br />

move <br />

}<br />

}<br />

...<br />

# wall #<br />

plane {<br />

transform {<br />

move <br />

}<br />

ambience 0.2 0.2 0.2<br />

texture {<br />

...<br />

}<br />

}<br />

# picture #<br />

box {<br />

transform {<br />

scale <br />

move <br />

}<br />

ambience 0.8 0.8 0.8<br />

texture {<br />

use material "chrome"<br />

bitmap { file "maps/world.tga" stretch } # Bitmap-Textur - gestreckt<br />

}<br />

}<br />

# sugar pot #<br />

cylinder {<br />

transform {<br />

scale <br />

move <br />

}<br />

ambience 0.5 0.5 0.5<br />

use texture "bumpy chrome"


ANHANG C. SZENEN-DEFINITION 71<br />

}<br />

}<br />

}<br />

sphere {<br />

transform {<br />

scale <br />

scale <br />

move <br />

}<br />

ambience 0.5 0.5 0.5<br />

use texture "bumpy chrome"<br />

}<br />

# lid #<br />

cone { # Kegeldefinition<br />

size 0 1 1 # kleiner Radius, großer Radius <strong>und</strong> Höhe<br />

transform {<br />

scale <br />

rotate <br />

move <br />

}<br />

ambience 0.5 0.5 0.5<br />

use texture "bumpy chrome"<br />

}<br />

# Definition der Lichtquellen<br />

lights {<br />

# candle light #<br />

point {<br />

location # Position<br />

color { red 0.86 green 0.63 blue 0.06 } # Farbe<br />

}<br />

...<br />

}<br />

point {<br />

location <br />

color { red 0.7 green 0.6 blue 0.6 }<br />

}<br />

# Octree-Definition<br />

octree {<br />

}<br />

size 1000 # Kantenlänge<br />

location # Position der minEcke


ANHANG C. SZENEN-DEFINITION 72<br />

Abbildung C.1: Fertig erstelltes Bild der Beispiel-Szene


Anhang D<br />

C++ Quelltexte<br />

Die C++ Quellen in diesem Teil des Anhangs dokumentieren den aktuellen Stand des <strong>Ray</strong><br />

<strong>Tracing</strong>-Systems. Eine Zusammenfassung der Quelldateien ist auf der nächsten Seite in Form<br />

eines Inhalsverzeichnisses aufgelistet.<br />

73


Table of Contents<br />

1 raytrace.h.......... sheets 1 to 1 ( 1) pages 1− 1 43 lines<br />

2 raytrace.cpp........ sheets 2 to 4 ( 3) pages 2− 4 251 lines<br />

3 Model.h............. sheets 5 to 6 ( 2) pages 5− 6 146 lines<br />

5 4 Model.cpp........... sheets 7 to 9 ( 3) pages 7− 9 249 lines<br />

5 shade.h............. sheets 10 to 10 ( 1) pages 10− 10 45 lines<br />

6 shade.cpp........... sheets 11 to 15 ( 5) pages 11− 15 442 lines<br />

7 Noise.h............. sheets 16 to 16 ( 1) pages 16− 16 63 lines<br />

8 Noise.cpp........... sheets 17 to 19 ( 3) pages 17− 19 280 lines<br />

10 9 PointMapper.h....... sheets 20 to 20 ( 1) pages 20− 20 62 lines<br />

10 PointMapper.cpp..... sheets 21 to 22 ( 2) pages 21− 22 149 lines<br />

11 Texture.h........... sheets 23 to 23 ( 1) pages 23− 23 94 lines<br />

12 Texture.cpp......... sheets 24 to 24 ( 1) pages 24− 24 101 lines<br />

13 BumpTexture.h....... sheets 25 to 25 ( 1) pages 25− 25 69 lines<br />

15 14 BumpTexture.cpp..... sheets 26 to 27 ( 2) pages 26− 27 199 lines<br />

15 BitmapTexture.h..... sheets 28 to 28 ( 1) pages 28− 28 35 lines<br />

16 BitmapTexture.cpp... sheets 29 to 29 ( 1) pages 29− 29 54 lines<br />

17 ReflectionTexture.h. sheets 30 to 30 ( 1) pages 30− 30 27 lines<br />

18 ReflectionTexture.cpp sheets 31 to 31 ( 1) pages 31− 31 35 lines<br />

20 19 ProceduralTexture.h. sheets 32 to 32 ( 1) pages 32− 32 43 lines<br />

20 ProceduralTexture.cpp sheets 33 to 33 ( 1) pages 33− 33 25 lines<br />

21 BrickTexture.h...... sheets 34 to 34 ( 1) pages 34− 34 33 lines<br />

22 BrickTexture.cpp.... sheets 35 to 35 ( 1) pages 35− 35 41 lines<br />

23 CheckerTexture.h.... sheets 36 to 36 ( 1) pages 36− 36 44 lines<br />

25 24 CheckerTexture.cpp.. sheets 37 to 37 ( 1) pages 37− 37 74 lines<br />

25 MarbleTexture.h..... sheets 38 to 38 ( 1) pages 38− 38 46 lines<br />

26 MarbleTexture.cpp... sheets 39 to 39 ( 1) pages 39− 39 38 lines<br />

27 WoodTexture.h....... sheets 40 to 40 ( 1) pages 40− 40 40 lines<br />

28 WoodTexture.cpp..... sheets 41 to 41 ( 1) pages 41− 41 58 lines<br />

30 29 Image.h............. sheets 42 to 42 ( 1) pages 42− 42 39 lines<br />

30 Image.cpp........... sheets 43 to 43 ( 1) pages 43− 43 47 lines<br />

31 TGAImage.h.......... sheets 44 to 45 ( 2) pages 44− 45 124 lines<br />

32 TGAImage.cpp........ sheets 46 to 52 ( 7) pages 46− 52 656 lines<br />

33 GfxScreen.h......... sheets 53 to 53 ( 1) pages 53− 53 41 lines<br />

35 34 GfxScreen.cpp....... sheets 54 to 54 ( 1) pages 54− 54 58 lines<br />

35 GfxWindow.h......... sheets 55 to 55 ( 1) pages 55− 55 77 lines<br />

36 GfxWindow.cpp....... sheets 56 to 57 ( 2) pages 56− 57 164 lines<br />

37 Object.h............ sheets 58 to 58 ( 1) pages 58− 58 53 lines<br />

38 Object.cpp.......... sheets 59 to 60 ( 2) pages 59− 60 117 lines<br />

40 39 Box.h............... sheets 61 to 61 ( 1) pages 61− 61 46 lines<br />

40 Box.cpp............. sheets 62 to 64 ( 3) pages 62− 64 274 lines<br />

41 Cone.h.............. sheets 65 to 65 ( 1) pages 65− 65 46 lines<br />

42 Cone.cpp............ sheets 66 to 66 ( 1) pages 66− 66 93 lines<br />

43 Cylinder.h.......... sheets 67 to 67 ( 1) pages 67− 67 49 lines<br />

45 44 Cylinder.cpp........ sheets 68 to 68 ( 1) pages 68− 68 65 lines<br />

45 Plane.h............. sheets 69 to 69 ( 1) pages 69− 69 38 lines<br />

46 Plane.cpp........... sheets 70 to 70 ( 1) pages 70− 70 62 lines<br />

47 Sphere.h............ sheets 71 to 71 ( 1) pages 71− 71 36 lines<br />

48 Sphere.cpp.......... sheets 72 to 72 ( 1) pages 72− 72 53 lines<br />

50 49 FreeformSurface.h... sheets 73 to 74 ( 2) pages 73− 74 159 lines<br />

50 FreeformSurface.cpp. sheets 75 to 81 ( 7) pages 75− 81 695 lines<br />

51 Bezier.h............ sheets 82 to 82 ( 1) pages 82− 82 35 lines<br />

52 BezierCurve.h....... sheets 83 to 83 ( 1) pages 83− 83 54 lines<br />

53 BezierCurve.cpp..... sheets 84 to 86 ( 3) pages 84− 86 272 lines<br />

55 54 BezierSurface.h..... sheets 87 to 87 ( 1) pages 87− 87 27 lines<br />

55 BezierSurface.cpp... sheets 88 to 90 ( 3) pages 88− 90 275 lines<br />

56 Octree.h............ sheets 91 to 91 ( 1) pages 91− 91 70 lines<br />

57 Octree.cpp.......... sheets 92 to 96 ( 5) pages 92− 96 430 lines<br />

58 Parser.h............ sheets 97 to 98 ( 2) pages 97− 98 184 lines<br />

60 59 Parser.cpp.......... sheets 99 to 117 (19) pages 99−117 1844 lines<br />

60 Scanner.h........... sheets 118 to 119 ( 2) pages 118−119 120 lines<br />

61 Scanner.cpp......... sheets 120 to 123 ( 4) pages 120−123 366 lines<br />

62 SimpleList.h........ sheets 124 to 125 ( 2) pages 124−125 199 lines<br />

63 PatchBuffer.h....... sheets 126 to 126 ( 1) pages 126−126 95 lines<br />

65 64 Types.h............. sheets 127 to 127 ( 1) pages 127−127 101 lines<br />

65 utils.h............. sheets 128 to 130 ( 3) pages 128−130 286 lines<br />

66 utils.cpp........... sheets 131 to 136 ( 6) pages 131−136 508 lines


5<br />

10<br />

#ifndef __RAYTRACE_H<br />

#define __RAYTRACE_H<br />

#include <br />

// predeclarations<br />

class Model;<br />

class Parser;<br />

class Image;<br />

typedef enum {<br />

TGAFORMAT,<br />

XWINDOW<br />

//***************<br />

15 } ImageFormat;<br />

//***************<br />

typedef struct __Arguments {<br />

bool verbose;<br />

20 char *sceneFile;<br />

char *imgName;<br />

ImageFormat imgFormat;<br />

unsigned int imgWidth;<br />

unsigned int imgHeight;<br />

25 //*************<br />

} Arguments;<br />

//*************<br />

// globals<br />

30 static Model *model;<br />

static Parser *parser;<br />

static Image *image;<br />

// functions<br />

35 void windowExpose( XEvent *event );<br />

void keyPressEvent( XEvent *event );<br />

void cleanup();<br />

void getArguments( char **argv, Arguments *args );<br />

void printHelp( bool showDetails );<br />

40<br />

#endif /* __RAYTRACE_H */


#include <br />

#include <br />

#include <br />

5 #include "raytrace.h"<br />

#include "TGAImage.h"<br />

#include "GfxWindow.h"<br />

#include "Parser.h"<br />

#include "Model.h"<br />

10 #include "Types.h"<br />

/* <strong>Ray</strong>trace main<br />

*/<br />

//**********************************<br />

15 int main( int argc, char **argv )<br />

//**********************************<br />

{<br />

Arguments args;<br />

20 // initialize pointers<br />

model = NULL;<br />

parser = NULL;<br />

image = NULL;<br />

25 // register cleanup function<br />

atexit( cleanup );<br />

getArguments( argv, &args );<br />

30 // get model<br />

if( (model = new Model()) == NULL )<br />

{<br />

fprintf( stderr, "Can’t get model\n" );<br />

exit(1);<br />

35 }<br />

// parse scene description file<br />

if( (parser = new Parser( args.sceneFile, model, ((args.verbose)? stdout : NULL) )) == NULL )<br />

{<br />

40 fprintf( stderr, "Parser not availible!\n" );<br />

exit(1);<br />

}<br />

// release parser<br />

45 delete parser; parser = NULL;<br />

// get image<br />

switch( args.imgFormat )<br />

{<br />

50 case TGAFORMAT: image = new TGAImage( args.imgName, args.imgWidth, args.imgHeight );<br />

break;<br />

case XWINDOW: if((image = new GfxWindow( args.imgWidth, args.imgHeight, args.imgName, ""))!=NULL)<br />

((GfxWindow *)image)−>displayWindow();<br />

55 break;<br />

}<br />

default: break;<br />

60 if( image == NULL )<br />

{<br />

fprintf( stderr, "Can’t get image!\n" );<br />

exit(1);<br />

}<br />

65<br />

// render model<br />

model−>renderModel( image, &args );<br />

if( args.imgFormat == XWINDOW )<br />

70 {<br />

// start window event recognition<br />

((GfxWindow *)image)−>registerCallback( Expose, windowExpose );<br />

((GfxWindow *)image)−>registerCallback( KeyPress, keyPressEvent );<br />

((GfxWindow *)image)−>enableEvent( KeyPressMask | ExposureMask );<br />

75 ((GfxWindow *)image)−>mainLoop();<br />

}<br />

// release image and model<br />

delete image; image = NULL;<br />

80 delete model; model = NULL;<br />

}<br />

return 0;<br />

85 /**<br />

* Event handler<br />

*/<br />

// expose event<br />

//***********************************<br />

90 void windowExpose( XEvent *event )<br />

//***********************************<br />

{<br />

GfxWindow *window = (GfxWindow *)image;<br />

95 if( window−>getWidth() != image−>getDim( X ) || window−>getHeight() != image−>getDim( Y ) )<br />

window−>resizeWindow( image−>getDim( X ), image−>getDim( Y ) );<br />

100<br />

}<br />

((GfxWindow *)image)−>redraw();


key press event<br />

//************************************<br />

void keyPressEvent( XEvent *event )<br />

//************************************<br />

105 { char charBuffer[10];<br />

XLookupString( (XKeyEvent *)event, charBuffer, 10, NULL, NULL );<br />

110 switch (charBuffer[0])<br />

{<br />

case ’q’: ((GfxWindow *)image)−>quitMainLoop();<br />

break;<br />

115 case ’s’: fprintf( stdout, "saving image \"%s.tga\" ... ", image−>getName() );<br />

((TGAImage *)image)−>saveTGA24();<br />

fprintf( stdout, "OK\n" );<br />

break;<br />

120 default: break;<br />

}<br />

}<br />

125 /* Clean up at exit<br />

*/<br />

//***************<br />

void cleanup()<br />

//***************<br />

130 {<br />

if( image != NULL ) delete image;<br />

if( model != NULL ) delete model;<br />

if( parser != NULL ) delete parser;<br />

}<br />

135<br />

/* Get Arguments from argument list<br />

*/<br />

//**************************************************<br />

void getArguments( char **argv, Arguments *args )<br />

140 //**************************************************<br />

{<br />

bool argError = false;<br />

// set defaults<br />

145 args−>verbose = false;<br />

args−>imgName = NULL;<br />

args−>imgFormat = TGAFORMAT;<br />

args−>imgWidth = 600;<br />

args−>imgHeight = 600;<br />

150<br />

// skip program name<br />

++argv;<br />

// check for options<br />

155 if( *argv != NULL && (*argv)[0] == ’−’ )<br />

{<br />

switch( (*argv)[1] )<br />

{<br />

case ’h’: printHelp( true );<br />

160 exit(0);<br />

165 }<br />

}<br />

case ’v’: args−>verbose = true;<br />

++argv;<br />

break;<br />

// check for scene description file<br />

if( *argv == NULL )<br />

170 argError = true;<br />

else<br />

args−>sceneFile = *argv++;<br />

// check for optional arguments<br />

175 while( !argError && *argv != NULL )<br />

{<br />

if( (*argv)[0] != ’−’ )<br />

{<br />

argError = true;<br />

180 break;<br />

}<br />

switch( (*argv)[1] )<br />

{<br />

185 case ’o’: args−>imgName = *(++argv);<br />

break;<br />

case ’f’: if( *(++argv) != NULL && !strncmp( *argv, "TGA", 3 ) )<br />

args−>imgFormat = TGAFORMAT;<br />

190 else if( *argv != NULL && !strncmp( *argv, "X11", 3 ) )<br />

args−>imgFormat = XWINDOW;<br />

else<br />

argError = true;<br />

break;<br />

195<br />

case ’x’: argError = ( *(++argv) == NULL || (args−>imgWidth = atoi( *argv )) == 0 );<br />

break;<br />

case ’y’: argError = ( *(++argv) == NULL || (args−>imgHeight = atoi( *argv )) == 0 );<br />

200 break;


205 }<br />

default: argError = true;<br />

}<br />

argv++;<br />

if( argError )<br />

{<br />

printf("Error in arguments!\n");<br />

210 printHelp( false );<br />

exit(1);<br />

}<br />

if( args−>imgName == NULL )<br />

215 args−>imgName = args−>sceneFile;<br />

}<br />

/* Print help page<br />

*/<br />

220 //***********************************<br />

void printHelp( bool showDetails )<br />

//***********************************<br />

{<br />

printf("\nUsage: <strong>Ray</strong>trace [−h | −v] scenefile [−o imagename] [−f imageformat] [−x width] [−y height]\n\n");<br />

225<br />

if( showDetails )<br />

{<br />

printf(" Options:\n");<br />

printf(" −h Print this help page and exit.\n");<br />

230 printf(" −v Causes <strong>Ray</strong>trace to be verbose, showing trace information per image line.\n\n");<br />

printf(" scenefile Specify the scene description file to be rendered.\n\n");<br />

printf(" −o imagefile Specify the image filename.\n");<br />

235 printf(" The format extention will be appended. By default,\n");<br />

printf(" <strong>Ray</strong>trace uses the basename of the scenefile.\n");<br />

printf(" Note: <strong>Ray</strong>trace will overwrite image files may exist!\n\n" );<br />

printf(" −f imageformat Specify the format of the image file.\n");<br />

240 printf(" Implemented formats: TGA (default)\n");<br />

printf(" X11\n\n");<br />

245<br />

250<br />

}<br />

}<br />

printf(" −x width Determine the width of the image in pixels.\n");<br />

printf(" The default value for width is 600.\n\n");<br />

printf(" −y height Determine the height of the image in pixels.\n");<br />

printf(" The default value for height is 600.\n\n");


5<br />

#ifndef __MODEL_H<br />

#define __MODEL_H<br />

#include "Types.h"<br />

// forward declarations<br />

class Image;<br />

class Object;<br />

struct __Octree;<br />

10 struct __Arguments;<br />

typedef enum {<br />

POINTLIGHT,<br />

15 SPOTLIGHT,<br />

NUMBER_OF_LIGHTSOURCE_TYPES<br />

//*******************<br />

} LightSourceType;<br />

20 //*******************<br />

/**<br />

* Defintions of specific light sources<br />

*/<br />

25 typedef struct {<br />

LightSourceType type;<br />

Point location;<br />

Color color;<br />

//**************<br />

30 } PointLight;<br />

//**************<br />

typedef struct {<br />

LightSourceType type;<br />

35 Point location;<br />

Color color;<br />

Vector direction;<br />

Real spotBo<strong>und</strong>;<br />

//*************<br />

40 } SpotLight;<br />

//*************<br />

typedef struct {<br />

LightSourceType type;<br />

45 Point location;<br />

Color color;<br />

//************<br />

} AnyLight;<br />

//************<br />

50<br />

/**<br />

* Light data structure<br />

*/<br />

typedef union __Light {<br />

55 LightSourceType type;<br />

AnyLight any;<br />

PointLight point;<br />

SpotLight spot;<br />

//*********<br />

60 } Light;<br />

//*********<br />

typedef struct __LightNode {<br />

65 Light light;<br />

struct __LightNode *next;<br />

//*************<br />

} LightNode;<br />

//*************<br />

70<br />

typedef struct __ObjectNode {<br />

Object *object;<br />

struct __ObjectNode *next;<br />

75 //**************<br />

} ObjectNode;<br />

//**************<br />

80 typedef struct __ObjectHit {<br />

Object *object;<br />

Real distance;<br />

Point point;<br />

//*************<br />

85 } ObjectHit;<br />

//*************<br />

typedef struct __View {<br />

90 Vector location;<br />

Vector direction;<br />

Vector up;<br />

Vector right;<br />

Real widthHalf;<br />

95 Real heightHalf;<br />

bool superSampling;<br />

//********<br />

} View;<br />

//********<br />

100


* The Model class<br />

*/<br />

//**************<br />

105 class Model {<br />

//**************<br />

public:<br />

Model();<br />

~Model();<br />

110<br />

void renderModel( Image *image, struct __Arguments *args );<br />

bool addObject( Object *obj );<br />

Light *addLight();<br />

115 struct {<br />

Real intersectionCnt;<br />

unsigned int rayCnt;<br />

unsigned int view<strong>Ray</strong>Cnt;<br />

unsigned int light<strong>Ray</strong>Cnt;<br />

120 unsigned int reflectionCnt;<br />

unsigned int transpCnt;<br />

} statistics;<br />

struct {<br />

125 LightNode *lights;<br />

unsigned int numOfLights;<br />

130<br />

135<br />

ObjectNode *objects;<br />

unsigned int numOfObjects;<br />

View view;<br />

} scene;<br />

struct __Octree *octree;<br />

// function pointers<br />

bool (* intersect)( Model *model, const <strong>Ray</strong> &ray, ObjectHit *hit );<br />

void (* trace)( Model *model, <strong>Ray</strong> *ray, Real weight, int level );<br />

140 // Default function for determination of ray/object−intersections<br />

static bool defaultIntersect( Model *model, const <strong>Ray</strong> &ray, ObjectHit *hit );<br />

};<br />

145<br />

#endif /* __MODEL_H */


#include <br />

#include <br />

#include "Model.h"<br />

5 #include "Object.h"<br />

#include "Image.h"<br />

#include "Octree.h"<br />

#include "utils.h"<br />

#include "shade.h"<br />

10 #include "raytrace.h"<br />

/* Constructor<br />

*/<br />

//***************<br />

15 Model::Model()<br />

//***************<br />

{<br />

octree = NULL;<br />

20 statistics.intersectionCnt =<br />

statistics.rayCnt =<br />

statistics.view<strong>Ray</strong>Cnt =<br />

statistics.light<strong>Ray</strong>Cnt =<br />

statistics.reflectionCnt =<br />

25 statistics.transpCnt = 0;<br />

scene.lights = NULL;<br />

scene.objects = NULL;<br />

scene.numOfLights =<br />

30 scene.numOfObjects = 0;<br />

// set default view<br />

scene.view.right = baseVector[X];<br />

scene.view.up = baseVector[Y];<br />

35 scene.view.direction = baseVector[Z];<br />

scene.view.location.x = 0;<br />

scene.view.location.y = 0;<br />

scene.view.location.z = −1;<br />

40 scene.view.superSampling = false;<br />

}<br />

/* Destructor<br />

*/<br />

45 //****************<br />

Model::~Model()<br />

//****************<br />

{<br />

LightNode *lightPtr;<br />

50 ObjectNode *objPtr;<br />

// delete lights<br />

while( scene.lights != NULL )<br />

{<br />

55 lightPtr = scene.lights;<br />

scene.lights = scene.lights−>next;<br />

delete lightPtr;<br />

}<br />

60 // delete objects<br />

while( scene.objects != NULL )<br />

{<br />

objPtr = scene.objects;<br />

scene.objects = scene.objects−>next;<br />

65 if( objPtr−>object != NULL ) delete objPtr−>object;<br />

delete objPtr;<br />

}<br />

// delete octree<br />

70 if( octree != NULL )<br />

deleteOctree( octree );<br />

}<br />

75 /* Adds a light source to model<br />

*/<br />

//*************************<br />

Light *Model::addLight()<br />

//*************************<br />

80 { LightNode *tmp;<br />

if( (tmp = new LightNode) != NULL )<br />

{<br />

85 tmp−>next = scene.lights;<br />

scene.lights = tmp;<br />

++scene.numOfLights;<br />

return &tmp−>light;<br />

90 }<br />

return NULL;<br />

}<br />

/* Adds an object to model<br />

95 */<br />

//*************************************<br />

bool Model::addObject( Object *obj )<br />

//*************************************<br />

{<br />

100 ObjectNode *tmp;


if( (tmp = new ObjectNode) != NULL )<br />

{<br />

tmp−>object = obj;<br />

105 tmp−>next = scene.objects;<br />

scene.objects = tmp;<br />

++scene.numOfObjects;<br />

return true;<br />

110 }<br />

return false;<br />

}<br />

/* Renders the model by tracing and sampling each pixel of the view.<br />

115 */<br />

//*******************************************************<br />

void Model::renderModel( Image *img, Arguments *args )<br />

//*******************************************************<br />

{<br />

120 <strong>Ray</strong> ray;<br />

Color pixelColor;<br />

SamplePoint point[5];<br />

time_t start;<br />

time_t end;<br />

125 struct tm *localTime;<br />

long int duration;<br />

fprintf( stdout, "ray tracing in progress ... ");<br />

130 ray.origin = scene.view.location;<br />

scene.view.widthHalf = args−>imgWidth * 0.5;<br />

scene.view.heightHalf = args−>imgHeight * 0.5;<br />

135 // start timer<br />

time( &start );<br />

for( unsigned int y = 1; y imgHeight; ++y )<br />

{<br />

140 for( unsigned int x = 1; x imgWidth; ++x )<br />

{<br />

/**<br />

* Shot at least five rays per pixel<br />

*/<br />

145 if( x == 1 )<br />

{<br />

// reset line statistics ...<br />

statistics.rayCnt =<br />

statistics.light<strong>Ray</strong>Cnt =<br />

150 statistics.reflectionCnt =<br />

statistics.transpCnt = 0;<br />

point[0].x = 0.5; point[0].y = y − 0.5;<br />

point[1].x = 0.5; point[1].y = y + 0.5;<br />

155 point[2].x = 1.5; point[2].y = y − 0.5;<br />

point[3].x = 1.5; point[3].y = y + 0.5;<br />

point[4].x = 1.0; point[4].y = y;<br />

new<strong>Ray</strong>Direction( point[0], scene.view, &ray.direction );<br />

160 trace( this, &ray, 1.0, 1 );<br />

point[0].color = ray.color;<br />

new<strong>Ray</strong>Direction( point[1], scene.view, &ray.direction );<br />

trace( this, &ray, 1.0, 1 );<br />

165 point[1].color = ray.color;<br />

}<br />

else<br />

{<br />

point[0] = point[2];<br />

170 point[1] = point[3];<br />

point[2].x = x + 0.5; point[2].y = y − 0.5;<br />

point[3].x = x + 0.5; point[3].y = y + 0.5;<br />

point[4].x = x; point[4].y = y;<br />

}<br />

175<br />

sample( this, point, &pixelColor, 1 );<br />

img−>setPixelColor( pixelColor, x−1, y−1 );<br />

/**<br />

180 * Print line statistics<br />

*/<br />

if( x == args−>imgWidth )<br />

if( args−>verbose )<br />

fprintf( stdout, "\nline %5d: %10d rays ( %10d light, %10d reflect, %10d transp )", y,<br />

185 statistics.rayCnt,<br />

statistics.light<strong>Ray</strong>Cnt,<br />

statistics.reflectionCnt,<br />

statistics.transpCnt<br />

);<br />

190 else<br />

fprintf( stdout, "\b\b\b\b%3d%c", (int)(y * 100 / args−>imgHeight), ’%’ );<br />

}<br />

}<br />

195 time( &end );<br />

fprintf( stdout, "\ncomplete!\n" );<br />

/**<br />

* Print out statistics<br />

200 */


duration = (long int)end − start;<br />

fprintf( stdout, "\nStatistics:\n" );<br />

localTime = localtime( &start );<br />

205 fprintf( stdout, " start : %02d:%02d:%02d\n",<br />

localTime−>tm_hour, localTime−>tm_min, localTime−>tm_sec );<br />

localTime = localtime( &end );<br />

fprintf( stdout, " end : %02d:%02d:%02d\n",<br />

210 localTime−>tm_hour, localTime−>tm_min, localTime−>tm_sec );<br />

fprintf( stdout, " duration : %02d:%02d:%02d\n",<br />

(int)(duration / 3600), (int)((duration % 3600) / 60), (int)(duration % 60) );<br />

fprintf( stdout, " number of intersections : %−10.0f\n\n",<br />

215 statistics.intersectionCnt );<br />

}<br />

/* Default function for determination of intersections<br />

*/<br />

220 //***************************************************************************<br />

bool Model::defaultIntersect( Model *mdl, const <strong>Ray</strong> &ray, ObjectHit *hit )<br />

//***************************************************************************<br />

{<br />

Real distance;<br />

225 bool hitsObject = false;<br />

ObjectNode *curObj = mdl−>scene.objects;<br />

hit−>distance = DBL_MAX;<br />

230 // loop all objects of the scene ...<br />

while( curObj != NULL )<br />

{<br />

mdl−>statistics.intersectionCnt++;<br />

235 if( curObj−>object−>intersect( ray, &distance ) )<br />

if( distance < hit−>distance )<br />

{<br />

hitsObject = true;<br />

hit−>object = curObj−>object;<br />

240 hit−>distance = distance;<br />

}<br />

245<br />

}<br />

}<br />

curObj = curObj−>next;<br />

return hitsObject;


5<br />

#ifndef __SHADE_H<br />

#define __SHADE_H<br />

#include "Types.h"<br />

// forward declarations<br />

class Model;<br />

struct __View;<br />

10 typedef enum {<br />

AMBIENT, DIFFUSE,<br />

SPECULAR, TRANSPARENCY,<br />

REFLECTION,<br />

15 NUMBER_OF_LightTypes<br />

//*************<br />

} LightType;<br />

//*************<br />

20 typedef struct {<br />

Real x;<br />

Real y;<br />

Color color;<br />

//***************<br />

25 } SamplePoint;<br />

//***************<br />

//*************************<br />

#define MAXLEVEL 6<br />

30 #define MAXSAMPLE 4<br />

#define THRESHOLD 0.1<br />

#define MINWEIGHT 0.01<br />

//**************************<br />

35 void new<strong>Ray</strong>Direction( const SamplePoint &point, const struct __View &view, Vector *direction );<br />

void sample( Model *model, SamplePoint point[], Color *pixelColor, int level );<br />

bool shadowTest( Model *model, <strong>Ray</strong> *ray );<br />

// prototypes of shading functions<br />

40 void tracePhong( Model *model, <strong>Ray</strong> *ray, Real weight, int level );<br />

void traceWhitted( Model *model, <strong>Ray</strong> *ray, Real weight, int level );<br />

#endif /* __SHADE_H */


ay−>direction.y /= distance;<br />

ray−>direction.z /= distance;<br />

if( light.type == SPOTLIGHT )<br />

105 {<br />

dotProduct( light.spot.direction, ray−>direction, cosAngle );<br />

// The hit point is out of spot light<br />

// if cosine of angle is less then spot bo<strong>und</strong><br />

if( cosAngle < light.spot.spotBo<strong>und</strong> ) return true;<br />

110 }<br />

++model−>statistics.light<strong>Ray</strong>Cnt;<br />

++model−>statistics.rayCnt;<br />

115 if( model−>intersect( model, *ray, &hit ) )<br />

return ( hit.distance < distance );<br />

else<br />

return false;<br />

}<br />

120<br />

/* Determines the color of a ray using the phong model.<br />

*<br />

* Traces the ray through the scene until the maxmum level of<br />

* depth is reached or the ray leaves the scene.<br />

125 */<br />

//******************************************************************<br />

void tracePhong( Model *model, <strong>Ray</strong> *ray, Real weight, int level )<br />

//******************************************************************<br />

{<br />

130 Real nl, rv;<br />

Real t;<br />

<strong>Ray</strong> light<strong>Ray</strong>;<br />

ObjectHit hit;<br />

Vector reflection;<br />

135 Vector point;<br />

Vector v;<br />

LightNode *lightNode;<br />

Color colQuota[NUMBER_OF_LightTypes];<br />

unsigned int j;<br />

140 const ColorChannel *i;<br />

++model−>statistics.view<strong>Ray</strong>Cnt;<br />

if( model−>octree != NULL )<br />

145 {<br />

findPoint( *model−>octree, *ray, &point );<br />

findNode( model−>octree, point );<br />

}<br />

150 // initialize color quotas ...<br />

colQuota[AMBIENT] =<br />

colQuota[DIFFUSE] =<br />

colQuota[SPECULAR] =<br />

colQuota[TRANSPARENCY] =<br />

155 colQuota[REFLECTION] = black;<br />

++model−>statistics.rayCnt;<br />

// check ray for object hit ...<br />

160 if( !model−>intersect( model, *ray, &hit ) )<br />

{<br />

// no object hit => set backgro<strong>und</strong> color − black<br />

ray−>color = black;<br />

}<br />

165 else<br />

{<br />

// get hit point<br />

rayPoint( *ray, hit.distance, hit.point );<br />

170 // set hit data<br />

hit.object−>setHitData( *ray, hit.point );<br />

// get hit properties ...<br />

const Color &hitColor = hit.object−>getColor();<br />

175 const Vector &hitNormal = hit.object−>getNormal();<br />

const Material &hitMaterial = hit.object−>getMaterial();<br />

const Color &hitAmbience = hit.object−>getAmbience();<br />

/* −−−−−−−−−−−−−−−−− ambient light −−−−−−−−−−−−−−−*/<br />

180 for( i = RGBfirst; *i != ALPHA; i++ )<br />

colQuota[AMBIENT].channel[*i] = hitAmbience.channel[*i] * hitColor.channel[*i];<br />

// ray to light starts at hit point<br />

185 light<strong>Ray</strong>.origin = hit.point;<br />

// traverse all light sources ...<br />

for( lightNode = model−>scene.lights; lightNode != NULL; lightNode = lightNode−>next )<br />

{<br />

190 Light &curLight = lightNode−>light;<br />

// get light ray direction<br />

subVector( curLight.any.location, hit.point, light<strong>Ray</strong>.direction );<br />

195 if( !shadowTest( model, curLight, &light<strong>Ray</strong> ) )<br />

{<br />

/* −−−−−−−−−−−−−−−−−−−−− diffuse reflection −−−−−−−−−−−−−−−−−− */<br />

dotProduct( light<strong>Ray</strong>.direction, hitNormal, nl );<br />

if( nl > 0.0 )<br />

200 for( i = RGBfirst; *i != ALPHA; i++ )


colQuota[DIFFUSE].channel[*i] += curLight.any.color.channel[*i] * hitColor.channel[*i]*nl;<br />

/* −−−−−−−−−−−−−−−−−−−−− specular reflection −−−−−−−−−−−−−−−−− */<br />

205 normalizeVector( light<strong>Ray</strong>.direction, light<strong>Ray</strong>.direction );<br />

negateVector( light<strong>Ray</strong>.direction, light<strong>Ray</strong>.direction );<br />

dotProduct( hitNormal, light<strong>Ray</strong>.direction, nl );<br />

scaleVector( hitNormal, (−2 * nl), reflection );<br />

210 addVector( reflection, light<strong>Ray</strong>.direction, reflection );<br />

normalizeVector( reflection, reflection );<br />

215<br />

negateVector( ray−>direction, v );<br />

dotProduct( reflection, v, rv );<br />

if( rv > 0.0 )<br />

{<br />

t = pow( rv, hitMaterial.specularExp );<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

220 colQuota[SPECULAR].channel[*i] += curLight.any.color.channel[*i] * t;<br />

}<br />

}<br />

}<br />

225 // consider surface properties<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

colQuota[AMBIENT].channel[*i] *= hitMaterial.ambient.channel[*i];<br />

colQuota[DIFFUSE].channel[*i] *= hitMaterial.diffuse.channel[*i];<br />

230 colQuota[SPECULAR].channel[*i] *= hitMaterial.specular.channel[*i];<br />

}<br />

// check for invalid color quotas ...<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

235 for( j = AMBIENT; j < NUMBER_OF_LightTypes; ++j )<br />

if( colQuota[j].channel[*i] < 0.0 )<br />

colQuota[j].channel[*i] = 0.0;<br />

240 // initialize ray color<br />

ray−>color = black;<br />

// sum light types ...<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

245 {<br />

for( j = AMBIENT; j < NUMBER_OF_LightTypes; ++j )<br />

ray−>color.channel[*i] += colQuota[j].channel[*i];<br />

if( ray−>color.channel[*i] > 1.0 )<br />

250 ray−>color.channel[*i] = 1.0;<br />

}<br />

}<br />

}<br />

255 /* Determines the color of a ray using the whitted model.<br />

*<br />

* Traces the ray through the scene until the maxmum level of<br />

* depth is reached or the ray leaves the scene.<br />

*/<br />

260 //********************************************************************<br />

void traceWhitted( Model *model, <strong>Ray</strong> *ray, Real weight, int level )<br />

//********************************************************************<br />

{<br />

Real nl, nh, nv;<br />

265 Real s, t;<br />

Real totalReflection;<br />

<strong>Ray</strong> light<strong>Ray</strong>;<br />

<strong>Ray</strong> new<strong>Ray</strong>;<br />

ObjectHit hit;<br />

270 Vector reflection;<br />

Vector half;<br />

Vector point;<br />

LightNode *lightNode;<br />

Color colQuota[NUMBER_OF_LightTypes];<br />

275 unsigned int j;<br />

280<br />

const Color *objAmbience;<br />

const Material *objMaterial;<br />

const ColorChannel *i;<br />

if( level == 1 )<br />

{<br />

++model−>statistics.view<strong>Ray</strong>Cnt;<br />

285 if( model−>octree != NULL )<br />

{<br />

findPoint( *model−>octree, *ray, &point );<br />

findNode( model−>octree, point );<br />

}<br />

290 }<br />

else if( model−>octree != NULL )<br />

findNode( model−>octree, ray−>origin );<br />

// initialize color quotas ...<br />

295 colQuota[AMBIENT] =<br />

colQuota[DIFFUSE] =<br />

colQuota[SPECULAR] =<br />

colQuota[TRANSPARENCY] =<br />

colQuota[REFLECTION] = black;<br />

300


++model−>statistics.rayCnt;<br />

// check ray for object hit ...<br />

if( !model−>intersect( model, *ray, &hit ) )<br />

305 {<br />

// no object hit => set backgro<strong>und</strong> color − black<br />

ray−>color = black;<br />

}<br />

else<br />

310 {<br />

// get hit point<br />

rayPoint( *ray, hit.distance, hit.point );<br />

// set hit data<br />

315 hit.object−>setHitData( *ray, hit.point );<br />

// get hit properties ...<br />

const Color &hitColor = hit.object−>getColor();<br />

const Vector &hitNormal = hit.object−>getNormal();<br />

320 const Material &hitMaterial = hit.object−>getMaterial();<br />

const Color &hitAmbience = hit.object−>getAmbience();<br />

/* −−−−−−−−−−−−−−−−− ambient light −−−−−−−−−−−−−−−*/<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

325 colQuota[AMBIENT].channel[*i] = hitAmbience.channel[*i] * hitColor.channel[*i];<br />

330<br />

335<br />

// ray to light starts at hit point<br />

light<strong>Ray</strong>.origin = hit.point;<br />

// traverse all light sources ...<br />

for( lightNode = model−>scene.lights; lightNode != NULL; lightNode = lightNode−>next )<br />

{<br />

Light &curLight = lightNode−>light;<br />

// get light ray direction<br />

subVector( curLight.any.location, hit.point, light<strong>Ray</strong>.direction );<br />

if( !shadowTest( model, curLight, &light<strong>Ray</strong> ) )<br />

340 {<br />

/* −−−−−−−−−−−−−−−−−−−−− diffuse reflection −−−−−−−−−−−−−−−−−− */<br />

dotProduct( light<strong>Ray</strong>.direction, hitNormal, nl );<br />

if( nl > 0.0 )<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

345 colQuota[DIFFUSE].channel[*i] += curLight.any.color.channel[*i] * hitColor.channel[*i]*nl;<br />

/* −−−−−−−−−−−−−−−−−−−−− specular reflection −−−−−−−−−−−−−−−−− */<br />

negateVector( light<strong>Ray</strong>.direction, light<strong>Ray</strong>.direction );<br />

addVector( light<strong>Ray</strong>.direction, ray−>direction, half );<br />

350 scaleVector( half, 0.5, half );<br />

normalizeVector( half, half );<br />

negateVector( half, half );<br />

dotProduct( half, hitNormal, nh );<br />

355 if( nh > 0.0 )<br />

{<br />

t = pow( nh, hitMaterial.specularExp );<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

colQuota[SPECULAR].channel[*i] += curLight.any.color.channel[*i] * t;<br />

360 }<br />

}<br />

}<br />

// consider material coefficients<br />

365 for( i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

colQuota[AMBIENT].channel[*i] *= hitMaterial.ambient.channel[*i];<br />

colQuota[DIFFUSE].channel[*i] *= hitMaterial.diffuse.channel[*i];<br />

colQuota[SPECULAR].channel[*i] *= hitMaterial.specular.channel[*i];<br />

370 }<br />

dotProduct( ray−>direction, hitNormal, nv );<br />

totalReflection = 0.0;<br />

375 /* −−−−−−−−−−−−−−−−−− global transparency −−−−−−−−−−−−−−−−−− */<br />

if( (level < MAXLEVEL) && (weight * hitMaterial.transparency > MINWEIGHT) )<br />

{<br />

s = (nv < 0.0)? 1 / hitMaterial.refractIndex : hitMaterial.refractIndex;<br />

t = 1 − s * s * (1 − nv * nv);<br />

380<br />

if( t >= 0.0 )<br />

{<br />

t = −s * nv + ((nv < 0.0)? −sqrt( t ) : sqrt( t ));<br />

scaleVector( ray−>direction, s, new<strong>Ray</strong>.direction );<br />

385 scaleVector( hitNormal, t, reflection );<br />

addVector( reflection, new<strong>Ray</strong>.direction, new<strong>Ray</strong>.direction );<br />

normalizeVector( new<strong>Ray</strong>.direction, new<strong>Ray</strong>.direction );<br />

new<strong>Ray</strong>.origin = hit.point;<br />

390 ++model−>statistics.transpCnt;<br />

model−>trace( model, &new<strong>Ray</strong>, (weight * hitMaterial.transparency ), level + 1 );<br />

colQuota[TRANSPARENCY] = new<strong>Ray</strong>.color;<br />

395 for( i = RGBfirst; *i != ALPHA; i++ )<br />

colQuota[TRANSPARENCY].channel[*i] *= hitMaterial.transparency;<br />

}<br />

else<br />

totalReflection = hitMaterial.transparency;<br />

400 }


* −−−−−−−−−−−−−−−− global reflection −−−−−−−−−−−−−−−−−− */<br />

if( (level < MAXLEVEL) && (weight * hitMaterial.reflect + totalReflection > MINWEIGHT) )<br />

{<br />

405 ++model−>statistics.reflectionCnt;<br />

410<br />

scaleVector( hitNormal, (−2 * nv), reflection );<br />

addVector( reflection, ray−>direction, reflection );<br />

normalizeVector( reflection, new<strong>Ray</strong>.direction );<br />

// reflected ray starts at hit point<br />

new<strong>Ray</strong>.origin = hit.point;<br />

model−>trace( model, &new<strong>Ray</strong>, (weight * hitMaterial.reflect), level + 1 );<br />

415 colQuota[REFLECTION] = new<strong>Ray</strong>.color;<br />

420<br />

}<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

colQuota[REFLECTION].channel[*i] *= (hitMaterial.reflect + totalReflection);<br />

// check for invalid color quotas ...<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

for( j = AMBIENT; j < NUMBER_OF_LightTypes; ++j )<br />

if( colQuota[j].channel[*i] < 0.0 )<br />

425 colQuota[j].channel[*i] = 0.0;<br />

// initialize ray color<br />

ray−>color = black;<br />

430 // sum light types ...<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

for( j = AMBIENT; j < NUMBER_OF_LightTypes; ++j )<br />

ray−>color.channel[*i] += colQuota[j].channel[*i];<br />

435<br />

440 }<br />

}<br />

}<br />

if( ray−>color.channel[*i] > 1.0 )<br />

ray−>color.channel[*i] = 1.0;


5<br />

#ifndef __NOISE_H<br />

#define __NOISE_H<br />

#include "Types.h"<br />

#define TABSIZE (1


5<br />

10<br />

#include <br />

#include "Noise.h"<br />

#include "utils.h"<br />

// initialize static elements<br />

const Noise::Turbulence Noise::defaultTurbulence = { 0, 0.0, 1.0, 1.0 };<br />

Noise *Noise::noise = NULL;<br />

int Noise::refCnt = 0;<br />

/**<br />

* Inline functions<br />

*/<br />

// smooth noise values<br />

15 //**************************<br />

Real smoothStep( Real x )<br />

//**************************<br />

{<br />

return ((− 2 * x + 3) * x * x);<br />

20 }<br />

// smooth noise gradients<br />

//******************************<br />

Real smoothGradient( Real x )<br />

25 //******************************<br />

{<br />

return ((−6 * x + 6) * x);<br />

}<br />

30 /* Get a pointer to a noise object.<br />

*/<br />

//*************************<br />

Noise *Noise::getNoise()<br />

//*************************<br />

35 {<br />

if( noise == NULL )<br />

if( (noise = new Noise()) != NULL )<br />

++refCnt;<br />

else ++refCnt;<br />

40<br />

}<br />

return noise;<br />

/* Release noise object pointer.<br />

45 */<br />

//***************************<br />

void Noise::releaseNoise()<br />

//***************************<br />

{<br />

50 if( refCnt > 0 )<br />

{<br />

−−refCnt;<br />

if( refCnt == 0 ) delete noise;<br />

}<br />

55 }<br />

/* Constructor<br />

*/<br />

//***************<br />

60 Noise::Noise()<br />

//***************<br />

{<br />

unsigned int i;<br />

unsigned int swp_a, swp_b;<br />

65<br />

// setting seed for random generator<br />

srand( 42 );<br />

// fill tables<br />

70 for( i = 0; i < TABSIZE; ++i )<br />

{<br />

noiseTable[i] = 1.0 − 2.0 * (Real)rand() / (Real)RAND_MAX; // −> [−1,1]<br />

indexTable[i] = i;<br />

}<br />

75<br />

// shuffle index table<br />

for( i = 0; i < TABSIZE*TABSIZE/2; ++i )<br />

{<br />

swp_a = i % TABSIZE;<br />

80 swp_b = rand() % TABSIZE;<br />

85<br />

}<br />

}<br />

swapXOR( indexTable[swp_a], indexTable[swp_b] );<br />

/* Destructor<br />

*/<br />

//****************<br />

Noise::~Noise()<br />

90 //****************<br />

{<br />

}<br />

/* Determines interpolated noise in point given by parameter.<br />

95 */<br />

//*************************************************<br />

Real Noise::getSmoothNoise( const Point &point )<br />

//*************************************************<br />

{<br />

100 int pInt[3];


105<br />

110<br />

Real pFrct[3];<br />

Real ltc_x0, ltc_x1;<br />

Real edge[4];<br />

Real face_z0, face_z1;<br />

// split point coordinates into integer<br />

// part and smooth fractional part<br />

pInt[X] = floorVal( point.x );<br />

pFrct[X] = smoothStep( point.x − pInt[X] );<br />

pInt[Y] = floorVal( point.y );<br />

pFrct[Y] = smoothStep( point.y − pInt[Y] );<br />

pInt[Z] = floorVal( point.z );<br />

115 pFrct[Z] = smoothStep( point.z − pInt[Z] );<br />

/**<br />

* Get lattice noise values at the eight points<br />

* of the lattice noise cube and interpolate these values<br />

120 * according to the fractional parts of the 3D−point.<br />

*/<br />

// front edge − top<br />

ltc_x0 = latticeNoise( pInt[X], pInt[Y], pInt[Z] );<br />

ltc_x1 = latticeNoise( pInt[X] + 1, pInt[Y], pInt[Z] );<br />

125 edge[0] = blend( ltc_x1, ltc_x0, pFrct[X] );<br />

// front edge − bottom<br />

ltc_x0 = latticeNoise( pInt[X], pInt[Y] + 1, pInt[Z] );<br />

ltc_x1 = latticeNoise( pInt[X] + 1, pInt[Y] + 1, pInt[Z] );<br />

edge[1] = blend( ltc_x1, ltc_x0, pFrct[X] );<br />

130 // back edge − top<br />

ltc_x0 = latticeNoise( pInt[X], pInt[Y], pInt[Z] + 1 );<br />

ltc_x1 = latticeNoise( pInt[X] + 1, pInt[Y], pInt[Z] + 1 );<br />

edge[2] = blend( ltc_x1, ltc_x0, pFrct[X] );<br />

// back edge − bottom<br />

135 ltc_x0 = latticeNoise( pInt[X], pInt[Y] + 1, pInt[Z] + 1 );<br />

ltc_x1 = latticeNoise( pInt[X] + 1, pInt[Y] + 1, pInt[Z] + 1 );<br />

edge[3] = blend( ltc_x1, ltc_x0, pFrct[X] );<br />

// blend over front and back face<br />

140 face_z0 = blend( edge[1], edge[0], pFrct[Y] );<br />

face_z1 = blend( edge[3], edge[2], pFrct[Y] );<br />

145 }<br />

// return blended face values<br />

return blend( face_z1, face_z0, pFrct[Z] );<br />

/* Determines the gradient vector in point given by parameter.<br />

*/<br />

150 //**********************************************************************<br />

void Noise::getSmoothGradient( const Point &point, Vector *gradient )<br />

//**********************************************************************<br />

{<br />

int pInt[3];<br />

155 Real pFrct[3];<br />

Real ltc000, ltc001, ltc010, ltc011, ltc100, ltc101, ltc110, ltc111;<br />

Real edge[4];<br />

Real face[2];<br />

160 // split point coordinates into integer<br />

// part and fractional part<br />

pInt[X] = floorVal( point.x );<br />

pFrct[X] = point.x − pInt[X];<br />

pInt[Y] = floorVal( point.y );<br />

165 pFrct[Y] = point.y − pInt[Y];<br />

pInt[Z] = floorVal( point.z );<br />

pFrct[Z] = point.z − pInt[Z];<br />

// get lattice noise values at the eight<br />

170 // points ltc of the lattice noise cube.<br />

ltc000 = latticeNoise( pInt[X], pInt[Y], pInt[Z] );<br />

ltc001 = latticeNoise( pInt[X], pInt[Y], pInt[Z] + 1 );<br />

ltc010 = latticeNoise( pInt[X], pInt[Y] + 1, pInt[Z] );<br />

ltc011 = latticeNoise( pInt[X], pInt[Y] + 1, pInt[Z] + 1 );<br />

175 ltc100 = latticeNoise( pInt[X] + 1, pInt[Y], pInt[Z] );<br />

ltc101 = latticeNoise( pInt[X] + 1, pInt[Y], pInt[Z] + 1 );<br />

ltc110 = latticeNoise( pInt[X] + 1, pInt[Y] + 1, pInt[Z] );<br />

ltc111 = latticeNoise( pInt[X] + 1, pInt[Y] + 1, pInt[Z] + 1 );<br />

180 /**<br />

* Determine gradient of X−axis<br />

*/<br />

// blend along the four Z−edges of the cube<br />

edge[0] = blend( ltc001, ltc000, pFrct[Z] );<br />

185 edge[1] = blend( ltc011, ltc010, pFrct[Z] );<br />

edge[2] = blend( ltc101, ltc100, pFrct[Z] );<br />

edge[3] = blend( ltc111, ltc110, pFrct[Z] );<br />

// blend over left and right face<br />

face[0] = blend( edge[1], edge[0], pFrct[Y] );<br />

190 face[1] = blend( edge[3], edge[2], pFrct[Y] );<br />

// determine gradient<br />

gradient−>x = (face[1] − face[0]) * smoothGradient( pFrct[X] );<br />

/**<br />

195 * Determine gradient of Y−axis<br />

*/<br />

// blend over top and bottom face<br />

face[0] = blend( edge[2], edge[0], pFrct[X] );<br />

face[1] = blend( edge[3], edge[1], pFrct[X] );<br />

200 // determine gradient


gradient−>y = (face[1] − face[0]) * smoothGradient( pFrct[Y] );<br />

/**<br />

* Determine gradient of Z−axis<br />

205 */<br />

// blend along the four Y−edges of the cube<br />

edge[0] = blend( ltc010, ltc000, pFrct[Y] );<br />

edge[1] = blend( ltc110, ltc100, pFrct[Y] );<br />

edge[2] = blend( ltc011, ltc001, pFrct[Y] );<br />

210 edge[3] = blend( ltc111, ltc101, pFrct[Y] );<br />

// blend over front and back face<br />

face[0] = blend( edge[1], edge[0], pFrct[X] );<br />

face[1] = blend( edge[3], edge[2], pFrct[X] );<br />

// determine gradient<br />

215 gradient−>z = (face[1] − face[0]) * smoothGradient( pFrct[Z] );<br />

}<br />

/* Calculates the fractal sum noise in point given by parameter.<br />

220 */<br />

//**************************************************************************<br />

Real Noise::getFractalNoise( const Point &point, const Turbulence &turb )<br />

//**************************************************************************<br />

{<br />

225 Point noisePoint, p;<br />

Real fktlNoiseVal;<br />

Real curFreq = FQ_SCALE;<br />

scaleVector( point, turb.scale, noisePoint );<br />

230 fktlNoiseVal = getSmoothNoise( noisePoint );<br />

for( int i = 1; i < turb.frequencies; ++i )<br />

{<br />

scaleVector( noisePoint, curFreq, p );<br />

235 fktlNoiseVal += getSmoothNoise( p ) / curFreq;<br />

curFreq *= FQ_SCALE;<br />

}<br />

fktlNoiseVal = (fktlNoiseVal+1.0) / 2.0; // [−1,1] −> [0,1]<br />

240 if( fktlNoiseVal < 0.0 ) fktlNoiseVal = 0.0;<br />

if( fktlNoiseVal > 1.0 ) fktlNoiseVal = 1.0;<br />

if( turb.exponent == 0.0 ) return turb.size;<br />

if( turb.exponent == 1.0 ) return fktlNoiseVal * turb.size;<br />

245 else return pow( fktlNoiseVal, turb.exponent ) * turb.size;<br />

}<br />

/* Calculates the fractal sum gradient in point given by parameter.<br />

250 */<br />

//***********************************************************************************************<br />

void Noise::getFractalGradient( const Point &point, const Turbulence &turb, Vector *fktlGrad )<br />

//***********************************************************************************************<br />

{<br />

255 Vector gradient;<br />

Point noisePoint, p;<br />

Real curFreq = FQ_SCALE;<br />

Real scale;<br />

int i;<br />

260<br />

scaleVector( point, turb.scale, noisePoint );<br />

getSmoothGradient( noisePoint, fktlGrad );<br />

for( i = 1; i < turb.frequencies; ++i )<br />

265 {<br />

// scale point<br />

scaleVector( noisePoint, curFreq, p );<br />

// determine gradient<br />

getSmoothGradient( p, &gradient );<br />

270<br />

// sum gradient<br />

fktlGrad−>x += gradient.x / curFreq;<br />

fktlGrad−>y += gradient.y / curFreq;<br />

fktlGrad−>z += gradient.z / curFreq;<br />

275 curFreq *= FQ_SCALE;<br />

}<br />

scaleVector( *fktlGrad, turb.size, *fktlGrad );<br />

}


5<br />

#ifndef __POINTMAPPER_H<br />

#define __POINTMAPPER_H<br />

#include "Types.h"<br />

/* The PointMapper class<br />

*/<br />

//********************<br />

class PointMapper {<br />

10 //********************<br />

public:<br />

typedef enum {<br />

LAYOUT_STRETCH,<br />

LAYOUT_TILE<br />

15 //*****************<br />

} MappingLayout;<br />

//*****************<br />

typedef enum {<br />

20 MAP_LINEAR,<br />

MAP_SPHERIC,<br />

MAP_EXTERN<br />

//***************<br />

} MappingMode;<br />

25 //***************<br />

typedef struct {<br />

char filename[256];<br />

Vector scale;<br />

30 MappingLayout layout;<br />

MappingMode mappingMode[3];<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

35<br />

40<br />

static const ParseInfo defaultData;<br />

PointMapper( const ParseInfo &data = defaultData );<br />

virtual ~PointMapper() {}<br />

void setMappingMode( Coordinate c, MappingMode m ) { mode[c] = m; }<br />

void setMappingLayout( MappingLayout l ) { layout = l; }<br />

void setMapDimension( Coordinate c, Real dim ) { mapDim[c] = dim; }<br />

void setSurfaceDimRef( const Real * const ref ) { surfDim = ref; }<br />

45 void setExternMappingRef( const Real * const ref ) { extMapping = ref; }<br />

Real getMapDimension( Coordinate c ) { return mapDim[c]; }<br />

void mapPoint( const Point &point, Point *result );<br />

50 void getMapDirections( const Point &point, Vector *dirX, Vector *dirY );<br />

protected:<br />

MappingMode mode[3];<br />

MappingLayout layout;<br />

55 Real mapDim[3];<br />

const Real *surfDim;<br />

const Real *extMapping;<br />

};<br />

60 #endif /* __POINTMAPPER_H */


#include "PointMapper.h"<br />

#include "utils.h"<br />

5 // initialize default data<br />

const PointMapper::ParseInfo PointMapper::defaultData = { "", oneVector, LAYOUT_STRETCH,<br />

{ MAP_LINEAR, MAP_LINEAR, MAP_LINEAR } };<br />

/* Constructor<br />

10 */<br />

//***************************************************************<br />

PointMapper::PointMapper( const PointMapper::ParseInfo &data )<br />

//***************************************************************<br />

{<br />

15 layout = data.layout;<br />

for( unsigned int i = X; i [−pi,+pi]<br />

40 phi = atan2( point.z, point.x );<br />

// [−pi,+pi] −> [0,1]<br />

fract = (phi + M_PI) / (2 * M_PI);<br />

// tiling<br />

45 if( layout == LAYOUT_TILE )<br />

fract = (surfDim[X] * fract) / mapDim[X];<br />

fract −= (int)fract;<br />

result−>x = fract;<br />

50 break;<br />

case MAP_LINEAR: // map coordinate lineary<br />

if( layout == LAYOUT_STRETCH )<br />

fract = point.x / surfDim[X];<br />

55 else // tiling<br />

fract = point.x / mapDim[X];<br />

fract −= (int)fract;<br />

// prevent image mirroring<br />

60 if( fract < 0.0 ) fract += 1.0;<br />

result−>x = fract;<br />

break;<br />

case MAP_EXTERN: // use extern result<br />

65 if( layout == LAYOUT_STRETCH )<br />

result−>x = extMapping[X];<br />

else // tiling<br />

{<br />

fract = extMapping[X] / mapDim[X];<br />

70 fract −= (int)fract;<br />

result−>x = fract;<br />

}<br />

}<br />

75 /**<br />

* Map Y coordinate according to mapping info<br />

* Note: Due to the y−axis of the image increasing downwards,<br />

* the sign of the y−coordinate of the point is changed!<br />

*/<br />

80 switch( mode[Y] )<br />

{<br />

case MAP_SPHERIC: // map coordinate spherical<br />

radius = vectorLength( point );<br />

85 if( radius != 0.0 )<br />

{<br />

// acos −> [0,pi]<br />

phi = acos( −point.y / radius );<br />

// [0,pi] −> [0,1]<br />

90 fract = (M_PI − phi) / M_PI;<br />

95<br />

fract −= (int)fract;<br />

result−>y = fract;<br />

}<br />

else<br />

100 result−>y = 0.0;<br />

// tiling<br />

if( layout == LAYOUT_TILE )<br />

fract = (surfDim[Y] * fract) / mapDim[Y];


eak;<br />

case MAP_LINEAR: // map coordinate lineary<br />

if( layout == LAYOUT_STRETCH )<br />

105 fract = −point.y / surfDim[Y];<br />

else // tiling<br />

fract = −point.y / mapDim[Y];<br />

fract −= (int)fract;<br />

110 // prevent image mirroring<br />

if( fract < 0.0 ) fract += 1.0;<br />

result−>y = fract;<br />

break;<br />

115 case MAP_EXTERN: // use extern result<br />

if( layout == LAYOUT_STRETCH )<br />

result−>y = extMapping[Y];<br />

else // tiling<br />

{<br />

120 fract = extMapping[Y] / mapDim[Y];<br />

fract −= (int)fract;<br />

result−>y = fract;<br />

}<br />

}<br />

125 result−>z = 0;<br />

}<br />

/* Determines mapping directions X and Y<br />

*/<br />

130 //*************************************************************************************<br />

void PointMapper::getMapDirections( const Point &point, Vector *dirX, Vector *dirY )<br />

//*************************************************************************************<br />

{<br />

if( mode[X] == MAP_SPHERIC )<br />

135 {<br />

crossProduct( point, baseVector[Y], *dirX );<br />

normalizeVector( *dirX, *dirX );<br />

}<br />

else *dirX = baseVector[X];<br />

140<br />

if( mode[Y] == MAP_SPHERIC )<br />

{<br />

crossProduct( *dirX, point, *dirY );<br />

normalizeVector( *dirY, *dirY );<br />

145 }<br />

else *dirY = baseVector[Y];<br />

}


#ifndef __TEXTURE_H<br />

#define __TEXTURE_H<br />

#include "Types.h"<br />

5 #include "Noise.h"<br />

#include "utils.h"<br />

/* The Texture class<br />

*/<br />

10 //****************<br />

class Texture {<br />

//****************<br />

public:<br />

typedef struct {<br />

15 Matrix transform;<br />

Noise::Turbulence turbulence;<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

20<br />

25<br />

static const ParseInfo defaultData;<br />

Texture();<br />

virtual ~Texture() { Noise::releaseNoise(); }<br />

void setupTextureSystem( const Matrix &transform )<br />

{<br />

toTexture = transform;<br />

invertMatrix( toTexture, &fromTexture );<br />

30 }<br />

void setTurbulence( const Noise::Turbulence &turb )<br />

{<br />

turbulence = turb;<br />

35 isTurbulent = (turb.frequencies > 0 && turb.size != 0.0);<br />

}<br />

void setBaseColor( const Color &color ) { baseColor = color; }<br />

void setBaseMaterial( const Material &material ) { baseMaterial = material; }<br />

40 void setSurfaceDimension( Coordinate coord, Real dim ) { surfDimension[coord] = absVal( dim ); }<br />

void setExternMapping( Coordinate coord, Real fact ) { externMapping[coord] = absVal( fact ); }<br />

void setHitData( const Point &colHit, const Point &nrmlHit );<br />

/*<br />

45 * Set parameters may be used by some textures (e.g. ReflectionTexture)<br />

* Note: normal and ray direction are expected to be in world system!<br />

*/<br />

void set<strong>Ray</strong>AndNormal( const <strong>Ray</strong> &ray, const Vector &normal )<br />

{<br />

50 if( needs<strong>Ray</strong>AndNormal() ) {<br />

rayDirection = ray.direction;<br />

vertexNormal = normal;<br />

}<br />

}<br />

55<br />

/**<br />

* Texture specific methods.<br />

*/<br />

virtual bool is2D() = 0;<br />

60 virtual bool needs<strong>Ray</strong>AndNormal() = 0;<br />

virtual void detectTextureData() = 0;<br />

virtual const Color &getColor() { return baseColor; }<br />

virtual const Material &getMaterial() { return baseMaterial; }<br />

65 // methods for texture sharing<br />

void registerHandle() { handles++; }<br />

void releaseHandle() { if( handles > 0 ) handles−−; };<br />

bool isReferenced() { return (handles > 0); }<br />

70 protected:<br />

Matrix toTexture;<br />

Matrix fromTexture;<br />

Color baseColor;<br />

Material baseMaterial;<br />

75 Noise *noise;<br />

Vector colorHitPoint;<br />

Vector normalHitPoint;<br />

Vector rayDirection;<br />

80 Vector vertexNormal;<br />

Real surfDimension[3];<br />

Real externMapping[3];<br />

private:<br />

85 void getCoordTurbulence( const Vector &point, Vector *displacement );<br />

90 };<br />

Noise::Turbulence turbulence;<br />

bool isTurbulent;<br />

int handles;<br />

#endif /* __TEXTURE_H */


5<br />

#include <br />

#include "Texture.h"<br />

#include "utils.h"<br />

// initialize default data<br />

const Texture::ParseInfo Texture::defaultData = { identityMatrix, Noise::defaultTurbulence };<br />

/* Constructor<br />

10 */<br />

//*******************<br />

Texture::Texture()<br />

//*******************<br />

{<br />

15 // initialize texture system<br />

toTexture =<br />

fromTexture = identityMatrix;<br />

baseColor = white;<br />

20 baseMaterial = defaultMaterial;<br />

// get noise object<br />

noise = Noise::getNoise();<br />

25 // set defaults<br />

for( unsigned int i = X; i x = noise−>getFractalNoise( noisePoint, turbulence );<br />

addVector( point, noiseDispY, noisePoint );<br />

90 displacement−>y = noise−>getFractalNoise( noisePoint, turbulence );<br />

addVector( point, noiseDispZ, noisePoint );<br />

displacement−>z = noise−>getFractalNoise( noisePoint, turbulence );<br />

95 // move noise displacement from interval [0,1] to [−1,1]<br />

// and scale it by turbulence size<br />

translateVector( *displacement, −0.5, *displacement );<br />

scaleVector( *displacement, 2.0 * turbulence.size, *displacement );<br />

}<br />

100


#ifndef __BUMPTEXTURE_H<br />

#define __BUMPTEXTURE_H<br />

#include "Texture.h"<br />

5 #include "PointMapper.h"<br />

#include "Types.h"<br />

10<br />

// forward declaration<br />

class TGAImage;<br />

/* The BumpTexture class<br />

*/<br />

//*************************************<br />

class BumpTexture : public Texture {<br />

15 //*************************************<br />

public:<br />

typedef struct {<br />

PointMapper::ParseInfo mapperInfo;<br />

Real bumpSize;<br />

20 Noise::Turbulence turbulence;<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

25 static const ParseInfo defaultData;<br />

BumpTexture();<br />

virtual ~BumpTexture();<br />

30 // setup bump texture<br />

void setBumpData( const ParseInfo &data );<br />

35<br />

void getBumpNormal( const Vector &normal, Vector *bumpNormal );<br />

bool isBumpy() { return isBumpyTexture; }<br />

/* This default implementation provides<br />

* a plain colored and bumpy texture<br />

*/<br />

virtual bool is2D() { return false; }<br />

40 virtual bool needs<strong>Ray</strong>AndNormal() { return false; }<br />

virtual void detectTextureData() {}<br />

protected:<br />

virtual void getTextureSlope( Vector *slope ) {}<br />

45 bool isBumpyTexture;<br />

private:<br />

void getMapSlope( Vector *slope );<br />

50 void getNoiseSlope( Vector *slope )<br />

{<br />

Vector noiseGradient;<br />

noise−>getFractalGradient( normalHitPoint, turbulence, &noiseGradient );<br />

addVector( *slope, noiseGradient, *slope );<br />

55 }<br />

TGAImage *bumpMap;<br />

Real bumpSize;<br />

Noise::Turbulence turbulence;<br />

60 PointMapper mapper;<br />

};<br />

//**********************************<br />

typedef BumpTexture PlainTexture;<br />

65 //**********************************<br />

#endif /* __BUMPTEXTURE_H */


#include "BumpTexture.h"<br />

#include "TGAImage.h"<br />

#include "Noise.h"<br />

5 #include "utils.h"<br />

10<br />

// initialize default data<br />

const BumpTexture::ParseInfo BumpTexture::defaultData = { PointMapper::defaultData, 0.0,<br />

Noise::defaultTurbulence };<br />

/* Constructor<br />

*/<br />

//***************************<br />

BumpTexture::BumpTexture()<br />

15 //***************************<br />

: Texture(), mapper()<br />

{<br />

bumpMap = NULL;<br />

bumpSize = 0.0;<br />

20 isBumpyTexture = false;<br />

turbulence = Noise::defaultTurbulence;<br />

}<br />

/* Destructor<br />

25 */<br />

//****************************<br />

BumpTexture::~BumpTexture()<br />

//****************************<br />

{<br />

30 if( bumpMap != NULL ) delete bumpMap;<br />

}<br />

/* Enable bump mapping by setting bump data<br />

*/<br />

35 //********************************************************************<br />

void BumpTexture::setBumpData( const BumpTexture::ParseInfo &data )<br />

//********************************************************************<br />

{<br />

/**<br />

40 * Set bump map, scaling and layout<br />

*/<br />

// release bump map may be loaded<br />

if( bumpMap != NULL ) delete bumpMap;<br />

45 // try to load bump map file<br />

if( data.mapperInfo.filename[0] )<br />

{<br />

if( (bumpMap = new TGAImage( data.mapperInfo.filename )) == NULL )<br />

fprintf( stderr, "Unable to load bump map %s\n", data.mapperInfo.filename );<br />

50 else<br />

{<br />

// set map scaling<br />

bumpMap−>setScale( X, data.mapperInfo.scale.x );<br />

bumpMap−>setScale( Y, data.mapperInfo.scale.y );<br />

55 bumpMap−>setScale( Z, data.mapperInfo.scale.z );<br />

mapper.setMappingLayout( data.mapperInfo.layout );<br />

for( unsigned int i = X; i getScaledDim( (Coordinate)i ) );<br />

}<br />

}<br />

}<br />

65 mapper.setSurfaceDimRef( &surfDimension[0] );<br />

mapper.setExternMappingRef( &externMapping[0] );<br />

70<br />

// set bump turbulence<br />

turbulence = data.turbulence;<br />

// set bump size<br />

bumpSize = data.bumpSize;<br />

// enable/disable bump mapping<br />

75 isBumpyTexture = (bumpSize != 0.0);<br />

}<br />

/* Get the normal vector adjusted to the textures properties<br />

*/<br />

80 //****************************************************************************<br />

void BumpTexture::getBumpNormal( const Vector &normal, Vector *bumpNormal )<br />

//****************************************************************************<br />

{<br />

Vector n;<br />

85 Vector slope;<br />

Vector vct_U, vct_V, vct_D;<br />

Real size_U, size_V;<br />

// transform normal vector by inverse matrix into texture system<br />

90 transformNormal( fromTexture, normal, &n );<br />

normalizeVector( n, n );<br />

// Place vectors U and V in the<br />

// tangent plane of the surface:<br />

95 vct_U = nullVector;<br />

// Set vector U for being not parallel to n.<br />

if( n.y == 0.0 && n.z == 0.0 )<br />

vct_U.y = 1.0; // n is multiple of (1,0,0)<br />

100 else


vct_U.x = 1.0; // n is not multiple of (1,0,0)<br />

// Use cross product of n and vector U to<br />

// set vector U perpendicular to n.<br />

105 crossProduct( n, vct_U, vct_U );<br />

110<br />

// Determine cross product of n and vector U to get a vector V<br />

// perpendicular to n and vector U.<br />

crossProduct( n, vct_U, vct_V );<br />

// normalize vectors U and V<br />

normalizeVector( vct_U, vct_U );<br />

normalizeVector( vct_V, vct_V );<br />

115 /* Get slope from bump functions ...<br />

*/<br />

slope = nullVector;<br />

// get texture specific slope<br />

getTextureSlope( &slope );<br />

120 // get caused by bump map<br />

if( bumpMap != NULL )<br />

getMapSlope( &slope );<br />

// get slope caused by turbulence<br />

if( turbulence.frequencies > 0 && turbulence.size != 0.0 )<br />

125 getNoiseSlope( &slope );<br />

130<br />

// get quota of slope in directions u and v<br />

dotProduct( vct_U, slope, size_U );<br />

dotProduct( vct_V, slope, size_V );<br />

scaleVector( vct_U, size_U * bumpSize, vct_U );<br />

scaleVector( vct_V, size_V * bumpSize, vct_V );<br />

// tilt normal by sum of u and v<br />

135 addVector( vct_U, vct_V, vct_D );<br />

addVector( n, vct_D, n );<br />

normalizeVector( n, n );<br />

// transform new normal by inverse(!) matrix from texture system<br />

140 transformNormal( toTexture, n, bumpNormal );<br />

normalizeVector( *bumpNormal, *bumpNormal );<br />

}<br />

/* Determine slope in normal point mapped to bumpmap<br />

145 */<br />

//***********************************************<br />

void BumpTexture::getMapSlope( Vector *slope )<br />

//***********************************************<br />

{<br />

150 int pxlInt[2];<br />

double pxlFrct[2];<br />

Point pxlCoord;<br />

Color pxlColor;<br />

double grayVal[2][2];<br />

155 double dx, dy;<br />

Vector mapDir[2];<br />

Vector noisePoint;<br />

Vector noiseGradient;<br />

160 // get pixel coordinate<br />

mapper.mapPoint( normalHitPoint, &pxlCoord );<br />

pxlCoord.x *= bumpMap−>getDim( X ) − 1;<br />

pxlCoord.y *= bumpMap−>getDim( Y ) − 1;<br />

165 // split pixel coordinate into integer and fractional part<br />

pxlInt[X] = (int)pxlCoord.x;<br />

pxlFrct[X] = pxlCoord.x − pxlInt[X];<br />

pxlInt[Y] = (int)pxlCoord.y;<br />

pxlFrct[Y] = pxlCoord.y − pxlInt[Y];<br />

170<br />

// get colors of 2x2 pixel matrix and convert them into gray scale<br />

bumpMap−>getPixelColor( &pxlColor, pxlInt[X], pxlInt[Y] );<br />

grayVal[0][0] = grayValue( pxlColor );<br />

bumpMap−>getPixelColor( &pxlColor, pxlInt[X], pxlInt[Y] + 1 );<br />

175 grayVal[0][1] = grayValue( pxlColor );<br />

bumpMap−>getPixelColor( &pxlColor, pxlInt[X] + 1, pxlInt[Y] );<br />

grayVal[1][0] = grayValue( pxlColor );<br />

bumpMap−>getPixelColor( &pxlColor, pxlInt[X] + 1, pxlInt[Y] + 1 );<br />

grayVal[1][1] = grayValue( pxlColor );<br />

180<br />

// get difference in gray values<br />

dy = blend( grayVal[1][0], grayVal[1][1], pxlFrct[X] );<br />

dy −= blend( grayVal[0][0], grayVal[0][1], pxlFrct[X] );<br />

185 dx = blend( grayVal[0][1], grayVal[1][1], pxlFrct[Y] );<br />

dx −= blend( grayVal[0][0], grayVal[1][0], pxlFrct[Y] );<br />

190<br />

// get mapping directions<br />

mapper.getMapDirections( normalHitPoint, &mapDir[X], &mapDir[Y] );<br />

// scale directions<br />

scaleVector( mapDir[X], dx, mapDir[X] );<br />

scaleVector( mapDir[Y], dy, mapDir[Y] );<br />

195 addVector( mapDir[X], *slope, *slope );<br />

addVector( mapDir[Y], *slope, *slope );<br />

}


#ifndef __BITMAPTEXTURE_H<br />

#define __BITMAPTEXTURE_H<br />

#include "BumpTexture.h"<br />

5 #include "PointMapper.h"<br />

#include "Types.h"<br />

10<br />

// predeclarations<br />

class TGAImage;<br />

/* The BitmapTexture class<br />

*/<br />

//*******************************************<br />

class BitmapTexture : public BumpTexture {<br />

15 //*******************************************<br />

public:<br />

typedef PointMapper::ParseInfo ParseInfo;<br />

BitmapTexture( const ParseInfo &data = PointMapper::defaultData );<br />

20 virtual ~BitmapTexture();<br />

// implement dimension info<br />

bool is2D() { return true; }<br />

25 // *** color calculations ***<br />

virtual void detectTextureData();<br />

protected:<br />

PointMapper mapper;<br />

30 TGAImage *bitmap;<br />

};<br />

#endif /* __BITMAPTEXTURE_H */


#include <br />

#include <br />

#include "shade.h"<br />

5 #include "utils.h"<br />

#include "Model.h"<br />

#include "Object.h"<br />

#include "Octree.h"<br />

10 /* Generate the direction vector for a new ray,<br />

* starting from the given view location.<br />

*/<br />

//**************************************************************************************<br />

void new<strong>Ray</strong>Direction( const SamplePoint &point, const View &view, Vector *direction )<br />

15 //**************************************************************************************<br />

{<br />

direction−>x = view.direction.x + (point.x − view.widthHalf) * view.right.x / view.widthHalf +<br />

(point.y − view.heightHalf) / view.heightHalf * view.up.x * −1;<br />

direction−>y = view.direction.y + (point.x − view.widthHalf) * view.right.y / view.widthHalf +<br />

20 (point.y − view.heightHalf) / view.heightHalf * view.up.y * −1;<br />

direction−>z = view.direction.z + (point.x − view.widthHalf) * view.right.z / view.widthHalf +<br />

(point.y − view.heightHalf) / view.heightHalf * view.up.z * −1;<br />

25 }<br />

normalizeVector( *direction, *direction );<br />

/* Tone down the aliasing of edges by tracing additional rays through pixels<br />

* with high color fluctuation.<br />

*/<br />

30 //*******************************************************************************<br />

void sample( Model *model, SamplePoint point[], Color *pixelColor, int level )<br />

//*******************************************************************************<br />

{<br />

<strong>Ray</strong> ray;<br />

35 Real colorDiff;<br />

SamplePoint extraPoint[5];<br />

Color extraColor;<br />

register unsigned int i;<br />

40 ray.origin = model−>scene.view.location;<br />

for( i = 2; i < 5; ++i )<br />

{<br />

new<strong>Ray</strong>Direction( point[i], model−>scene.view, &ray.direction );<br />

45 model−>trace( model, &ray, 1.0, 1 );<br />

point[i].color = ray.color;<br />

}<br />

50<br />

*pixelColor = black;<br />

for( i = 0; i < 4; ++i )<br />

{<br />

if( model−>scene.view.superSampling )<br />

{<br />

55 colorDiff = grayValue( subColor( point[i].color, point[4].color ) );<br />

if( (level < MAXSAMPLE) && (absVal( colorDiff ) > THRESHOLD) )<br />

{<br />

/**<br />

60 * Deterimine subpoints to shot additional rays<br />

*/<br />

extraPoint[0] = point[i];<br />

extraPoint[1] = point[4];<br />

extraPoint[2].x = point[i].x; extraPoint[2].y = point[4].y;<br />

65 extraPoint[3].x = point[4].x; extraPoint[3].y = point[i].y;<br />

extraPoint[4].x = (point[i].x + point[4].x) * 0.5;<br />

extraPoint[4].y = (point[i].y + point[4].y) * 0.5;<br />

// ... and sample again<br />

70 sample( model, extraPoint, &extraColor, level + 1 );<br />

75 }<br />

80<br />

}<br />

}<br />

}<br />

*pixelColor = addColor( *pixelColor, extraColor );<br />

continue;<br />

for( const ColorChannel *j = RGBfirst; *j != ALPHA; j++ )<br />

pixelColor−>channel[*j] += (point[i].color.channel[*j] + point[4].color.channel[*j]) * 0.5;<br />

*pixelColor = scaleColor( *pixelColor, 0.25 );<br />

/* Tests the origin of a ray for being in<br />

85 * the shadow of the light source hit by the<br />

* ray’s direction.<br />

*/<br />

//**************************************************************<br />

bool shadowTest( Model *model, const Light &light, <strong>Ray</strong> *ray )<br />

90 //**************************************************************<br />

{<br />

Real distance;<br />

Real cosAngle;<br />

ObjectHit hit;<br />

95<br />

// ray−>direction not normalized yet!<br />

distance = vectorLength( ray−>direction );<br />

// normalize direction<br />

100 ray−>direction.x /= distance;


5<br />

#include "BitmapTexture.h"<br />

#include "TGAImage.h"<br />

#include "utils.h"<br />

/* Constructor<br />

*/<br />

//********************************************************************<br />

BitmapTexture::BitmapTexture( const BitmapTexture::ParseInfo &data )<br />

10 //********************************************************************<br />

: BumpTexture(), mapper( data )<br />

{<br />

if( (bitmap = new TGAImage( data.filename )) != NULL )<br />

{<br />

15 bitmap−>setScale( X, data.scale.x );<br />

bitmap−>setScale( Y, data.scale.y );<br />

bitmap−>setScale( Z, data.scale.z );<br />

for( unsigned int i = X; i getScaledDim( (Coordinate)i ) );<br />

}<br />

else<br />

fprintf( stderr, "Unable to load bitmap file %s\n", data.filename );<br />

25 mapper.setSurfaceDimRef( &surfDimension[0] );<br />

mapper.setExternMappingRef( &externMapping[0] );<br />

}<br />

/* Destructor<br />

30 */<br />

//********************************<br />

BitmapTexture::~BitmapTexture()<br />

//********************************<br />

{<br />

35 if( bitmap != NULL ) delete bitmap;<br />

}<br />

/* Get the texture data according to the texture properties<br />

*/<br />

40 //****************************************<br />

void BitmapTexture::detectTextureData()<br />

//****************************************<br />

{<br />

Point bmp;<br />

45<br />

50<br />

}<br />

// map point to flat image<br />

mapper.mapPoint( colorHitPoint, &bmp );<br />

bmp.x *= bitmap−>getDim( X ) − 1;<br />

bmp.y *= bitmap−>getDim( Y ) − 1;<br />

bitmap−>getPixelColor( &baseColor, Ro<strong>und</strong>( bmp.x ), Ro<strong>und</strong>( bmp.y ) );


#ifndef __REFLECTIONTEXTURE_H<br />

#define __REFLECTIONTEXTURE_H<br />

#include "BitmapTexture.h"<br />

5 #include "Types.h"<br />

#include "utils.h"<br />

/* The ReflectionTexture class<br />

*/<br />

10 //*************************************************<br />

class ReflectionTexture : public BitmapTexture {<br />

//*************************************************<br />

public:<br />

typedef PointMapper::ParseInfo ParseInfo;<br />

15<br />

ReflectionTexture( const ParseInfo &data = PointMapper::defaultData )<br />

: BitmapTexture( data ) {}<br />

virtual ~ReflectionTexture() {}<br />

20 // Override methods inherited from class BitmapTexture<br />

bool needs<strong>Ray</strong>AndNormal() { return true; }<br />

void detectTextureData();<br />

};<br />

25 #endif /* __REFLECTIONTEXTURE_H */


5<br />

#include "ReflectionTexture.h"<br />

#include "TGAImage.h"<br />

#include "utils.h"<br />

/* Get the color according to the texture properties<br />

*/<br />

//********************************************<br />

void ReflectionTexture::detectTextureData()<br />

10 //********************************************<br />

{<br />

Real nv;<br />

Point bmp;<br />

Vector reflection;<br />

15<br />

20<br />

// determine reflection vector − need not to be normalized<br />

dotProduct( rayDirection, vertexNormal, nv );<br />

scaleVector( vertexNormal, (−2 * nv), reflection );<br />

addVector( reflection, rayDirection, reflection );<br />

/**<br />

* Use the reflection vector to determine a point<br />

* on a sphere with infinite radius surro<strong>und</strong>ing the object.<br />

* Apply texture transformations to surro<strong>und</strong>ing sphere first<br />

25 * by transforming the reflection vector.<br />

*/<br />

transformVector( toTexture, reflection, &reflection );<br />

mapper.mapPoint( reflection, &bmp );<br />

bmp.x *= bitmap−>getDim( X ) − 1;<br />

30 bmp.y *= bitmap−>getDim( Y ) − 1;<br />

}<br />

bitmap−>getPixelColor( &baseColor, Ro<strong>und</strong>( bmp.x ), Ro<strong>und</strong>( bmp.y ) );


#ifndef __PROCEDURALTEXTURE_H<br />

#define __PROCEDURALTEXTURE_H<br />

#include "BumpTexture.h"<br />

5 #include "Types.h"<br />

/* The ProceduralTexture class<br />

*/<br />

//***********************************************<br />

10 class ProceduralTexture : public BumpTexture {<br />

//***********************************************<br />

public:<br />

typedef struct {<br />

Color color;<br />

15 Material material;<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

20 static const ParseInfo defaultData;<br />

ProceduralTexture( const ParseInfo &data = defaultData );<br />

virtual ~ProceduralTexture() {}<br />

25 void setExtraColor( const Color &col ) { extColor = col; }<br />

void setExtraMaterial( const Material &mat ) { extMaterial = mat; }<br />

30<br />

35<br />

40<br />

};<br />

virtual const Color &getColor() { return *procColor[procIndex]; }<br />

virtual const Material &getMaterial() { return *procMaterial[procIndex]; }<br />

protected:<br />

Color *procColor[2];<br />

Material *procMaterial[2];<br />

unsigned int procIndex;<br />

private:<br />

Color extColor;<br />

Material extMaterial;<br />

#endif /* __PROCEDURALTEXTURE_H */


#include "ProceduralTexture.h"<br />

#include "utils.h"<br />

5 // initialize default data<br />

const ProceduralTexture::ParseInfo ProceduralTexture::defaultData = { black, defaultMaterial };<br />

/* Constructor<br />

*/<br />

10 //*********************************************************************************<br />

ProceduralTexture::ProceduralTexture( const ProceduralTexture::ParseInfo &data )<br />

//*********************************************************************************<br />

: BumpTexture()<br />

{<br />

15 procColor[0] = &baseColor;<br />

procColor[1] = &extColor;<br />

procMaterial[0] = &baseMaterial;<br />

procMaterial[1] = &extMaterial;<br />

procIndex = 0;<br />

20<br />

}<br />

extColor = data.color;<br />

extMaterial = data.material;


#ifndef __BRICKTEXTURE_H<br />

#define __BRICKTEXTURE_H<br />

#include "ProceduralTexture.h"<br />

5 #include "Types.h"<br />

/* The BrickTexture class<br />

*/<br />

//************************************************<br />

10 class BrickTexture : public ProceduralTexture {<br />

//************************************************<br />

public:<br />

typedef struct {<br />

ProceduralTexture::ParseInfo procInfo;<br />

15 Vector brickSize;<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

20 static const ParseInfo defaultData;<br />

BrickTexture( const ParseInfo &data = defaultData );<br />

~BrickTexture() {}<br />

25 void detectTextureData();<br />

30<br />

};<br />

private:<br />

Vector brickSize;<br />

#endif /* __BRICKTEXTURE_H */


5<br />

#include <br />

#include "BrickTexture.h"<br />

#include "utils.h"<br />

// initialize default data<br />

const BrickTexture::ParseInfo BrickTexture::defaultData = { ProceduralTexture::defaultData, oneVector };<br />

/* Constructor<br />

10 */<br />

//******************************************************************<br />

BrickTexture::BrickTexture( const BrickTexture::ParseInfo &data )<br />

//******************************************************************<br />

: ProceduralTexture( data.procInfo )<br />

15 { brickSize = data.brickSize;<br />

}<br />

/* Get texture data<br />

20 */<br />

//***************************************<br />

void BrickTexture::detectTextureData()<br />

//***************************************<br />

{<br />

25 int pos[3];<br />

unsigned int idx;<br />

pos[X] = (int)(colorHitPoint.x / brickSize.x);<br />

pos[Y] = (int)(colorHitPoint.y / brickSize.y);<br />

30 pos[Z] = (int)(colorHitPoint.z / brickSize.z);<br />

// prevent mirroring at origin by calc floor value<br />

// use RAYEPS to compensate ro<strong>und</strong>ing errors<br />

if( colorHitPoint.x < −RAYEPS ) pos[X]−−;<br />

35 if( colorHitPoint.y < −RAYEPS ) pos[Y]−−;<br />

if( colorHitPoint.z < −RAYEPS ) pos[Z]−−;<br />

40<br />

}<br />

procIndex = absVal(pos[X] + pos[Y] + pos[Z]) % 2;


#ifndef __CHECKERTEXTURE_H<br />

#define __CHECKERTEXTURE_H<br />

#include "ProceduralTexture.h"<br />

5 #include "Types.h"<br />

/* The CheckerTexture class<br />

*/<br />

//**************************************************<br />

10 class CheckerTexture : public ProceduralTexture {<br />

//**************************************************<br />

public:<br />

typedef struct {<br />

ProceduralTexture::ParseInfo procInfo;<br />

15 PointMapper::ParseInfo mapperInfo;<br />

Real checkerSize;<br />

Real quota[2];<br />

//*************<br />

} ParseInfo;<br />

20 //*************<br />

static const ParseInfo defaultData;<br />

CheckerTexture( const ParseInfo &data = defaultData );<br />

25 ~CheckerTexture() {}<br />

// implement dimension info<br />

bool is2D() { return true; }<br />

void getTextureSlope( Vector *slope )<br />

30 { addVector( *slope, textureSlope, *slope ); }<br />

void detectTextureData();<br />

private:<br />

35 Real checkerSize;<br />

Real quota[2];<br />

Real q10[2];<br />

PointMapper mapper;<br />

Vector textureSlope;<br />

40 };<br />

#endif /* __CHECKERTEXTURE_H */


#include "CheckerTexture.h"<br />

#include "utils.h"<br />

5 // initialize default data<br />

const CheckerTexture::ParseInfo CheckerTexture::defaultData = { ProceduralTexture::defaultData,<br />

PointMapper::defaultData, 1.0, {0.5, 0.5} };<br />

/* Constructor<br />

10 */<br />

//************************************************************************<br />

CheckerTexture::CheckerTexture( const CheckerTexture::ParseInfo &data )<br />

//************************************************************************<br />

: ProceduralTexture( data.procInfo ), mapper( data.mapperInfo )<br />

15 {<br />

checkerSize = data.checkerSize;<br />

quota[X] = data.quota[X];<br />

quota[Y] = data.quota[Y];<br />

q10[X] = quota[X]/10;<br />

20 q10[Y] = quota[Y]/10;<br />

mapper.setMappingLayout( PointMapper::LAYOUT_TILE );<br />

mapper.setMapDimension( X, 2.0 * checkerSize );<br />

mapper.setMapDimension( Y, 2.0 * checkerSize );<br />

mapper.setSurfaceDimRef( &surfDimension[0] );<br />

25 mapper.setExternMappingRef( &externMapping[0] );<br />

textureSlope = nullVector;<br />

}<br />

/* Detect texture data<br />

30 */<br />

//*****************************************<br />

void CheckerTexture::detectTextureData()<br />

//*****************************************<br />

{<br />

35 Point chk;<br />

Real dx, dy;<br />

Vector mapDir[2];<br />

// map point onto checker pattern<br />

40 mapper.mapPoint( colorHitPoint, &chk );<br />

procIndex = ((chk.x > quota[X]) ^ (chk.y > quota[Y]));<br />

// determine slope on bump mapping enabled<br />

if( isBumpyTexture )<br />

45 {<br />

// determine slope on primary color<br />

if( procIndex == 0 )<br />

{<br />

if( chk.x > quota[X] )<br />

50 {<br />

dx = (chk.x < quota[X] + 0.05)? 0.5 : (chk.x > 0.95)? −0.5 : 0;<br />

dy = (chk.y < quota[Y] + 0.05)? 0.5 : (chk.y > 0.95)? −0.5 : 0;<br />

}<br />

else<br />

55 {<br />

dx = (chk.x < 0.05)? 0.5 : (chk.x > quota[X] − 0.05)? −0.5 : 0;<br />

dy = (chk.y < 0.05)? 0.5 : (chk.y > quota[Y] − 0.05)? −0.5 : 0;<br />

}<br />

60 // get mapping directions<br />

mapper.getMapDirections( normalHitPoint, &mapDir[X], &mapDir[Y] );<br />

// scale directions<br />

scaleVector( mapDir[X], dx, mapDir[X] );<br />

65 scaleVector( mapDir[Y], dy, mapDir[Y] );<br />

addVector( mapDir[X], mapDir[Y], textureSlope );<br />

}<br />

else<br />

70 textureSlope = nullVector;<br />

}<br />

}


#ifndef __MARBLETEXTURE_H<br />

#define __MARBLETEXTURE_H<br />

#include "ProceduralTexture.h"<br />

5 #include "Noise.h"<br />

#include "Types.h"<br />

/* The MarbleTexture class<br />

*/<br />

10 //*************************************************<br />

class MarbleTexture : public ProceduralTexture {<br />

//*************************************************<br />

public:<br />

typedef struct {<br />

15 ProceduralTexture::ParseInfo procInfo;<br />

Noise::Turbulence turbulence;<br />

//*************<br />

} ParseInfo;<br />

//*************<br />

20<br />

25<br />

static const ParseInfo defaultData;<br />

MarbleTexture( const ParseInfo &data = defaultData );<br />

~MarbleTexture() {}<br />

void detectTextureData();<br />

const Color &getColor() { return marbleColor; }<br />

const Material &getMaterial() { return marbleMaterial; }<br />

30 protected:<br />

// Quadratic spline function generating tone wave<br />

Real toneSpline( Real x ) {<br />

if( x < −0.4 ) return ((3.95 * x + 5.94) * x + 2.24 );<br />

else if ( x


5<br />

#include <br />

#include "MarbleTexture.h"<br />

#include "utils.h"<br />

// initialize default data<br />

const MarbleTexture::ParseInfo MarbleTexture::defaultData = { ProceduralTexture::defaultData,<br />

Noise::defaultTurbulence };<br />

10 /* Constructor<br />

*/<br />

//*********************************************************************<br />

MarbleTexture::MarbleTexture( const MarbleTexture::ParseInfo &data )<br />

//*********************************************************************<br />

15 : ProceduralTexture( data.procInfo )<br />

{<br />

turbulence = data.turbulence;<br />

}<br />

20 /* Get texture data<br />

*/<br />

//****************************************<br />

void MarbleTexture::detectTextureData()<br />

//****************************************<br />

25 { Vector marblePoint;<br />

Real noiseValue;<br />

Real marbleValue;<br />

30 noiseValue = noise−>getFractalNoise( colorHitPoint, turbulence );<br />

marbleValue = toneSpline( sin( 2.0 * M_PI * colorHitPoint.x + noiseValue ) );<br />

// get point color by blending of procedural colors<br />

blendColor( *procColor[0], *procColor[1], marbleValue, &marbleColor );<br />

35 blendMaterial( *procMaterial[0], *procMaterial[1], marbleValue, &marbleMaterial );<br />

}


#ifndef __WOODTEXTURE_H<br />

#define __WOODTEXTURE_H<br />

#include "ProceduralTexture.h"<br />

5 #include "Noise.h"<br />

#include "Types.h"<br />

/* The WoodTexture class<br />

*/<br />

10 //*************************************************<br />

class WoodTexture : public ProceduralTexture {<br />

//*************************************************<br />

public:<br />

typedef struct {<br />

15 ProceduralTexture::ParseInfo procInfo;<br />

Noise::Turbulence turbulence;<br />

Real sharpness;<br />

//*************<br />

} ParseInfo;<br />

20 //*************<br />

static const ParseInfo defaultData;<br />

WoodTexture( const ParseInfo &data = defaultData );<br />

25 ~WoodTexture() {}<br />

30<br />

void detectTextureData();<br />

const Color &getColor() { return woodColor; }<br />

const Material &getMaterial() { return woodMaterial; }<br />

private:<br />

Noise::Turbulence turbulence;<br />

Real sharpness;<br />

Color woodColor;<br />

35 Material woodMaterial;<br />

};<br />

#endif /* __WOODTEXTURE_H */


5<br />

#include <br />

#include "WoodTexture.h"<br />

#include "utils.h"<br />

// initialize default data<br />

const WoodTexture::ParseInfo WoodTexture::defaultData = { ProceduralTexture::defaultData,<br />

Noise::defaultTurbulence, 1.0 };<br />

10 /* Constructor<br />

*/<br />

//***************************************************************<br />

WoodTexture::WoodTexture( const WoodTexture::ParseInfo &data )<br />

//***************************************************************<br />

15 : ProceduralTexture( data.procInfo )<br />

{<br />

turbulence = data.turbulence;<br />

sharpness = data.sharpness;<br />

}<br />

20<br />

/* Get texture data<br />

*/<br />

//**************************************<br />

void WoodTexture::detectTextureData()<br />

25 //**************************************<br />

{<br />

Vector woodPoint;<br />

Vector noisePoint;<br />

Real woodValue;<br />

30 static Vector displacement = { 1.23, −5.42, 3.1409 };<br />

/* Consider ring turbulence<br />

*/<br />

// get fractal noise value for y−dimension<br />

35 noisePoint = woodPoint = colorHitPoint;<br />

woodPoint.y += noise−>getFractalNoise( noisePoint, turbulence );<br />

// add displacement and get fractal noise value for z−dimension<br />

addVector( noisePoint, displacement, noisePoint );<br />

40 woodPoint.z += noise−>getFractalNoise( noisePoint, turbulence );<br />

/* Generate annual rings of tree<br />

*/<br />

// determine distance to object’s origin −> circles aro<strong>und</strong> origin<br />

45 woodValue = sqrt( woodPoint.y * woodPoint.y + woodPoint.z * woodPoint.z );<br />

// only fractional part matters<br />

woodValue = woodValue − floorVal( woodValue );<br />

50 // consider sharpness of rings<br />

woodValue = pow( woodValue, sharpness );<br />

// do final blending ...<br />

blendColor( *procColor[0], *procColor[1], woodValue, &woodColor );<br />

55 blendMaterial( *procMaterial[0], *procMaterial[1], woodValue, &woodMaterial );<br />

}


5<br />

#ifndef __IMAGE_H<br />

#define __IMAGE_H<br />

#include "Types.h"<br />

/* The Pixel structure<br />

*/<br />

typedef union {<br />

unsigned int pixel;<br />

10 unsigned char channel[4];<br />

//*********<br />

} Pixel;<br />

//*********<br />

15 /* The Image class<br />

*/<br />

//**************<br />

class Image {<br />

//**************<br />

20 public:<br />

Image( const char *name, unsigned int width, unsigned int height );<br />

virtual ~Image();<br />

25<br />

virtual void setPixelColor( const Color &color, unsigned int x_pos, unsigned int y_pos ) = 0;<br />

const char *getName() { return name; }<br />

unsigned int getDim( Coordinate coord ) { return dimension[coord]; }<br />

protected:<br />

30 bool getPixelBuffer( unsigned int width, unsigned int height );<br />

35 };<br />

char name[256];<br />

unsigned int dimension[3];<br />

Pixel *buffer;<br />

#endif /* __IMAGE_H */


#include <br />

#include "Image.h"<br />

5 /* Constructor<br />

*/<br />

//***********************************************************************************<br />

Image::Image( const char *imgName, unsigned int imgWidth, unsigned int imgHeight )<br />

//***********************************************************************************<br />

10 { name[255] = ’\0’;<br />

strncpy( name, imgName, 255 );<br />

dimension[X] = imgWidth;<br />

dimension[Y] = imgHeight;<br />

15 buffer = NULL;<br />

}<br />

getPixelBuffer( imgWidth, imgHeight );<br />

20 /* Destructor<br />

*/<br />

//****************<br />

Image::~Image()<br />

//****************<br />

25 { if( buffer != NULL ) delete[] buffer;<br />

}<br />

/* Try to get memory for pixel buffer<br />

30 */<br />

//****************************************************************************<br />

bool Image::getPixelBuffer( unsigned int imgWidth, unsigned int imgHeight )<br />

//****************************************************************************<br />

{<br />

35 if( buffer != NULL )<br />

{<br />

delete[] buffer;<br />

buffer = NULL;<br />

}<br />

40<br />

45 }<br />

if( imgWidth != 0 && imgHeight != 0 )<br />

buffer = new Pixel[imgWidth * imgHeight];<br />

return (buffer != NULL);


5<br />

#ifndef __TGAIMAGE_H<br />

#define __TGAIMAGE_H<br />

#include <br />

#include "Image.h"<br />

#include "Types.h"<br />

#include "utils.h"<br />

10 /* Definitions for image types. */<br />

#define TGA_Null 0<br />

#define TGA_Map 1<br />

#define TGA_RGB 2<br />

#define TGA_Mono 3<br />

15 #define TGA_RLEMap 9<br />

#define TGA_RLERGB 10<br />

#define TGA_RLEMono 11<br />

#define TGA_CompMap 32<br />

#define TGA_CompMap4 33<br />

20<br />

25<br />

/* Definitions for interleave flag. */<br />

#define TGA_IL_None 0<br />

#define TGA_IL_Two 1<br />

#define TGA_IL_Four 2<br />

#define MAXCOLORS 16384<br />

#define MAXMAXVAL 1023<br />

/* TGA pixel<br />

30 */<br />

//**************************<br />

typedef Byte TGAPixel[3];<br />

//**************************<br />

35 /* Targa header definition<br />

*/<br />

typedef struct {<br />

Byte idLength; // Lenght of ID field<br />

Byte colMapType; // 0=no palette, 1=palette<br />

40 Byte imgType; // 0=none, 1−3=uncompressed, 9−11= compressed<br />

Byte colMapStart[2]; // Offset to first entry of colormap<br />

Byte colMapLength[2]; // Number of colormap entries<br />

Byte colMapDepth; // Bits per colormap entry [8, 15, 16, 24, 32]<br />

Byte offsetX[2]; // Image origin X−axis (lo hi)<br />

45 Byte offsetY[2]; // Image origin Y−axis (lo hi)<br />

Byte width[2]; // Image width in pixels (lo hi)<br />

Byte height[2]; // Image height in pixels (lo hi)<br />

Byte pixelDepth; // Bits per pixel [8, 16, 24, 32]<br />

50 union {<br />

Byte imgDescriptor;<br />

struct {<br />

Byte attBits:4, // 4 bits, number of attribute bits per pixel<br />

55 rsrvd:1, // 1 bit, reserved<br />

orgBit:1, // 1 bit, origin: 0=lower left, 1=upper left<br />

intrLve:2; // 2 bits, interleaving flag<br />

} descriptorBits;<br />

};<br />

60 //*************<br />

} TGAHeader;<br />

//*************<br />

/* The TGAImage class<br />

65 */<br />

//********************************<br />

class TGAImage : public Image {<br />

//********************************<br />

public:<br />

70 TGAImage( const char *tgaName, unsigned int tgaWidth = 0, unsigned int tgaHeight = 0 );<br />

~TGAImage();<br />

75<br />

Real getScaledDim( Coordinate coord )<br />

{ return (dimension[coord] * scale[coord]); }<br />

void setScale( Coordinate coord, Real fact )<br />

{ scale[coord] = fact; }<br />

Real getScale( Coordinate coord )<br />

80 { return scale[coord]; }<br />

85<br />

// set or get pixel color of bitmap<br />

void setPixelColor( const Color &color, unsigned int x_pos, unsigned int y_pos );<br />

void getPixelColor( Color *color, unsigned int x_pos, unsigned int y_pos );<br />

// set or get pixel color of bitmap considering scaling<br />

void setColor( const Color &color, Real x, Real y )<br />

{ setPixelColor( color, Ro<strong>und</strong>(x / scale[X]), Ro<strong>und</strong>(y / scale[Y]) ); }<br />

90 void getColor( Color *color, Real x, Real y )<br />

{ getPixelColor( color, Ro<strong>und</strong>(x / scale[X]), Ro<strong>und</strong>(y / scale[Y]) ); }<br />

95<br />

void loadTGA();<br />

void saveTGA24();<br />

private:<br />

void readHeader();<br />

Byte readByte();<br />

void getMapEntry( Pixel *pxl );<br />

100 void readPixel( Pixel *pxl );


FILE *openWriteFile();<br />

void writeHeader( char *imageID );<br />

void writePixel( Pixel &pxl );<br />

105 void computeRunlengths( int cols, Pixel *pxlrow, int *runlength );<br />

bool pixelEquals( Pixel &pxl1, Pixel &pxl2 );<br />

FILE *imgFile;<br />

110 Pixel *colorMap;<br />

TGAHeader header;<br />

char imageID[256];<br />

Real scale[3];<br />

115 int rle_count;<br />

int rle_flag;<br />

bool isRLEencoded;<br />

bool isMapped;<br />

bool isLoaded;<br />

120 };<br />

#endif /* __TGAIMAGE_H */


5<br />

#include <br />

#include "TGAImage.h"<br />

#include "utils.h"<br />

/* Constructor<br />

*/<br />

//*****************************************************************************************<br />

TGAImage::TGAImage( const char *tgaName, unsigned int tgaWidth, unsigned int tgaHeight )<br />

10 //*****************************************************************************************<br />

: Image( tgaName, tgaWidth, tgaHeight )<br />

{<br />

isLoaded = false;<br />

scale[X] =<br />

15 scale[Y] =<br />

scale[Z] = 1.0;<br />

20 }<br />

if( tgaWidth == 0 || tgaHeight == 0 )<br />

loadTGA();<br />

/* Destructor<br />

*/<br />

//**********************<br />

25 TGAImage::~TGAImage()<br />

//**********************<br />

{<br />

if( buffer != NULL )<br />

{<br />

30 if( !isLoaded )<br />

saveTGA24();<br />

}<br />

}<br />

35 /* Set pixel color<br />

*/<br />

//*******************************************************************************************<br />

void TGAImage::setPixelColor( const Color &color, unsigned int x_pos, unsigned int y_pos )<br />

//*******************************************************************************************<br />

40 {<br />

if( buffer == NULL ) return;<br />

if( x_pos < dimension[X] && y_pos < dimension[Y] )<br />

{<br />

45 Real rgbVal;<br />

unsigned int idx = y_pos * dimension[X] + x_pos;<br />

// set channel data<br />

50 for( const ColorChannel *i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

rgbVal = color.channel[*i] * 255.0;<br />

buffer[idx].channel[*i] = (rgbVal > 255.0)? 255 : Ro<strong>und</strong>( rgbVal );<br />

}<br />

55 }<br />

}<br />

/* Get pixel color<br />

*/<br />

60 //*************************************************************************************<br />

void TGAImage::getPixelColor( Color *color, unsigned int x_pos, unsigned int y_pos )<br />

//*************************************************************************************<br />

{<br />

if( buffer != NULL )<br />

65 {<br />

unsigned int idx = (y_pos % dimension[Y]) * dimension[X] + x_pos % dimension[X];<br />

70 }<br />

}<br />

for( const ColorChannel *i = RGBfirst; *i != ALPHA; i++ )<br />

color−>channel[*i] = buffer[idx].channel[*i] / 255.0;<br />

/* Load a tga image file<br />

*/<br />

75 //*************************<br />

void TGAImage::loadTGA()<br />

//*************************<br />

{<br />

int i;<br />

80 unsigned int temp1, temp2;<br />

int rows, cols, row, col, realrow, truerow, baserow;<br />

int maxval;<br />

rle_count = rle_flag =0;<br />

85 colorMap = NULL;<br />

if( (imgFile = fopen( name, "rb" )) == NULL )<br />

{<br />

fprintf( stderr, "Can’t open file %s\n", name );<br />

90 return;<br />

}<br />

// read the Targa file header<br />

readHeader();<br />

95 dimension[Y] = rows = ( (int)header.height[0] ) + ( (int)header.height[1] ) * 256;<br />

dimension[X] = cols = ( (int)header.width[0] ) + ( (int)header.width[1] ) * 256;<br />

switch( header.imgType )<br />

{<br />

100 case TGA_Map:


case TGA_RGB:<br />

case TGA_Mono:<br />

case TGA_RLEMap:<br />

case TGA_RLERGB:<br />

105 case TGA_RLEMono:<br />

break;<br />

default: // error: unknown format<br />

fprintf( stderr, "unknown image format!\n" );<br />

110 goto closeReadFile;<br />

}<br />

if( header.imgType == TGA_Map ||<br />

header.imgType == TGA_RLEMap ||<br />

115 header.imgType == TGA_CompMap ||<br />

header.imgType == TGA_CompMap4<br />

)<br />

{ // color−mapped image<br />

if( header.colMapType != 1 )<br />

120 {<br />

// error: invalid map type<br />

fprintf( stderr, "invalid map type!\n" );<br />

goto closeReadFile;<br />

}<br />

125<br />

isMapped = true;<br />

// figure maxval from colMapDepth<br />

switch ( header.colMapDepth )<br />

130 {<br />

case 8:<br />

case 24:<br />

case 32:<br />

maxval = 255;<br />

135 break;<br />

case 15:<br />

case 16:<br />

maxval = 31;<br />

140 break;<br />

145 }<br />

default: // error: unkown pixelsize of colormap<br />

fprintf( stderr, "unknown pixelsize of colormap!\n" );<br />

goto closeReadFile;<br />

if( maxval > MAXMAXVAL )<br />

{<br />

// error: colormap depth too large<br />

150 fprintf( stderr, "depth of colormap too large!\n" );<br />

goto closeReadFile;<br />

}<br />

}<br />

else<br />

155 { // no colormap<br />

isMapped = false;<br />

// figure maxval from pixel depth<br />

switch( header.pixelDepth )<br />

160 {<br />

case 8:<br />

case 24:<br />

case 32:<br />

maxval = 255;<br />

165 break;<br />

case 15:<br />

case 16:<br />

maxval = 31;<br />

170 break;<br />

175 }<br />

default: // error: unknown pixel depth<br />

fprintf( stderr, "unknown pixel depth!\n" );<br />

goto closeReadFile;<br />

if( maxval > MAXMAXVAL )<br />

{<br />

// error: pixel depth too large<br />

180 fprintf( stderr, "pixel depth too large!\n" );<br />

goto closeReadFile;<br />

}<br />

}<br />

185 // read colormap info if required<br />

if( header.colMapType != 0 )<br />

{<br />

if( (colorMap = new Pixel[MAXCOLORS]) == NULL )<br />

{<br />

190 // error: can’t get colormap<br />

fprintf( stderr, "can’t get colormap!\n" );<br />

goto closeReadFile;<br />

}<br />

195 temp1 = header.colMapStart[0] + header.colMapStart[1] * 256;<br />

temp2 = header.colMapLength[0] + header.colMapLength[1] * 256;<br />

if( (temp1 + temp2 + 1) >= MAXCOLORS )<br />

{<br />

200 // error: too many colors


}<br />

fprintf( stderr, "too many colors!\n" );<br />

goto delColorMap;<br />

205 for( i = temp1; i < (int)(temp1 + temp2); ++i )<br />

getMapEntry( &colorMap[i] );<br />

}<br />

// check run−length encoding<br />

210 if( header.imgType == TGA_RLEMap ||<br />

header.imgType == TGA_RLERGB ||<br />

header.imgType == TGA_RLEMono<br />

)<br />

isRLEencoded = true;<br />

215 else<br />

isRLEencoded = false;<br />

// read the Targa file body and convert to portable format<br />

if( !getPixelBuffer( cols, rows ) )<br />

220 {<br />

// error: can’t get buffer<br />

fprintf( stderr, "can’t get buffer!\n" );<br />

goto delColorMap;<br />

}<br />

225<br />

truerow = 0;<br />

baserow = 0;<br />

for( row = 0; row < rows; ++row )<br />

230 {<br />

realrow = truerow;<br />

235<br />

if( header.descriptorBits.orgBit == 0 )<br />

realrow = rows − realrow − 1;<br />

for( col = 0; col < cols; ++col )<br />

readPixel( &buffer[realrow * cols + col] );<br />

if( header.descriptorBits.intrLve == TGA_IL_Four )<br />

240 truerow += 4;<br />

else if( header.descriptorBits.intrLve == TGA_IL_Two )<br />

truerow += 2;<br />

else<br />

++truerow;<br />

245<br />

}<br />

if( truerow >= rows )<br />

truerow = ++baserow;<br />

250 isLoaded = true;<br />

255<br />

260 }<br />

// clean up ...<br />

delColorMap:<br />

if( colorMap != NULL ) delete[] colorMap;<br />

closeReadFile:<br />

fclose( imgFile );<br />

return;<br />

/* Reades the header of a tga image file<br />

*/<br />

//****************************<br />

265 void TGAImage::readHeader()<br />

//****************************<br />

{<br />

header.idLength = readByte();<br />

header.colMapType = readByte();<br />

270 header.imgType = readByte();<br />

header.colMapStart[0] = readByte();<br />

header.colMapStart[1] = readByte();<br />

header.colMapLength[0] = readByte();<br />

header.colMapLength[1] = readByte();<br />

275 header.colMapDepth = readByte();<br />

header.offsetX[0] = readByte();<br />

header.offsetX[1] = readByte();<br />

header.offsetY[0] = readByte();<br />

header.offsetY[1] = readByte();<br />

280 header.width[0] = readByte();<br />

header.width[1] = readByte();<br />

header.height[0] = readByte();<br />

header.height[1] = readByte();<br />

header.pixelDepth = readByte();<br />

285 header.imgDescriptor = readByte();<br />

290<br />

}<br />

if( header.idLength )<br />

fread( imageID, 1, (int)header.idLength, imgFile );<br />

/* Reads a byte from the image file<br />

*/<br />

//**************************<br />

Byte TGAImage::readByte()<br />

295 //**************************<br />

{<br />

Byte b;<br />

if( fread( (char *)&b, 1, 1, imgFile ) != 1 )<br />

300 return 0;


}<br />

return b;<br />

/* Reads a pixel of the color map<br />

305 */<br />

//*****************************************<br />

void TGAImage::getMapEntry( Pixel *pxl )<br />

//*****************************************<br />

{<br />

310 Byte j, k;<br />

Byte red, green, blue, alpha;<br />

// Read appropriate number of bytes, break into rgb & put in map<br />

switch ( header.colMapDepth )<br />

315 {<br />

case 8: // grey scale, read and triplicate<br />

red = green = blue = readByte();<br />

alpha = 0;<br />

break;<br />

320<br />

325<br />

case 16: // 5 bits each of red green and blue<br />

case 15: // watch for byte order<br />

j = readByte();<br />

k = readByte();<br />

red = ( k & 0x7C ) >> 2;<br />

green = ( ( k & 0x03 ) > 5 );<br />

blue = j & 0x1F;<br />

alpha = 0;<br />

330 break;<br />

case 32:<br />

case 24: // 8 bits each of blue green and red<br />

blue = readByte();<br />

335 green = readByte();<br />

red = readByte();<br />

if ( header.colMapDepth == 32 ) alpha = readByte();<br />

else alpha = 0;<br />

340 break;<br />

345<br />

350 }<br />

}<br />

default:<br />

return;<br />

pxl−>channel[RED] = red;<br />

pxl−>channel[GREEN] = green;<br />

pxl−>channel[BLUE] = blue;<br />

pxl−>channel[ALPHA] = alpha;<br />

/* Reads a pixel of the image body<br />

*/<br />

//***************************************<br />

355 void TGAImage::readPixel( Pixel *pxl )<br />

//***************************************<br />

{<br />

static unsigned int l;<br />

static Byte red, green, blue, alpha;<br />

360 Byte j, k;<br />

// check if run length encoded<br />

if( isRLEencoded )<br />

{<br />

365 if( rle_count == 0 )<br />

{ // have to restart run<br />

Byte i;<br />

i = readByte();<br />

370 rle_flag = ( i & 0x80 );<br />

if( rle_flag == 0 )<br />

// stream of unencoded pixels<br />

rle_count = i + 1;<br />

375 else<br />

// single pixel replicated<br />

rle_count = i − 127;<br />

// decrement count & get pixel<br />

380 −−rle_count;<br />

}<br />

else<br />

{ // have already read count & (at least) first pixel<br />

−−rle_count;<br />

385 if( rle_flag != 0 )<br />

// replicated pixels<br />

goto PixEncode;<br />

}<br />

}<br />

390<br />

// read appropriate number of bytes, break into RGB<br />

switch( header.pixelDepth )<br />

{<br />

case 8: // Grey scale, read and triplicate<br />

395 red = green = blue = l = readByte();<br />

alpha = 0;<br />

break;<br />

case 16: // 5 bits each of red green and blue<br />

400 case 15: // watch byte order


j = readByte();<br />

k = readByte();<br />

l = ( (unsigned int) k > 2;<br />

405 green = ( ( k & 0x03 ) > 5 );<br />

blue = j & 0x1F;<br />

alpha = 0;<br />

break;<br />

410 case 32:<br />

case 24: // 8 bits each of blue green and red<br />

blue = readByte();<br />

green = readByte();<br />

red = readByte();<br />

415<br />

l = 0;<br />

420 break;<br />

}<br />

if( header.pixelDepth == 32 ) alpha = readByte();<br />

else alpha = 0;<br />

default: return;<br />

425 PixEncode:<br />

if( isMapped )<br />

{<br />

pxl−>channel[RED] = colorMap[l].channel[RED];<br />

pxl−>channel[GREEN] = colorMap[l].channel[GREEN];<br />

430 pxl−>channel[BLUE] = colorMap[l].channel[BLUE];<br />

pxl−>channel[ALPHA] = colorMap[l].channel[ALPHA];<br />

}<br />

else<br />

{<br />

435 pxl−>channel[RED] = red;<br />

pxl−>channel[GREEN] = green;<br />

pxl−>channel[BLUE] = blue;<br />

pxl−>channel[ALPHA] = alpha;<br />

}<br />

440 }<br />

/* Saves the buffer to a tga image file<br />

*/<br />

//***************************<br />

445 void TGAImage::saveTGA24()<br />

//***************************<br />

{<br />

int rows = dimension[Y];<br />

int cols = dimension[X];<br />

450 int row, col, i, realrow;<br />

int *runlength;<br />

if( (imgFile = openWriteFile()) == NULL )<br />

{<br />

455 fprintf( stderr, "can’t open file %s for writing!\n", name );<br />

return;<br />

}<br />

if( (runlength = (int *)new int[cols]) == NULL )<br />

460 {<br />

fprintf( stderr, "can’t get runlength buffer!\n" );<br />

goto closeWriteFile;<br />

}<br />

465 // set header data ...<br />

header.colMapType = 0x00;<br />

header.imgType = TGA_RLERGB;<br />

header.colMapStart[0] =<br />

header.colMapStart[1] = 0x00;<br />

470 header.colMapLength[0] =<br />

header.colMapLength[1] = 0x00;<br />

header.colMapDepth = 0;<br />

header.offsetX[0] =<br />

header.offsetX[1] = 0x00;<br />

475 header.offsetY[0] =<br />

header.offsetY[1] = 0x00;<br />

header.width[0] = dimension[X] % 256;<br />

header.width[1] = (dimension[X] >> 8) % 256;<br />

header.height[0] = dimension[Y] % 256;<br />

480 header.height[1] = (dimension[Y] >> 8) % 256;<br />

header.pixelDepth = 24;<br />

header.descriptorBits.attBits = 0;<br />

header.descriptorBits.rsrvd = 0;<br />

485 header.descriptorBits.orgBit = 0;<br />

header.descriptorBits.intrLve = 0;<br />

490<br />

// write out the Targa header<br />

writeHeader( NULL );<br />

// write out pixels<br />

for( row = 0; row < rows; ++row )<br />

{<br />

realrow = row;<br />

495 if ( header.descriptorBits.orgBit == 0 )<br />

realrow = rows − realrow − 1;<br />

computeRunlengths( cols, &buffer[realrow * cols], runlength );<br />

500 for( col = 0; col < cols; )


{<br />

if( runlength[col] > 0 )<br />

{<br />

// set runlength<br />

505 fputc( 0x80 + runlength[col] − 1 , imgFile );<br />

// write pixel<br />

writePixel( buffer[realrow * cols + col] );<br />

col += runlength[col];<br />

}<br />

510 else if( runlength[col] < 0 )<br />

{<br />

// set runlength<br />

fputc( −runlength[col] − 1 , imgFile );<br />

// write pixels<br />

515 for ( i = 0; i < −runlength[col]; ++i )<br />

writePixel( buffer[realrow * cols + col + i] );<br />

col += −runlength[col];<br />

}<br />

520 else<br />

goto delRunlengthBuffer;<br />

}<br />

}<br />

525 // clean up ...<br />

delRunlengthBuffer:<br />

delete[] runlength;<br />

closeWriteFile:<br />

530 fclose( imgFile );<br />

}<br />

return;<br />

535 /* Open new image file<br />

*/<br />

//********************************<br />

FILE *TGAImage::openWriteFile()<br />

//********************************<br />

540 { char tgaFile[259];<br />

545<br />

}<br />

strcpy( tgaFile, name );<br />

strcat( tgaFile, ".tga" );<br />

return fopen( tgaFile, "wb" );<br />

/* Writes the header of a tga image file<br />

550 */<br />

//******************************************<br />

void TGAImage::writeHeader( char *imgID )<br />

//******************************************<br />

{<br />

555 int idLength;<br />

if( imgID != NULL )<br />

{<br />

idLength = strlen( imgID );<br />

560 if( idLength > 255 ) idLength = 255;<br />

}<br />

else idLength = 0;<br />

fputc( idLength, imgFile );<br />

565 fputc( header.colMapType, imgFile );<br />

fputc( header.imgType, imgFile );<br />

fputc( header.colMapStart[0], imgFile );<br />

fputc( header.colMapStart[1], imgFile );<br />

fputc( header.colMapLength[0], imgFile );<br />

570 fputc( header.colMapLength[1], imgFile );<br />

fputc( header.colMapDepth, imgFile );<br />

fputc( header.offsetX[0], imgFile );<br />

fputc( header.offsetX[1], imgFile );<br />

fputc( header.offsetY[0], imgFile );<br />

575 fputc( header.offsetY[1], imgFile );<br />

fputc( header.width[0], imgFile );<br />

fputc( header.width[1], imgFile );<br />

fputc( header.height[0], imgFile );<br />

fputc( header.height[1], imgFile );<br />

580 fputc( header.pixelDepth, imgFile );<br />

fputc( header.imgDescriptor, imgFile );<br />

585 }<br />

if( idLength )<br />

fwrite( imgID, 1, (int)idLength, imgFile );<br />

/* Writes a pixel to the image file − ignores alpha channel<br />

*/<br />

//****************************************<br />

590 void TGAImage::writePixel( Pixel &pxl )<br />

//****************************************<br />

{<br />

fputc( pxl.channel[BLUE], imgFile );<br />

fputc( pxl.channel[GREEN], imgFile );<br />

595 fputc( pxl.channel[RED], imgFile );<br />

}<br />

/* Computes the pixel runlengths of a pixelrow<br />

*/<br />

600 //******************************************************************************


605<br />

void TGAImage::computeRunlengths( int cols, Pixel *pixelrow, int *runlength )<br />

//******************************************************************************<br />

{<br />

int col, start;<br />

// initialize all run lengths to 0. (This is just an error check.)<br />

for( col = 0; col < cols; ++col )<br />

runlength[col] = 0;<br />

610 // find runs of identical pixels<br />

for( col = 0; col < cols; )<br />

{<br />

start = col;<br />

do {<br />

615 ++col;<br />

} while( col < cols &&<br />

col − start < 128 &&<br />

pixelEquals( pixelrow[col], pixelrow[start] )<br />

);<br />

620<br />

}<br />

runlength[start] = col − start;<br />

// now look for runs of length−1 runs, and turn them into negative runs.<br />

625 for( col = 0; col < cols; )<br />

{<br />

if( runlength[col] == 1 )<br />

{<br />

start = col;<br />

630 while( col < cols &&<br />

col − start < 128 &&<br />

runlength[col] == 1 )<br />

{<br />

runlength[col] = 0;<br />

635 ++col;<br />

}<br />

runlength[start] = − ( col − start );<br />

}<br />

640 else<br />

col += runlength[col];<br />

}<br />

}<br />

645 /* Compares two pixels − ignores alpha channel<br />

*/<br />

//*******************************************************<br />

bool TGAImage::pixelEquals( Pixel &pxl1, Pixel &pxl2 )<br />

//*******************************************************<br />

650 {<br />

return ( pxl1.channel[RED] == pxl2.channel[RED] &&<br />

pxl1.channel[GREEN] == pxl2.channel[GREEN] &&<br />

pxl1.channel[BLUE] == pxl2.channel[BLUE] );<br />

}<br />

655


#ifndef __GFXSCREEN_H<br />

#define __GFXSCREEN_H<br />

#include <br />

5 #include <br />

/* The GfxScreen class<br />

*/<br />

//******************<br />

10 class GfxScreen {<br />

//******************<br />

public:<br />

GfxScreen(){}<br />

virtual ~GfxScreen() {}<br />

15<br />

// implement method of GfxNDCBuffer interface<br />

double getAspectRatio() { return aspectRatio; }<br />

// get screen properties<br />

20 const XVisualInfo &getInfo() { return visualInfo; }<br />

void printResolution();<br />

void printVisualInfo();<br />

protected:<br />

25 void initScreen( Display *display );<br />

// GfxScreen’s properties<br />

XVisualInfo visualInfo;<br />

Colormap colorMap;<br />

30 Visual *visual;<br />

double aspectRatio;<br />

int screen;<br />

int depth;<br />

int x_res, y_res;<br />

35<br />

40<br />

};<br />

static const char *classStr[6];<br />

#endif /* __GFXSCREEN_H */


#include <br />

#include "GfxScreen.h"<br />

5 // initialize static<br />

const char *GfxScreen::classStr[] = { "StaticGray", "GrayScale", "StaticColor",<br />

"PseudoColor", "TrueColor", "DirectColor" };<br />

/* init screen<br />

10 */<br />

//***********************************************<br />

void GfxScreen::initScreen( Display *display )<br />

//***********************************************<br />

{<br />

15 int xMM, yMM;<br />

screen = DefaultScreen( display );<br />

visual = DefaultVisual( display, screen );<br />

colorMap = DefaultColormap( display, screen );<br />

20 depth = DefaultDepth( display, screen );<br />

XMatchVisualInfo( display, screen, depth, visual−>c_class, &visualInfo );<br />

x_res = XDisplayWidth( display, screen );<br />

25 y_res = XDisplayHeight( display, screen );<br />

xMM = XDisplayWidthMM( display, screen );<br />

yMM = XDisplayHeightMM( display, screen );<br />

// evaluate aspect ratio<br />

30 aspectRatio = ((double)yMM * x_res)/((double)y_res * xMM);<br />

}<br />

/* Print resolution<br />

*/<br />

35 //**********************************<br />

void GfxScreen::printResolution()<br />

//**********************************<br />

{<br />

fprintf( stdout, "Resolution: %d x %d\n", x_res, y_res );<br />

40 }<br />

/* Print visual info<br />

*/<br />

//**********************************<br />

45 void GfxScreen::printVisualInfo()<br />

//**********************************<br />

{<br />

fprintf( stdout, "%−15s%X\n", "VisualId:", visualInfo.visualid );<br />

fprintf( stdout, "%−15s%s\n", "Color class:", classStr[visualInfo.c_class] );<br />

50 fprintf( stdout, "%−15s%d\n", "Color depth:", visualInfo.depth );<br />

fprintf( stdout, "%−15s%X\n", "Red mask:", visualInfo.red_mask );<br />

fprintf( stdout, "%−15s%X\n", "Green mask:", visualInfo.green_mask );<br />

fprintf( stdout, "%−15s%X\n", "Blue mask:", visualInfo.blue_mask );<br />

fprintf( stdout, "%−15s%d\n", "Colormap Size:", visualInfo.colormap_size );<br />

55 fprintf( stdout, "%−15s%d\n", "Bits per RGB:", visualInfo.bits_per_rgb );<br />

}


#ifndef __GFXGRAPHWINDOW_H<br />

#define __GFXGRAPHWINDOW_H<br />

#include "Image.h"<br />

5 #include "GfxScreen.h"<br />

/* The GfxWindow class<br />

*/<br />

//*************************************************<br />

10 class GfxWindow : public Image, public GfxScreen<br />

//*************************************************<br />

{<br />

public:<br />

GfxWindow(unsigned int wdth, unsigned int hght, const char *windowName = "", const char *iconName = "")<br />

;<br />

15 virtual ~GfxWindow();<br />

inline int getWidth();<br />

inline int getHeight();<br />

20 /**<br />

* Implement method for Image interface<br />

*/<br />

void setPixelColor( const Color &color, unsigned int x_pos, unsigned int y_pos );<br />

25 /**<br />

* Get window properties<br />

*/<br />

Display *getDisplay() { return display; }<br />

Window getWindow() { return window; }<br />

30 GC getGC() { return gc; }<br />

/**<br />

* Window control<br />

*/<br />

35 inline void displayWindow();<br />

void resizeWindow( unsigned int width, unsigned int height )<br />

{ XResizeWindow( display, window, width, height ); }<br />

40 void redraw()<br />

{ XPutImage( display, window, gc, ximage, 0, 0, 0, 0, dimension[X], dimension[Y] ); }<br />

/**<br />

* Event handling<br />

45 */<br />

void registerCallback( unsigned long eventName, void(*func)(XEvent *) )<br />

{ callback[eventName] = func; }<br />

void enableEvent( unsigned long mask )<br />

50 { XSelectInput( display, window, (inputMask |= mask) ); }<br />

void disableEvent( unsigned long mask )<br />

{ XSelectInput( display, window, (inputMask &= ~mask) ); }<br />

55 void quitMainLoop()<br />

{ quitLoop = true; }<br />

inline void mainLoop();<br />

60 protected:<br />

// window properties<br />

Display *display;<br />

Window window;<br />

XImage *ximage;<br />

65 XSizeHints sizeHints;<br />

XWindowAttributes attribs;<br />

GC gc;<br />

unsigned long inputMask;<br />

bool quitLoop;<br />

70<br />

};<br />

// callback table<br />

void(*callback[LASTEvent])( XEvent * );<br />

75 #endif /* __GFXGRAPHWINDOW_H */


#include <br />

#include <br />

5 #include "GfxWindow.h"<br />

#include "utils.h"<br />

// Constructor<br />

//********************************************************************************************************<br />

10 GfxWindow::GfxWindow( unsigned int wdth, unsigned int hght, const char *winName, const char *iconName )<br />

//********************************************************************************************************<br />

: Image( winName, wdth, hght ), GfxScreen()<br />

{<br />

XColor color;<br />

15 Window root;<br />

unsigned long border;<br />

/* Open display and initialize screen ...<br />

*/<br />

20 if( (display = XOpenDisplay( NULL )) == NULL )<br />

{<br />

fprintf( stderr, "Can’t open display\n" );<br />

exit( 1 );<br />

}<br />

25 initScreen( display );<br />

root = RootWindow( display, getInfo().screen );<br />

border = WhitePixel( display, getInfo().screen );<br />

30 // create window<br />

window = XCreateSimpleWindow( display, root, 100, 100, wdth, hght, 5, border, 0 );<br />

// set size hints<br />

sizeHints.x = 100;<br />

35 sizeHints.y = 100;<br />

sizeHints.width = wdth;<br />

sizeHints.height = hght;<br />

sizeHints.flags = (PPosition | PSize);<br />

40 // register window<br />

XSetStandardProperties( display, window, winName, iconName, None, NULL, 0, &sizeHints );<br />

// create ximage<br />

ximage = XCreateImage( display, visual, depth, ZPixmap, 0, (char *)buffer, dimension[X], dimension[Y],<br />

45 32, dimension[X] * sizeof(Pixel) );<br />

if( ximage == NULL )<br />

{<br />

fprintf( stderr, "Can’t create XImage\n" );<br />

50 exit( 1 );<br />

}<br />

XInitImage( ximage );<br />

// create graphics context<br />

55 gc = XCreateGC( display, window, 0, NULL );<br />

// set default color<br />

color.red =<br />

color.green =<br />

60 color.blue = 0x0000;<br />

XSetForegro<strong>und</strong>( display, gc, color.pixel );<br />

XSetWindowBackgro<strong>und</strong>( display, window, color.pixel );<br />

65 // initialize callback table<br />

for( unsigned long i = 0; i < LASTEvent; ++i )<br />

callback[i] = NULL;<br />

// care for no events<br />

70 inputMask = NoEventMask;<br />

}<br />

// Destructor<br />

//************************<br />

75 GfxWindow::~GfxWindow()<br />

//************************<br />

{<br />

XUnmapWindow( display, window );<br />

XFreeGC( display, gc );<br />

80 XCloseDisplay( display );<br />

}<br />

/* Get width of window<br />

*/<br />

85 //**************************<br />

int GfxWindow::getWidth()<br />

//**************************<br />

{<br />

XGetWindowAttributes( display, window, &attribs );<br />

90 return attribs.width;<br />

}<br />

/* Get height of window<br />

*/<br />

95 //***************************<br />

int GfxWindow::getHeight()<br />

//***************************<br />

{<br />

XGetWindowAttributes( display, window, &attribs );<br />

100 return attribs.height;


}<br />

/**<br />

* Implement methods for Image interface<br />

105 */<br />

//********************************************************************************************<br />

void GfxWindow::setPixelColor( const Color &color, unsigned int x_pos, unsigned int y_pos )<br />

//********************************************************************************************<br />

{<br />

110 Real rgbVal, rdRgbVal, alphaVal;<br />

unsigned int idx;<br />

if( x_pos < dimension[X] && y_pos < dimension[Y] )<br />

{<br />

115 idx = y_pos * dimension[X] + x_pos;<br />

alphaVal = 0;<br />

// set channel data<br />

for( const ColorChannel *i = RGBfirst; *i != ALPHA; i++ )<br />

120 {<br />

rgbVal = color.channel[*i] * 0xFF;<br />

rdRgbVal = Ro<strong>und</strong>( rgbVal );<br />

alphaVal += rgbVal − rdRgbVal;<br />

buffer[idx].channel[*i] = (unsigned char)((rgbVal > 0xFF)? 0xFF : rdRgbVal);<br />

125 }<br />

130<br />

}<br />

}<br />

// use average of ro<strong>und</strong>ing error for alpha<br />

buffer[idx].channel[ALPHA] = Ro<strong>und</strong>( (alphaVal / 3) * 0xFF );<br />

if( x_pos == dimension[X]−1 )<br />

XPutImage( display, window, gc, ximage, 0, 0, 0, 0, dimension[X], dimension[Y] );<br />

135 /* display window<br />

*/<br />

//********************************<br />

void GfxWindow::displayWindow()<br />

//********************************<br />

140 {<br />

// show window<br />

XMapWindow( display, window );<br />

XFlush( display );<br />

}<br />

145<br />

/* The main loop<br />

*/<br />

//***************************<br />

void GfxWindow::mainLoop()<br />

150 //***************************<br />

{<br />

XEvent event;<br />

155<br />

quitLoop = false;<br />

while( !quitLoop )<br />

{<br />

XNextEvent( display, &event );<br />

if( callback[event.type] != NULL )<br />

160 callback[event.type]( &event );<br />

}<br />

}


#ifndef __OBJECT_H<br />

#define __OBJECT_H<br />

#include "PointMapper.h"<br />

5 #include "Texture.h"<br />

#include "Types.h"<br />

/* The Object class<br />

*/<br />

10 //***************<br />

class Object {<br />

//***************<br />

friend class Parser;<br />

15 public:<br />

Object();<br />

virtual ~Object();<br />

// *** texture setup ***<br />

20 virtual void matchMappingMode( PointMapper::MappingMode modes[] ) = 0;<br />

virtual void matchSurfaceDimension( Real dimension[] ) = 0;<br />

// *** get object’s properties ***<br />

const Color &getAmbience() { return ambience; }<br />

25 const Vector &getNormal() { return normal; }<br />

const Color &getColor() { return texture−>getColor(); }<br />

const Material &getMaterial() { return texture−>getMaterial(); }<br />

// *** object specific calculations ***<br />

30 virtual bool test( const Vector &min, const Vector &max );<br />

virtual bool intersect( const <strong>Ray</strong> &ray, Real *distance ) = 0;<br />

virtual void setHitData( const <strong>Ray</strong> &ray, const Point &hitPnt );<br />

protected:<br />

35 virtual void detectNormal( const Point &hitPoint ) = 0;<br />

// object’s properties<br />

Matrix toObject;<br />

Matrix toWorld;<br />

40 Color ambience;<br />

Texture *texture;<br />

// corners of octree box<br />

Vector minCorner;<br />

45 Vector maxCorner;<br />

50<br />

};<br />

// temporary member<br />

Vector normal;<br />

#endif /* __OBJECT_H */


5<br />

#include "Object.h"<br />

#include "BumpTexture.h"<br />

#include "utils.h"<br />

/* Constructor<br />

*/<br />

//*****************<br />

Object::Object()<br />

10 //*****************<br />

{<br />

// initialize transform matrices<br />

toObject =<br />

toWorld = identityMatrix;<br />

15<br />

20<br />

// initialize points for object’s<br />

// bo<strong>und</strong>ing box used by octree.<br />

minCorner = nullVector;<br />

maxCorner = oneVector;<br />

/**<br />

* Initialize object’s surface<br />

*/<br />

ambience = black;<br />

25 texture = NULL;<br />

}<br />

/* Destructor<br />

*/<br />

30 //******************<br />

Object::~Object()<br />

//******************<br />

{<br />

if( texture != NULL )<br />

35 {<br />

texture−>releaseHandle();<br />

40 }<br />

}<br />

if( !texture−>isReferenced() )<br />

delete texture;<br />

/* Prepair hit point to be used with object and texture calculations.<br />

*/<br />

45 //***************************************************************<br />

void Object::setHitData( const <strong>Ray</strong> &ray, const Point &hitPnt )<br />

//***************************************************************<br />

{<br />

Point hitPoint;<br />

50<br />

// transform point into object system<br />

transformPoint( toObject, hitPnt, &hitPoint );<br />

/**<br />

55 * Set hit data used by texture to<br />

* detect color, material and normal vector.<br />

*/<br />

texture−>setHitData( hitPoint, hitPoint );<br />

60 /**<br />

* Determine normal vector<br />

*/<br />

// detect object’s real normal vector<br />

detectNormal( hitPoint );<br />

65<br />

// tilt normal vector by bump mapping<br />

if( ((BumpTexture *)texture)−>isBumpy() )<br />

((BumpTexture *)texture)−>getBumpNormal( normal, &normal );<br />

70 // transform normal vector by inverse(!) matrix into world system<br />

transformNormal( toObject, normal, &normal );<br />

// ... and normalize again<br />

normalizeVector( normal, normal );<br />

75 /**<br />

* texture data detection (color, material)<br />

*/<br />

// set normal vector of world system<br />

// − may be needed by some textures (e.g. ReflectionTexture)<br />

80 texture−>set<strong>Ray</strong>AndNormal( ray, normal );<br />

85<br />

}<br />

// detect texture data<br />

texture−>detectTextureData();<br />

/* Tests the object for being inside a cube specified by<br />

* parameters min and max.<br />

*/<br />

//**********************************************************<br />

90 bool Object::test( const Vector &min, const Vector &max )<br />

//**********************************************************<br />

{<br />

Vertices vert;<br />

unsigned int i;<br />

95<br />

// get the vertices of the cube enclosing the object<br />

getVertices( minCorner, maxCorner, vert );<br />

// and transform them into the world coordinate system<br />

100 for( i = 0; i < 8; ++i )


105<br />

transformPoint( toWorld, vert[i], &vert[i] );<br />

// test the box enclosing the object for being inside the cell<br />

if( testBox( vert, min, max ) ) return true;<br />

// get the vertices of the cell<br />

getVertices( min, max, vert );<br />

// and transform them into the object’s coordinate system<br />

110 for( i = 0; i < 8; ++i )<br />

transformPoint( toObject, vert[i], &vert[i] );<br />

115 }<br />

// test cell for being inside the box<br />

return testBox( vert, minCorner, maxCorner );


#ifndef __BOX_H<br />

#define __BOX_H<br />

#include "Object.h"<br />

5 #include "Types.h"<br />

/* The Box class<br />

*/<br />

//****************************<br />

10 class Box : public Object {<br />

//****************************<br />

public:<br />

Box();<br />

~Box();<br />

15<br />

// *** texture setup ***<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

{ modes[X] = modes[Y] = PointMapper::MAP_LINEAR; }<br />

20 void matchSurfaceDimension( Real dim[] )<br />

{ dim[X] = dim[Y] = 1.0; };<br />

// *** box calculations ***<br />

void setHitData( const <strong>Ray</strong> &ray, const Point &hitPnt );<br />

25 bool intersect( const <strong>Ray</strong> &ray, Real *dist );<br />

private:<br />

typedef enum {<br />

FRONT, BACK,<br />

30 RIGHT, LEFT,<br />

BOTTOM, TOP<br />

//***********<br />

} BoxFace;<br />

//***********<br />

35<br />

40<br />

45<br />

};<br />

/* Theres no need for implementing the calculation of the normal vector.<br />

* setHitData() uses the default vector instead.<br />

*/<br />

void detectNormal( const Point &hitPoint ) {}<br />

static const Vector frontNormal;<br />

#endif /* __BOX_H */


#include "Box.h"<br />

#include "Texture.h"<br />

#include "BumpTexture.h"<br />

5 #include "utils.h"<br />

// initialize front normal vector<br />

const Vector Box::frontNormal = { 0.0, 0.0, −1.0 };<br />

10 /* Constructor<br />

*/<br />

//***********<br />

Box::Box()<br />

//***********<br />

15 : Object()<br />

{<br />

// define the box enclosing the box object<br />

minCorner = nullVector;<br />

maxCorner = oneVector;<br />

20 }<br />

/* Destructor<br />

*/<br />

25 //************<br />

Box::~Box()<br />

//************<br />

{<br />

}<br />

30<br />

/* Prepair hit point to be used with box and texture calculations.<br />

*/<br />

//*************************************************************<br />

35 void Box::setHitData( const <strong>Ray</strong> &ray, const Vector &hitPnt )<br />

//*************************************************************<br />

{<br />

Real swp;<br />

BoxFace hitFace;<br />

40 Point hitPoint, texPoint;<br />

// transform point into object system<br />

transformPoint( toObject, hitPnt, &hitPoint );<br />

45 /**<br />

* Determine box face hit by ray<br />

* and correct ro<strong>und</strong>ing errors<br />

*/<br />

if( fabs(hitPoint.x) < RAYEPS ) {<br />

50 hitPoint.x = 0.0;<br />

hitFace = LEFT;<br />

}<br />

else if( fabs(hitPoint.x − 1.0) < RAYEPS ) {<br />

hitPoint.x = 1.0;<br />

55 hitFace = RIGHT;<br />

}<br />

else if( fabs(hitPoint.y) < RAYEPS ) {<br />

hitPoint.y = 0.0;<br />

hitFace = BOTTOM;<br />

60 }<br />

else if( fabs(hitPoint.y − 1.0) < RAYEPS ) {<br />

hitPoint.y = 1.0;<br />

hitFace = TOP;<br />

}<br />

65 else if( fabs(hitPoint.z) < RAYEPS ) {<br />

hitPoint.z = 0.0;<br />

hitFace = FRONT;<br />

}<br />

else if( fabs(hitPoint.z − 1.0) < RAYEPS ) {<br />

70 hitPoint.z = 1.0;<br />

hitFace = BACK;<br />

}<br />

/**<br />

75 * Set hit data and surface dimension used by<br />

* texture to detect color and normal vector.<br />

*/<br />

/**<br />

* In Fact the box is a compo<strong>und</strong> object. Thus for flat texture mapping<br />

80 * the hit point has to be transformed uniformly to one face of the box.<br />

* Hence the hit point will be transformed to the front face here ...<br />

*/<br />

texPoint = hitPoint;<br />

85 if( hitFace != FRONT )<br />

{<br />

// move origin to center ...<br />

translateVector( texPoint, −0.5, texPoint );<br />

90 // rotate texture point to front face ...<br />

switch( hitFace )<br />

{<br />

case LEFT: swp = texPoint.x;<br />

texPoint.x = −texPoint.z;<br />

95 texPoint.z = swp;<br />

break;<br />

case RIGHT: swp = texPoint.x;<br />

texPoint.x = texPoint.z;<br />

100 texPoint.z = −swp;


eak;<br />

case BOTTOM: swp = texPoint.y;<br />

texPoint.y = −texPoint.z;<br />

105 texPoint.z = swp;<br />

break;<br />

case TOP: swp = texPoint.y;<br />

texPoint.y = texPoint.z;<br />

110 texPoint.z = −swp;<br />

break;<br />

115<br />

}<br />

case BACK: texPoint.x = −texPoint.x;<br />

texPoint.z = −texPoint.z;<br />

default: break;<br />

// ... and move back.<br />

120 translateVector( texPoint, 0.5, texPoint );<br />

}<br />

/**<br />

* Set hit data for color detection according to texture dimension.<br />

125 * Bump mapping is always done in 2D manner −> 3rd param (normal hit point)<br />

* 2D => use point transformed to front face<br />

* 3D => use real hit point<br />

*/<br />

texture−>setHitData( ((texture−>is2D())? texPoint : hitPoint), texPoint );<br />

130<br />

/**<br />

* Determine normal vector<br />

*/<br />

// for bump mapping assume normal vector of the front face.<br />

135 if( ((BumpTexture *)texture)−>isBumpy() )<br />

((BumpTexture *)texture)−>getBumpNormal( frontNormal, &normal );<br />

else<br />

normal = frontNormal;<br />

140 // rotate result normal to hit face.<br />

switch( hitFace )<br />

{<br />

case LEFT: swp = normal.x;<br />

normal.x = normal.z;<br />

145 normal.z = −swp;<br />

break;<br />

case RIGHT: swp = normal.x;<br />

normal.x = −normal.z;<br />

150 normal.z = swp;<br />

break;<br />

case BOTTOM: swp = normal.y;<br />

normal.y = normal.z;<br />

155 normal.z = −swp;<br />

break;<br />

case TOP: swp = normal.y;<br />

normal.y = −normal.z;<br />

160 normal.z = swp;<br />

break;<br />

165<br />

}<br />

case BACK: normal.x = −normal.x;<br />

normal.z = −normal.z;<br />

default: break;<br />

// transform normal by inverse matrix into world system<br />

170 transformNormal( toObject, normal, &normal );<br />

// ... and normalize again<br />

normalizeVector( normal, normal );<br />

/**<br />

175 * texture data detection (color, material)<br />

*/<br />

// set normal − may be needed by some textures<br />

texture−>set<strong>Ray</strong>AndNormal( ray, normal );<br />

texture−>detectTextureData();<br />

180 }<br />

/* Tests a ray for intersecting the box.<br />

*/<br />

185 //**************************************************<br />

bool Box::intersect( const <strong>Ray</strong> &ray, Real *dist )<br />

//**************************************************<br />

{<br />

Real t1, t2;<br />

190 Real distFar = DBL_MAX;<br />

bool hitsBox = true;<br />

<strong>Ray</strong> local;<br />

195<br />

*dist = −DBL_MAX;<br />

transformPoint( toObject, ray.origin, &local.origin );<br />

transformVector( toObject, ray.direction, &local.direction );<br />

if( local.direction.x == 0.0 )<br />

200 hitsBox = (local.origin.x >= 0.0 && local.origin.x


205<br />

else<br />

{<br />

t1 = −local.origin.x / local.direction.x;<br />

t2 = (1.0 − local.origin.x) / local.direction.x;<br />

if( t1 < t2 )<br />

{<br />

*dist = t1;<br />

distFar = t2;<br />

210 }<br />

else<br />

{<br />

*dist = t2;<br />

distFar = t1;<br />

215 }<br />

hitsBox = (distFar >= 0.0);<br />

}<br />

if( hitsBox )<br />

220 {<br />

if( local.direction.y == 0.0 )<br />

hitsBox = (local.origin.y >= 0.0 && local.origin.y *dist ) *dist = t1;<br />

if( t2 < distFar ) distFar = t2;<br />

}<br />

else<br />

{<br />

235 if( t2 > *dist ) *dist = t2;<br />

if( t1 < distFar ) distFar = t1;<br />

}<br />

hitsBox = (*dist = 0.0);<br />

}<br />

240 }<br />

if( hitsBox )<br />

{<br />

if( local.direction.z == 0.0 )<br />

245 hitsBox = (local.origin.z >= 0.0 && local.origin.z *dist ) *dist = t1;<br />

if( t2 < distFar ) distFar = t2;<br />

255 }<br />

else<br />

{<br />

if( t2 > *dist ) *dist = t2;<br />

if( t1 < distFar ) distFar = t1;<br />

260 }<br />

hitsBox = (*dist = 0.0);<br />

}<br />

}<br />

265 if( hitsBox && *dist RAYEPS);<br />

*dist = distFar;<br />

}<br />

270<br />

}<br />

return hitsBox;


#ifndef __CONE_H<br />

#define __CONE_H<br />

#include "Object.h"<br />

5 #include "Types.h"<br />

/* The Cone class<br />

*/<br />

//*****************************<br />

10 class Cone : public Object {<br />

//*****************************<br />

public:<br />

Cone( Real radSml, Real radLrg, Real hght );<br />

~Cone() {};<br />

15<br />

// *** setup cone’s texture ***<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

{<br />

modes[X] = PointMapper::MAP_SPHERIC;<br />

20 modes[Y] = PointMapper::MAP_LINEAR;<br />

}<br />

void matchSurfaceDimension( Real dim[] )<br />

{<br />

25 dim[X] = (radLarge + radSmall) * M_PI;<br />

dim[Y] = surfLen;<br />

}<br />

// *** cone calculations ***<br />

30 bool intersect( const <strong>Ray</strong> &ray, Real *dist );<br />

private:<br />

void detectNormal( const Point &hitPoint );<br />

35 Real radSmall;<br />

Real radLarge;<br />

Real height;<br />

Real length;<br />

Real base;<br />

40 Real surfLen;<br />

bool invNormal;<br />

};<br />

45<br />

#endif /* __CONE_H */


#include <br />

#include "Cone.h"<br />

#include "Texture.h"<br />

5 #include "utils.h"<br />

/* Constructor<br />

*/<br />

//**************************************************<br />

10 Cone::Cone( Real radSml, Real radLrg, Real hght )<br />

//**************************************************<br />

: Object()<br />

{<br />

Real radDiff = (radLrg − radSml);<br />

15<br />

radSmall = radSml;<br />

radLarge = radLrg;<br />

height = hght;<br />

length = height * radLarge / radDiff;<br />

20 base = radLarge * radLarge / (length * length);<br />

surfLen = sqrt( (radDiff * radDiff) / 4.0 + height * height );<br />

// define the box enclosing the cone<br />

minCorner.x = −radLarge;<br />

25 minCorner.y = 0.0;<br />

minCorner.z = −radLarge;<br />

maxCorner.x = radLarge;<br />

maxCorner.y = height;<br />

30 maxCorner.z = radLarge;<br />

}<br />

/* Tests a ray for intersecting the cone.<br />

*/<br />

35 //***************************************************<br />

bool Cone::intersect( const <strong>Ray</strong> &ray, Real *dist )<br />

//***************************************************<br />

{<br />

Vector a, v;<br />

40 Real n, b, c, d, y;<br />

transformPoint( toObject, ray.origin, &a );<br />

transformVector( toObject, ray.direction, &v );<br />

45 a.y −= length;<br />

n = v.x * v.x + v.z * v.z − base * v.y * v.y;<br />

if( n != 0.0 )<br />

{<br />

50 b = −a.x * v.x − a.z * v.z + base * a.y * v.y;<br />

c = a.x * a.x + a.z * a.z − base * a.y * a.y;<br />

d = b * b − c * n;<br />

if( d >= 0.0 )<br />

55 {<br />

d = sqrt( d );<br />

*dist = (b − d) / n;<br />

y = a.y + *dist * v.y;<br />

60 if( *dist > RAYEPS && y = −length )<br />

{<br />

invNormal = false;<br />

return true;<br />

}<br />

65<br />

*dist = (b + d) / n;<br />

y = a.y + *dist * v.y;<br />

if( *dist > RAYEPS && y = −length )<br />

70 {<br />

invNormal = true;<br />

return true;<br />

}<br />

}<br />

75 }<br />

return false;<br />

}<br />

/* Determines the normal vector in the point<br />

80 * specified by setHitPoint()<br />

*/<br />

//*************************************************<br />

void Cone::detectNormal( const Point &hitPoint )<br />

//*************************************************<br />

85 { normal = hitPoint;<br />

normal.y −= length;<br />

normal.y = − normal.y * base;<br />

normalizeVector( normal, normal );<br />

90 if( invNormal ) negateVector( normal, normal );<br />

}


#ifndef __CYLINDER_H<br />

#define __CYLINDER_H<br />

#include "Object.h"<br />

5 #include "Types.h"<br />

/* The Cylinder class<br />

*/<br />

//*********************************<br />

10 class Cylinder : public Object {<br />

//*********************************<br />

public:<br />

Cylinder();<br />

~Cylinder() {};<br />

15<br />

// *** setup cylinder’s texture ***<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

{<br />

modes[X] = PointMapper::MAP_SPHERIC;<br />

20 modes[Y] = PointMapper::MAP_LINEAR;<br />

}<br />

void matchSurfaceDimension( Real dim[] )<br />

{<br />

25 dim[X] = 2.0 * M_PI;<br />

dim[Y] = 1.0;<br />

}<br />

// *** cylinder calculations ***<br />

30 bool intersect( const <strong>Ray</strong> &ray, Real *dist );<br />

private:<br />

void detectNormal( const Point &hitPoint )<br />

{<br />

35 normal = hitPoint;<br />

normal.y = 0.0;<br />

if( invNormal )<br />

{<br />

normal.x = −normal.x;<br />

40 normal.z = −normal.z;<br />

}<br />

}<br />

45 };<br />

bool invNormal;<br />

#endif /* __CYLINDER_H */


#include <br />

#include "Cylinder.h"<br />

#include "Texture.h"<br />

5 #include "utils.h"<br />

/* Constructor<br />

*/<br />

//*********************<br />

10 Cylinder::Cylinder()<br />

//*********************<br />

: Object()<br />

{<br />

// define the cube enclosing the cylinder<br />

15 minCorner.x = −1.0;<br />

minCorner.y = 0.0;<br />

minCorner.z = −1.0;<br />

maxCorner = oneVector;<br />

}<br />

20<br />

/* Tests a ray for intersecting the cylinder.<br />

*/<br />

//*******************************************************<br />

bool Cylinder::intersect( const <strong>Ray</strong> &ray, Real *dist )<br />

25 //*******************************************************<br />

{<br />

Vector a, v;<br />

Real n, b, d, c, y;<br />

30 transformPoint( toObject, ray.origin, &a );<br />

transformVector( toObject, ray.direction, &v );<br />

n = v.x * v.x + v.z * v.z;<br />

if( n != 0.0 )<br />

35 {<br />

b = −a.x * v.x − a.z * v.z;<br />

c = a.x * a.x + a.z * a.z;<br />

d = b * b − (c − 1.0) * n;<br />

40 if( d >= 0.0 )<br />

{<br />

d = sqrt( d );<br />

*dist = (b − d) / n;<br />

y = a.y + *dist * v.y;<br />

45<br />

if( *dist > RAYEPS && y >= 0.0 && y RAYEPS && y >= 0.0 && y


#ifndef __PLANE_H<br />

#define __PLANE_H<br />

#include "Object.h"<br />

5 #include "Types.h"<br />

/* The Plane class<br />

*/<br />

//******************************<br />

10 class Plane : public Object {<br />

//******************************<br />

public:<br />

Plane();<br />

~Plane() {}<br />

15<br />

// *** texture setup ***<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

{ modes[X] = modes[Y] = PointMapper::MAP_LINEAR; }<br />

20 void matchSurfaceDimension( Real dim[] )<br />

{ dim[X] = dim[Y] = 100; }<br />

// *** plane calculations ***<br />

bool intersect( const <strong>Ray</strong> &ray, Real *real );<br />

25 bool test( const Vector &min, const Vector &max );<br />

30<br />

35<br />

};<br />

private:<br />

void detectNormal( const Point &hitPoint )<br />

{ normal = (invNormal)? inv_direction : direction; }<br />

bool invNormal;<br />

Vector direction;<br />

Vector inv_direction;<br />

#endif /* __PLANE_H */


#include <br />

#include "Plane.h"<br />

#include "BumpTexture.h"<br />

5 #include "utils.h"<br />

/* Constructor<br />

*/<br />

//***************<br />

10 Plane::Plane()<br />

//***************<br />

: Object()<br />

{<br />

direction = baseVector[Z];<br />

15<br />

20 }<br />

negateVector( direction, inv_direction );<br />

minCorner =<br />

maxCorner = nullVector;<br />

/* Tests a ray for intersecting the plane.<br />

*/<br />

//****************************************************<br />

25 bool Plane::intersect( const <strong>Ray</strong> &ray, Real *dist )<br />

//****************************************************<br />

{<br />

<strong>Ray</strong> local;<br />

Real a, b;<br />

30<br />

transformPoint( toObject, ray.origin, &local.origin );<br />

transformVector( toObject, ray.direction, &local.direction );<br />

if( local.direction.z != 0.0 )<br />

35 {<br />

*dist = −local.origin.z / local.direction.z;<br />

if( *dist > RAYEPS )<br />

{<br />

40 invNormal = (local.direction.z > 0.0);<br />

return true;<br />

}<br />

}<br />

return false;<br />

45 }<br />

/* Tests the plane for being inside a cube specified by<br />

* parameters min and max.<br />

*/<br />

50 //*********************************************************<br />

bool Plane::test( const Vector &min, const Vector &max )<br />

//*********************************************************<br />

{<br />

Vector localMin, localMax;<br />

55<br />

60 }<br />

transformPoint( toObject, min, &localMin );<br />

transformPoint( toObject, max, &localMax );<br />

return (localMin.z * localMax.z


#ifndef __SPHERE_H<br />

#define __SPHERE_H<br />

#include "Object.h"<br />

5 #include "Types.h"<br />

/* The Sphere class<br />

*/<br />

//*******************************<br />

10 class Sphere : public Object {<br />

//*******************************<br />

public:<br />

Sphere();<br />

~Sphere() {};<br />

15<br />

// *** texture setup ***<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

{ modes[X] = modes[Y] = PointMapper::MAP_SPHERIC; }<br />

20 void matchSurfaceDimension( Real dim[] )<br />

{<br />

dim[X] = 2.0 * M_PI;<br />

dim[Y] = M_PI;<br />

}<br />

25<br />

// *** sphere calculations ***<br />

bool intersect( const <strong>Ray</strong> &ray, Real *distance );<br />

protected:<br />

30 // determine normal at hit point<br />

void detectNormal( const Point &hitPoint ) { normal = hitPoint; }<br />

};<br />

35<br />

#endif /* __SPHERE_H */


#include <br />

#include "Sphere.h"<br />

#include "Texture.h"<br />

5 #include "utils.h"<br />

/* Constructor<br />

*/<br />

//*****************<br />

10 Sphere::Sphere()<br />

//*****************<br />

: Object()<br />

{<br />

translateVector( nullVector, −1, minCorner );<br />

15 translateVector( nullVector, 1, maxCorner );<br />

}<br />

/* Tests a ray for intersecting the sphere.<br />

*/<br />

20 //*****************************************************<br />

bool Sphere::intersect( const <strong>Ray</strong> &ray, Real *dist )<br />

//*****************************************************<br />

{<br />

<strong>Ray</strong> local;<br />

25 Real dd, od, oo, discr, d;<br />

transformPoint( toObject, ray.origin, &local.origin );<br />

transformVector( toObject, ray.direction, &local.direction );<br />

30 dotProduct( local.direction, local.direction, dd );<br />

dotProduct( local.origin, local.direction, od );<br />

dotProduct( local.origin, local.origin, oo );<br />

// calculate the discriminant<br />

35 discr = od * od − dd * (oo − 1.0);<br />

// check for real solutions<br />

if( discr >= 0.0 )<br />

{<br />

40 d = sqrt( discr );<br />

*dist = (−od − d) / dd;<br />

if( *dist RAYEPS ) return true;<br />

}<br />

else return true;<br />

}<br />

50 return false;<br />

}


#ifndef __FREEFORMSURFACE_H<br />

#define __FREEFORMSURFACE_H<br />

#include <br />

5 #include "Object.h"<br />

#include "Texture.h"<br />

#include "Types.h"<br />

#define SURFACE_MAX_DEGREE 8<br />

10 #define SURFACE_MAX_ITERATION 100<br />

#define SURFACE_MAX_DIVIDE INT_MAX<br />

/* The FreeformSurface class<br />

*/<br />

15 //****************************************<br />

class FreeformSurface : public Object {<br />

//****************************************<br />

public:<br />

// The Helper class<br />

20 // Used to access the second dimension of the<br />

// control point matrix by operator[].<br />

//******************<br />

class Helper {<br />

//******************<br />

25 friend class FreeformSurface;<br />

public:<br />

Point &operator[]( unsigned int idx_n ) { return points[idx_m * dim_n + idx_n]; }<br />

30 private:<br />

// Constructor<br />

Helper( unsigned int _m, unsigned int _n, Point *p ) : points(p), idx_m(_m), dim_n(_n) {}<br />

Point *points;<br />

35 unsigned int idx_m;<br />

unsigned int dim_n;<br />

};<br />

// Constructor<br />

40 FreeformSurface( unsigned int _m, unsigned int _n );<br />

virtual ~FreeformSurface();<br />

45<br />

// operator[] for contol point access.<br />

Helper operator[]( unsigned int idx_m ) { return Helper( idx_m, n+1, cntlPoints ); }<br />

/**<br />

* Implement methods of Object interface<br />

*/<br />

void matchMappingMode( PointMapper::MappingMode modes[] )<br />

50 { modes[X] = modes[Y] = PointMapper::MAP_EXTERN; }<br />

void matchSurfaceDimension( Real dim[] )<br />

{ dim[X] = dim[Y] = 1.0; }<br />

55 // override method of Object<br />

void setHitData( const <strong>Ray</strong> &ray, const Vector &hitPnt )<br />

{<br />

texture−>setExternMapping( X, hitU );<br />

texture−>setExternMapping( Y, hitV );<br />

60 // call general object calculations<br />

Object::setHitData( ray, hitPnt );<br />

}<br />

65<br />

bool intersect( const <strong>Ray</strong> &ray, Real *distance );<br />

/**<br />

* Search tree<br />

*/<br />

void buildSearchTree();<br />

70 int getDivisions() { return divisions; }<br />

// print out control point matrix<br />

void dumpControlPoints( FILE *fptr = stdout );<br />

75 protected:<br />

typedef enum {<br />

U_AXIS = 0, V_AXIS = 1<br />

//********<br />

} Axis;<br />

80 //********<br />

// expected calculation methods of surface<br />

virtual void surfacePoint( Real u, Real v, Point *pnt ) = 0;<br />

virtual void surfaceGradients( Real u, Real v, Vector *gu, Vector *gv ) = 0;<br />

85 virtual bool isFlat( Axis *divAxis ) = 0;<br />

virtual bool divide( Axis a, Real t, FreeformSurface **left, FreeformSurface **right ) = 0;<br />

// Implement method expected by Object<br />

void detectNormal( const Point &hitPoint )<br />

90 { /* normal vector already evaluated in intersect() */ }<br />

/**<br />

* FreeformSurface’s properties<br />

*/<br />

95 Point *cntlPoints; // control points<br />

unsigned int m, n; // degrees<br />

Interval surfaceDomain[2]; // domain of u and v<br />

Real hitU, hitV;<br />

100 // criteria for dividing the surface


static const Real curvatureLimit;<br />

static const Real lengthDiffLimit;<br />

static const Real aspectRatioLimit;<br />

105 private:<br />

typedef struct __TreeNode {<br />

Point baseVertex;<br />

Vector base[3];<br />

Vector normal[3];<br />

110 Interval domain[2];<br />

// precalculated values<br />

Interval baseNormalDomain[3];<br />

Real scaleU, scaleV;<br />

115 Vector *planeNormal;<br />

Real aspectRatio;<br />

struct __TreeNode *left;<br />

struct __TreeNode *right;<br />

120 struct __TreeNode *parent;<br />

//************<br />

} TreeNode;<br />

//************<br />

125 typedef struct {<br />

Real distance;<br />

Vector normal;<br />

Real hitU;<br />

Real hitV;<br />

130 //******************<br />

} SurfaceHitData;<br />

//******************<br />

/**<br />

135 * Search tree methods<br />

*/<br />

bool addToSearchTree( TreeNode **nodeHook, TreeNode *parent, Interval domain[] );<br />

void getBo<strong>und</strong>ingVolume( TreeNode *volumeNode, Interval alpha[] );<br />

void chopSearchTree( TreeNode *node );<br />

140<br />

// iteration<br />

bool doIteration( TreeNode &node, <strong>Ray</strong> &ray, Real t0, SurfaceHitData *hitData );<br />

TreeNode *searchTreeRoot;<br />

145 int divisions;<br />

150<br />

155 };<br />

// internal logging<br />

static int iterations;<br />

static int divCounter;<br />

#ifdef DEBUG_FULL<br />

static FILE *debugInfo;<br />

static int refCounter;<br />

#endif<br />

#endif /* __FREEFORMSURFACE_H */


#include "FreeformSurface.h"<br />

#include "utils.h"<br />

5 // initialize static members<br />

const Real FreeformSurface::curvatureLimit = 1.2; // [1.0,inf]<br />

const Real FreeformSurface::lengthDiffLimit = 0.06; // [0.0,inf]<br />

const Real FreeformSurface::aspectRatioLimit = 1.0/4.0; // 1/n −> n out of [1,inf[<br />

int FreeformSurface::divCounter = 0;<br />

10 int FreeformSurface::iterations = 0;<br />

#ifdef DEBUG_FULL<br />

FILE *FreeformSurface::debugInfo = NULL;<br />

int FreeformSurface::refCounter = 0;<br />

15 #endif<br />

/* Constructor<br />

*/<br />

//*********************************************************************<br />

20 FreeformSurface::FreeformSurface( unsigned int _m, unsigned int _n )<br />

//*********************************************************************<br />

: Object()<br />

{<br />

m = _m;<br />

25 n = _n;<br />

surfaceDomain[U_AXIS].lower =<br />

surfaceDomain[V_AXIS].lower = 0.0;<br />

surfaceDomain[U_AXIS].upper =<br />

surfaceDomain[V_AXIS].upper = 1.0;<br />

30<br />

// check for maximum degree<br />

if( m > SURFACE_MAX_DEGREE )<br />

fprintf( stderr, "m degree exceeds maximum! declining to %d\n", (m = SURFACE_MAX_DEGREE) );<br />

if( n > SURFACE_MAX_DEGREE )<br />

35 fprintf( stderr, "n degree exceeds maximum! declining to %d\n", (n = SURFACE_MAX_DEGREE) );<br />

cntlPoints = new Point[(m+1)*(n+1)];<br />

searchTreeRoot = NULL;<br />

40 divisions = 0;<br />

#ifdef DEBUG_FULL<br />

// open file for debug info<br />

if( debugInfo == NULL )<br />

45 {<br />

if( (debugInfo = fopen( "FreeformSurface.info", "a" )) == NULL )<br />

fprintf( stderr, "Can’t open info file!\n" );<br />

else refCounter++;<br />

}<br />

50 #endif<br />

}<br />

/* Destructor<br />

*/<br />

55 //************************************<br />

FreeformSurface::~FreeformSurface()<br />

//************************************<br />

{<br />

if( cntlPoints != NULL ) delete[] cntlPoints;<br />

60<br />

if( searchTreeRoot != NULL )<br />

chopSearchTree( searchTreeRoot );<br />

#ifdef DEBUG_FULL<br />

65 −−refCounter;<br />

if( refCounter == 0 )<br />

{<br />

fclose( debugInfo );<br />

debugInfo = NULL;<br />

70 }<br />

#endif<br />

}<br />

/* Search the tree for patches intersected by the ray<br />

75 */<br />

//******************************************************************<br />

bool FreeformSurface::intersect( const <strong>Ray</strong> &ray, Real *distance )<br />

//******************************************************************<br />

{<br />

80 Real dot_d_n, dot_v_n;<br />

<strong>Ray</strong> local;<br />

Interval t[3], itrsct;<br />

bool isHit;<br />

85 Vector baseDiff;<br />

Vector vctrTmp;<br />

SurfaceHitData hitData;<br />

// start at root<br />

90 TreeNode *current = searchTreeRoot;<br />

95<br />

// transform ray into object system<br />

transformPoint( toObject, ray.origin, &local.origin );<br />

transformVector( toObject, ray.direction, &local.direction );<br />

*distance = DBL_MAX;<br />

// scan the search tree for intersections<br />

while( true )<br />

100 {


subVector( current−>baseVertex, local.origin, baseDiff );<br />

// intersect box by ray<br />

for( int i = 0; i < 3; ++i )<br />

105 {<br />

dotProduct( local.direction, current−>normal[i], dot_d_n );<br />

if( dot_d_n == 0.0 ) dot_d_n = RAYEPS;<br />

dotProduct( baseDiff, current−>normal[i], dot_v_n );<br />

110 t[i].lower = (dot_v_n + current−>baseNormalDomain[i].lower) / dot_d_n;<br />

t[i].upper = (dot_v_n + current−>baseNormalDomain[i].upper) / dot_d_n;<br />

if( t[i].lower > t[i].upper )<br />

swap( t[i].lower, t[i].upper );<br />

115 }<br />

// if intersection is valid ...<br />

if( intersectInterval( t[0], t[1], itrsct ) && intersectInterval( itrsct, t[2], itrsct ) )<br />

{<br />

// if no subnodes exist ...<br />

120 if( current−>left == NULL )<br />

{<br />

/**<br />

* node is a leaf, thus try iteration.<br />

*/<br />

125 // Choose the starting point for iteration.<br />

// This is done by the criterion<br />

// tan(ray,normal) > aspectRatio<br />

// The tangent is evaluated by<br />

130 // sine(ray,normal) / cosine(ray,normal)<br />

// and thus by<br />

// abs( cross(ray,normal) ) / dot(ray,normal)<br />

crossProduct( local.direction, *(current−>planeNormal), vctrTmp );<br />

dotProduct( local.direction, *(current−>planeNormal), dot_d_n );<br />

135 dotProduct( baseDiff, *(current−>planeNormal), dot_v_n );<br />

isHit = false;<br />

if( absVal( dot_d_n ) > RAYEPS )<br />

{<br />

140 if( vectorLength( vctrTmp ) / absVal( dot_d_n ) < current−>aspectRatio )<br />

{<br />

// start iteration using intersection point<br />

isHit = doIteration( *current, local, (dot_v_n / dot_d_n), &hitData );<br />

}<br />

145 }<br />

/* Due to the convex hull property, an intersection can’t occure nearer than the<br />

* point of intersection with the parallelepiped. So if an intersection already<br />

* exists that is nearer than the intersection with the parallelepiped it is<br />

150 * waste of time to do the iteration.<br />

*/<br />

if( !isHit && *distance > itrsct.lower )<br />

if( (isHit = doIteration( *current, local, itrsct.lower, &hitData)) == false )<br />

isHit = doIteration( *current, local, itrsct.upper, &hitData );<br />

155<br />

if( isHit && hitData.distance > RAYEPS && hitData.distance < *distance )<br />

{<br />

*distance = hitData.distance;<br />

normal = hitData.normal;<br />

160 hitU = hitData.hitU;<br />

hitV = hitData.hitV;<br />

}<br />

}<br />

else<br />

165 {<br />

// it’s an inner node<br />

current = current−>left;<br />

continue;<br />

}<br />

170 }<br />

// go up while current is right child<br />

while( current != searchTreeRoot && current == current−>parent−>right )<br />

current = current−>parent;<br />

175 // jump to right child of parent<br />

if( current != searchTreeRoot )<br />

current = current−>parent−>right;<br />

else<br />

break;<br />

180 }<br />

185<br />

}<br />

return (*distance < DBL_MAX);<br />

/* Find the intersection point using the newton iteration algorithm<br />

*/<br />

//************************************************************************************************<br />

bool FreeformSurface::doIteration( TreeNode &node, <strong>Ray</strong> &ray, Real t0, SurfaceHitData *hitData )<br />

190 //************************************************************************************************<br />

{<br />

Point P0, P1;<br />

Vector diff, gu, gv, n0;<br />

Real u0, v0, deltaU, deltaV;<br />

195 Real dot_d_n, dot_diff_n;<br />

bool secondTry = false;<br />

bool u_exceeds = false, v_exceeds = false;<br />

int iCount;<br />

200 // determine start point


ayPoint( ray, t0, P0 );<br />

subVector( P0, node.baseVertex, diff );<br />

/**<br />

205 * Calculate start parameter u0, v0<br />

*/<br />

Real solution1[3];<br />

Real equSystem1[3][3] = { { node.base[0].x, node.base[1].x, node.base[2].x },<br />

{ node.base[0].y, node.base[1].y, node.base[2].y },<br />

210 { node.base[0].z, node.base[1].z, node.base[2].z } };<br />

215<br />

solution1[0] = diff.x;<br />

solution1[1] = diff.y;<br />

solution1[2] = diff.z;<br />

if( !solveEquationSystem( (Real *)equSystem1, solution1, 3 ) )<br />

return false;<br />

deltaU = solution1[0] * node.scaleU;<br />

220 deltaV = solution1[1] * node.scaleV;<br />

u0 = node.domain[U_AXIS].lower + deltaU;<br />

v0 = node.domain[V_AXIS].lower + deltaV;<br />

225 /**<br />

* Iteration loop<br />

*/<br />

for( iCount = 0; iCount < SURFACE_MAX_ITERATION; ++iCount )<br />

{<br />

230 /**<br />

* Determine iteration update by newton algorithm<br />

*/<br />

surfacePoint( u0, v0, &P0 );<br />

surfaceGradients( u0, v0, &gu, &gv );<br />

235 crossProduct( gu, gv, n0 );<br />

240<br />

245<br />

// intersect plane by ray<br />

dotProduct( ray.direction, n0, dot_d_n );<br />

if( dot_d_n == 0.0 ) break;<br />

subVector( P0, ray.origin, diff );<br />

dotProduct( diff, n0, dot_diff_n );<br />

t0 = dot_diff_n / dot_d_n;<br />

rayPoint( ray, t0, P1 );<br />

subVector( P1, P0, diff );<br />

Real solution[3];<br />

Real equSystem[3][3] = { { gu.x, gv.x, n0.x },<br />

250 { gu.y, gv.y, n0.y },<br />

{ gu.z, gv.z, n0.z } };<br />

solution[0] = diff.x;<br />

solution[1] = diff.y;<br />

255 solution[2] = diff.z;<br />

if( !solveEquationSystem( (Real *)equSystem, solution, 3 ) )<br />

return false;<br />

260 deltaU = solution[0] * node.scaleU;<br />

deltaV = solution[1] * node.scaleV;<br />

/**<br />

* Check update step for being small enough<br />

265 */<br />

if( absVal( deltaU ) < RAYEPS && absVal( deltaV ) < RAYEPS )<br />

{<br />

// save hit point info<br />

hitData−>hitU = u0;<br />

270 hitData−>hitV = v0;<br />

hitData−>distance = t0;<br />

normalizeVector( n0, n0 );<br />

if( dot_d_n > 0 ) negateVector( n0, hitData−>normal );<br />

275 else hitData−>normal = n0;<br />

#ifdef ITERATION_INFO<br />

if( iCount > iterations )<br />

{<br />

280 iterations = iCount;<br />

fprintf( stderr, "max iterations: %d\n", iCount );<br />

}<br />

#endif<br />

return true;<br />

285 }<br />

290<br />

// update parameters<br />

u0 += deltaU;<br />

v0 += deltaV;<br />

/**<br />

* Check parameters for being out of bo<strong>und</strong>s<br />

*/<br />

if( u0 < node.domain[U_AXIS].lower )<br />

295 {<br />

if( secondTry ) break;<br />

u0 = node.domain[U_AXIS].lower;<br />

u_exceeds = true;<br />

}<br />

300 else if( u0 > node.domain[U_AXIS].upper )


{<br />

305 }<br />

if( secondTry ) break;<br />

u0 = node.domain[U_AXIS].upper;<br />

u_exceeds = true;<br />

if( v0 < node.domain[V_AXIS].lower )<br />

{<br />

if( secondTry ) break;<br />

310 v0 = node.domain[V_AXIS].lower;<br />

v_exceeds = true;<br />

}<br />

else if( v0 > node.domain[V_AXIS].upper )<br />

{<br />

315 if( secondTry ) break;<br />

v0 = node.domain[V_AXIS].upper;<br />

v_exceeds = true;<br />

}<br />

320 // note exceedance for a second try<br />

secondTry = (u_exceeds || v_exceeds);<br />

u_exceeds = v_exceeds = false;<br />

}<br />

return false;<br />

325 }<br />

/* Build a hierarchical bo<strong>und</strong>ing volume<br />

* used for intersection calulation.<br />

*/<br />

330 //****************************************<br />

void FreeformSurface::buildSearchTree()<br />

//****************************************<br />

{<br />

divCounter = 0;<br />

335<br />

// chop search tree may exist<br />

if( searchTreeRoot != NULL )<br />

{<br />

chopSearchTree( searchTreeRoot );<br />

340 searchTreeRoot = NULL;<br />

}<br />

// build new search tree<br />

addToSearchTree( &searchTreeRoot, NULL, surfaceDomain );<br />

345 divisions = divCounter;<br />

}<br />

/* Add bo<strong>und</strong>ing volume of surface to search tree.<br />

*/<br />

350 //******************************************************************************************************<br />

bool FreeformSurface::addToSearchTree( TreeNode **nodeHook, TreeNode *parent, Interval nodeDomain[] )<br />

//******************************************************************************************************<br />

{<br />

Axis divAxis;<br />

355 Interval alpha[3];<br />

Real realTmp;<br />

360<br />

// determine node destination<br />

if( nodeHook == NULL ) nodeHook = &searchTreeRoot;<br />

// hook new node into search tree<br />

if( (*nodeHook = new TreeNode) != NULL )<br />

{<br />

TreeNode &node = **nodeHook;<br />

365 unsigned int i, j;<br />

// fill tree node<br />

getBo<strong>und</strong>ingVolume( &node, alpha );<br />

node.domain[U_AXIS] = nodeDomain[U_AXIS];<br />

370 node.domain[V_AXIS] = nodeDomain[V_AXIS];<br />

node.left =<br />

node.right = NULL;<br />

node.parent = parent;<br />

375 /**<br />

* Enclose whole surface into octree box<br />

*/<br />

if( parent == NULL )<br />

{<br />

380 Interval surfaceRange[3];<br />

Real component[3];<br />

unsigned int coord;<br />

// initialize intervals<br />

385 for( coord = X; coord


for( coord = X; coord surfaceRange[coord].upper )<br />

surfaceRange[coord].upper = component[coord];<br />

}<br />

}<br />

410 // set corners of octree box<br />

minCorner.x = surfaceRange[X].lower;<br />

minCorner.y = surfaceRange[Y].lower;<br />

minCorner.z = surfaceRange[Z].lower;<br />

415 maxCorner.x = surfaceRange[X].upper;<br />

maxCorner.y = surfaceRange[Y].upper;<br />

maxCorner.z = surfaceRange[Z].upper;<br />

}<br />

420 /**<br />

* Precalculate values needed during iteration<br />

*/<br />

node.scaleU = (nodeDomain[U_AXIS].upper − nodeDomain[U_AXIS].lower) /<br />

(alpha[U_AXIS].upper − alpha[U_AXIS].lower);<br />

425 node.scaleV = (nodeDomain[V_AXIS].upper − nodeDomain[V_AXIS].lower) /<br />

(alpha[V_AXIS].upper − alpha[V_AXIS].lower);<br />

for( i = 0; i < 3; ++i )<br />

{<br />

430 dotProduct( node.base[i], node.normal[i], realTmp );<br />

node.baseNormalDomain[i].lower = realTmp * alpha[i].lower;<br />

node.baseNormalDomain[i].upper = realTmp * alpha[i].upper;<br />

}<br />

435 /**<br />

* Divide surface if not flat enough<br />

*/<br />

if( !isFlat( &divAxis ) )<br />

{<br />

440<br />

#ifdef DEBUG_FULL<br />

if( debugInfo != NULL )<br />

{<br />

fprintf( debugInfo, "\n" );<br />

445 dumpControlPoints( debugInfo );<br />

fprintf( debugInfo, "domain_u=[%.6f %.6f]; domain_v=[%.6f %.6f];\n",<br />

node.domain[U_AXIS].lower, node.domain[U_AXIS].upper,<br />

node.domain[V_AXIS].lower, node.domain[V_AXIS].upper );<br />

fprintf( debugInfo, "base=[%.6f %.6f %.6f];\n",<br />

450 node.baseVertex.x, node.baseVertex.y, node.baseVertex.z );<br />

fprintf( debugInfo, "alpha0=[%.6f %.6f]; alpha1=[%.6f %.6f]; alpha2=[%.6f %.6f];\n",<br />

alpha[0].lower, alpha[0].upper, alpha[1].lower,<br />

alpha[1].upper, alpha[2].lower, alpha[2].upper );<br />

fprintf( debugInfo, "n0=[%.6f %.6f %.6f]; n1=[%.6f %.6f %.6f]; n2=[%.6f %.6f %.6f];\ntileTester\n",<br />

455 node.base[0].x, node.base[0].y, node.base[0].z,<br />

node.base[1].x, node.base[1].y, node.base[1].z,<br />

node.base[2].x, node.base[2].y, node.base[2].z );<br />

}<br />

#endif<br />

460<br />

465<br />

470<br />

FreeformSurface *subLeft = NULL, *subRight = NULL;<br />

bool leftOK, rightOK;<br />

Interval splitLeft, splitRight;<br />

Interval save;<br />

// half surface along determined axis<br />

if( divCounter < SURFACE_MAX_DIVIDE && divide( divAxis, 0.5, &subLeft, &subRight ) )<br />

{<br />

divCounter++;<br />

// determine subintervals<br />

splitLeft.lower = nodeDomain[!divAxis].lower;<br />

splitRight.upper = nodeDomain[!divAxis].upper;<br />

splitLeft.upper =<br />

475 splitRight.lower = 0.5 * (nodeDomain[!divAxis].lower + nodeDomain[!divAxis].upper);<br />

save = nodeDomain[divAxis];<br />

// add left subsurface to search tree<br />

nodeDomain[!divAxis] = splitLeft;<br />

480 leftOK = subLeft−>addToSearchTree( &node.left, *nodeHook, nodeDomain );<br />

// add right subsurface to search tree<br />

nodeDomain[divAxis] = save;<br />

nodeDomain[!divAxis] = splitRight;<br />

485 rightOK = subRight−>addToSearchTree( &node.right, *nodeHook, nodeDomain );<br />

}<br />

490<br />

}<br />

if( subLeft != NULL ) delete subLeft;<br />

if( subRight != NULL ) delete subRight;<br />

return ( leftOK && rightOK );<br />

#ifdef DEBUG_FULL<br />

495 else<br />

{<br />

if( debugInfo != NULL )<br />

{<br />

fprintf( debugInfo, "\n" );<br />

500 dumpControlPoints( debugInfo );


fprintf( debugInfo, "domain_u=[%.6f %.6f]; domain_v=[%.6f %.6f];\n",<br />

node.domain[U_AXIS].lower, node.domain[U_AXIS].upper,<br />

node.domain[V_AXIS].lower, node.domain[V_AXIS].upper );<br />

fprintf( debugInfo, "base=[%.6f %.6f %.6f];\n",<br />

505 node.baseVertex.x, node.baseVertex.y, node.baseVertex.z );<br />

fprintf( debugInfo, "alpha0=[%.6f %.6f]; alpha1=[%.6f %.6f]; alpha2=[%.6f %.6f];\n",<br />

alpha[0].lower, alpha[0].upper, alpha[1].lower,<br />

alpha[1].upper, alpha[2].lower, alpha[2].upper );<br />

fprintf( debugInfo, "n0=[%.6f %.6f %.6f]; n1=[%.6f %.6f %.6f]; n2=[%.6f %.6f %.6f];\ntileTester\n",<br />

510 node.base[0].x, node.base[0].y, node.base[0].z,<br />

node.base[1].x, node.base[1].y, node.base[1].z,<br />

node.base[2].x, node.base[2].y, node.base[2].z );<br />

}<br />

}<br />

515 #endif<br />

return true;<br />

}<br />

return false;<br />

}<br />

520<br />

/* Dump control points of surface<br />

*/<br />

//******************************************************<br />

525 void FreeformSurface::dumpControlPoints( FILE *fptr )<br />

//******************************************************<br />

{<br />

unsigned int i, j;<br />

fprintf( fptr, "PM(:,:,1)=[" );<br />

530 for( i = 0; i


**<br />

* Build auxiliary plane to determine<br />

* control point’s distance to base parallelogram.<br />

*/<br />

605 Real n0, a0;<br />

Real dst;<br />

Real d_min = 0.0, d_max = 0.0;<br />

610<br />

dotProduct( nrml[2], E1, n0 );<br />

// determine plane distance for all control points<br />

for( i = 0; i planeNormal = &(volumeNode−>normal[min]);<br />

volumeNode−>baseVertex = E1;<br />

}<br />

/* Delete search tree recursively.<br />

685 */<br />

//*******************************************************<br />

void FreeformSurface::chopSearchTree( TreeNode *node )<br />

//*******************************************************<br />

{<br />

690 if( node−>left != NULL ) chopSearchTree( node−>left );<br />

if( node−>right != NULL ) chopSearchTree( node−>right );<br />

delete node;<br />

}


#ifndef __BEZIER_H<br />

#define __BEZIER_H<br />

#include <br />

5 #include "Types.h"<br />

/* The Bezier mixin class<br />

*/<br />

//***************<br />

10 class Bezier {<br />

//***************<br />

public:<br />

static unsigned int factorial( int x )<br />

{<br />

15 register unsigned int result = 1;<br />

20<br />

}<br />

while(x > 1) result *= x−−;<br />

return result;<br />

static Real bernstein( unsigned int n, unsigned int i, Real t )<br />

{<br />

Real fn = factorial( n );<br />

Real fni = factorial( n−i );<br />

25 Real fi = factorial( i );<br />

30 }<br />

};<br />

Real tmp = fn / (fni * fi);<br />

return (tmp * pow((1.0−t),(n−i)) * pow(t,i));<br />

#endif /* __BEZIER_H */


#ifndef __BEZIERCURVE_H<br />

#define __BEZIERCURVE_H<br />

#include "Bezier.h"<br />

5 #include "SimpleList.h"<br />

#include "Types.h"<br />

/* The BezierCurve class<br />

*/<br />

10 //**************************************************************<br />

class BezierCurve : public SimpleList, public Bezier {<br />

//**************************************************************<br />

public:<br />

BezierCurve( int deg = −1 );<br />

15 ~BezierCurve();<br />

20<br />

Point *newControlPoint() {<br />

return SimpleList::append();<br />

}<br />

bool deleteControlPoint( unsigned int pos ) {<br />

return SimpleList::deleteItem( pos );<br />

}<br />

25 void curvePoint( Real t, Point *pnt );<br />

void divide( Real t, BezierCurve **subLeft, BezierCurve **subRight );<br />

/**<br />

* Curve analysis<br />

30 */<br />

int getDegree() {<br />

return (SimpleList::length() − 1);<br />

}<br />

35 bool exceedingPoints();<br />

bool signChanges();<br />

Real maxPointDistance();<br />

Real guessLength();<br />

Real guessCurvature();<br />

40<br />

private:<br />

// override for protection<br />

Point *append() { return NULL; }<br />

bool deleteItem( unsigned int pos ) { return false; }<br />

45 unsigned int length() { return 0; }<br />

50 };<br />

// BezierCurve’s properties<br />

BezierCurve *right;<br />

BezierCurve *left;<br />

#endif /* __BEZIERCURVE_H */


5<br />

#include <br />

#include "BezierCurve.h"<br />

#include "utils.h"<br />

/* Constructor<br />

*/<br />

//************************************<br />

BezierCurve::BezierCurve( int deg )<br />

10 //************************************<br />

: SimpleList()<br />

{<br />

// initialize subcurve pointers<br />

right =<br />

15 left = NULL;<br />

20<br />

}<br />

// initialize for degree may given<br />

for(; deg > −1; −−deg ) newControlPoint();<br />

/* Destructor<br />

*/<br />

//****************************<br />

BezierCurve::~BezierCurve()<br />

25 //****************************<br />

{<br />

// delete subcurves may exist ...<br />

if( right != NULL ) delete right;<br />

if( left != NULL ) delete left;<br />

30 }<br />

/* Determine curve point at t<br />

*/<br />

//***************************************************<br />

35 void BezierCurve::curvePoint( Real t, Point *pnt )<br />

//***************************************************<br />

{<br />

int degree = getDegree();<br />

40 pnt−>x = pnt−>y = pnt−>z = 0.0;<br />

for( int i = 0; i x += p.x * b;<br />

pnt−>y += p.y * b;<br />

pnt−>z += p.z * b;<br />

/* Divide curve at t<br />

*/<br />

55 //**********************************************************************************<br />

void BezierCurve::divide( Real t, BezierCurve **subLeft, BezierCurve **subRight )<br />

//**********************************************************************************<br />

{<br />

Point *points;<br />

60 int pointIndex = 0, i;<br />

double t1 = 1.0 − t;<br />

int degree = getDegree();<br />

if( degree > 0 )<br />

65 {<br />

// check for existance of subcurves<br />

// and their degree for being not equal.<br />

if( left != NULL && left−>getDegree() != degree )<br />

{<br />

70 delete left, right;<br />

left = right = NULL;<br />

}<br />

if( right == NULL ) right = new BezierCurve( degree );<br />

75 if( left == NULL ) left = new BezierCurve( degree );<br />

// allocate point list on stack<br />

points = (Point *)alloca( (degree+1) * sizeof(Point) );<br />

80 // fill list with control points<br />

for( i = 0; i pointIndex; −−i )<br />

{<br />

points[i].x = t1 * points[i−1].x + t * points[i].x;<br />

95 points[i].y = t1 * points[i−1].y + t * points[i].y;<br />

points[i].z = t1 * points[i−1].z + t * points[i].z;<br />

}<br />

100 }<br />

pointIndex++;


105<br />

}<br />

}<br />

*subLeft = left;<br />

*subRight = right;<br />

/* Check curve for inner control points exceeding<br />

* bo<strong>und</strong>s of start and end<br />

*/<br />

//************************************<br />

110 bool BezierCurve::exceedingPoints()<br />

//************************************<br />

{<br />

bool exceedsLeft, exceedsRight;<br />

Vector a, b, p;<br />

115 Vector ab, ap, bp;<br />

Real tmp;<br />

int degree = getDegree();<br />

if( degree > 1 )<br />

120 {<br />

a = (*this)[0];<br />

b = (*this)[degree];<br />

125<br />

subVector( b, a, ab );<br />

// check all intermediate points for<br />

// lying beyond the bo<strong>und</strong>s<br />

for( int i = 1; i < degree; ++i )<br />

{<br />

130 p = (*this)[i];<br />

135<br />

subVector( p, a, ap );<br />

dotProduct( ap, ab, tmp );<br />

exceedsLeft = (tmp < 0 );<br />

subVector( p, b, bp );<br />

dotProduct( bp, ab, tmp );<br />

exceedsRight = (tmp > 0 );<br />

140 if( exceedsLeft || exceedsRight ) return true;<br />

}<br />

}<br />

return false;<br />

}<br />

145<br />

/* Check for sign change in curvature<br />

*/<br />

//********************************<br />

bool BezierCurve::signChanges()<br />

150 //********************************<br />

{<br />

Point a;<br />

Vector ap, ab;<br />

Vector n;<br />

155 int i, sgn;<br />

Real tmp = 0.0;<br />

int degree = getDegree();<br />

if( degree > 2 )<br />

160 {<br />

a = (*this)[0];<br />

// create auxiliary plane<br />

subVector( (*this)[degree], a, ab );<br />

165 if( vectorLength( ab ) == 0.0 ) return false;<br />

// make sure vector ap is<br />

// not multiple of vector ab<br />

for( i = 1; tmp == 0.0; ++i )<br />

170 {<br />

if( i == degree ) return false;<br />

subVector( (*this)[i], a, ap );<br />

crossProduct( ap, ab, n );<br />

175 tmp = vectorLength( n );<br />

}<br />

crossProduct( n, ab, n );<br />

// determine sign of the first<br />

180 // intermediate control point<br />

subVector( (*this)[1], a, ap );<br />

dotProduct( n, ap, tmp );<br />

sgn = (tmp >= 0.0);<br />

185 // ... and compare with sign<br />

// of remaining control points<br />

for( i = 2; i < degree; ++i )<br />

{<br />

subVector( (*this)[i], a, ap );<br />

190 dotProduct( n, ap, tmp );<br />

if( (int)(tmp >= 0.0) ^ sgn )<br />

return true;<br />

}<br />

}<br />

195 return false;<br />

}<br />

/* Deterine maximum point distance from a line<br />

* which is running from start point to end point<br />

200 */


*************************************<br />

Real BezierCurve::maxPointDistance()<br />

//*************************************<br />

{<br />

205 Vector a, tmp;<br />

Vector ab, ap;<br />

Real abLen, dist;<br />

Real maxDist = 0.0;<br />

int degree = getDegree();<br />

210<br />

if( degree > 1 )<br />

{<br />

a = (*this)[0];<br />

subVector( (*this)[degree], a, ab );<br />

215 abLen = vectorLength( ab );<br />

// determine maximum distance of intermediate<br />

// control points to line between bo<strong>und</strong>ary points.<br />

for( int i = 1; i < degree; ++i )<br />

220 {<br />

subVector( (*this)[i], a, ap );<br />

if( abLen > 0.0 )<br />

{<br />

225 crossProduct( ap, ab, tmp );<br />

dist = vectorLength( tmp ) / abLen;<br />

}<br />

else<br />

dist = vectorLength( ap );<br />

230<br />

235 }<br />

if( dist > maxDist ) maxDist = dist;<br />

}<br />

}<br />

return maxDist;<br />

/* Guess curve length<br />

*/<br />

//********************************<br />

240 Real BezierCurve::guessLength()<br />

//********************************<br />

{<br />

Real len = 0.0;<br />

Vector ab;<br />

245 int degree = getDegree();<br />

for( int i = 1; i


#ifndef __BEZIERSURFACE_H<br />

#define __BEZIERSURFACE_H<br />

#include <br />

5 #include "FreeformSurface.h"<br />

#include "Bezier.h"<br />

/* The BezierSurface class<br />

*/<br />

10 //**************************************************************<br />

class BezierSurface : public FreeformSurface, public Bezier {<br />

//**************************************************************<br />

public:<br />

BezierSurface( unsigned int _m, unsigned int _n );<br />

15 ~BezierSurface() {}<br />

private:<br />

// implement methods expected by FreeformSurface<br />

void surfacePoint( Real u, Real v, Point *pnt );<br />

20 void surfaceGradients( Real u, Real v, Vector *gu, Vector *gv );<br />

bool isFlat( Axis *divAxis );<br />

bool divide( Axis a, Real t, FreeformSurface **left, FreeformSurface **right );<br />

};<br />

25 #endif /* __BEZIERSURFACE_H */


5<br />

#include "BezierSurface.h"<br />

#include "BezierCurve.h"<br />

#include "utils.h"<br />

/* Constructor<br />

*/<br />

//*****************************************************************<br />

BezierSurface::BezierSurface( unsigned int _m, unsigned int _n )<br />

10 //*****************************************************************<br />

: FreeformSurface( _m, _n )<br />

{<br />

// set domain of u and v for bezier surfaces<br />

surfaceDomain[U_AXIS].lower =<br />

15 surfaceDomain[V_AXIS].lower = 0.0;<br />

surfaceDomain[U_AXIS].upper =<br />

surfaceDomain[V_AXIS].upper = 1.0;<br />

}<br />

20 /* Calculate a point on a bezier surface<br />

*/<br />

//***************************************************************<br />

void BezierSurface::surfacePoint( Real u, Real v, Point *pnt )<br />

//***************************************************************<br />

25 { Real b_m, b_n;<br />

Vector tmp;<br />

// initialize result vector<br />

30 *pnt = nullVector;<br />

for( unsigned int i = 0; i


}<br />

/* Test surface for being flat enough for iteration.<br />

*/<br />

105 //********************************************<br />

bool BezierSurface::isFlat( Axis *divAxis )<br />

//********************************************<br />

{<br />

unsigned int i, j;<br />

110 Real curv, len;<br />

bool exceedingPnt[2] = { false, false };<br />

bool signChange[2] = { false, false };<br />

Real maxCurvature[2] = { 1.0, 1.0 };<br />

Real minLength[2] = { DBL_MAX, DBL_MAX };<br />

115 Real maxLength[2] = { 0.0, 0.0 };<br />

/**<br />

* Check for control points<br />

* exceeding base bo<strong>und</strong>aries<br />

120 */<br />

BezierCurve uCurve( m );<br />

BezierCurve vCurve( n );<br />

// check curves running along u−axis<br />

125 for( i = 0; i maxLength[U_AXIS] ) maxLength[U_AXIS] = len;<br />

if( len < minLength[U_AXIS] ) minLength[U_AXIS] = len;<br />

}<br />

// check curves running along v−axis<br />

145 for( i = 0; i maxLength[V_AXIS] ) maxLength[V_AXIS] = len;<br />

if( len < minLength[V_AXIS] ) minLength[V_AXIS] = len;<br />

}<br />

// evaluate split criteria<br />

165 Real lengthDiff[2];<br />

Real aspectRatio[2];<br />

if( maxLength[U_AXIS] > RAYEPS && maxLength[V_AXIS] > RAYEPS )<br />

{<br />

170 lengthDiff[U_AXIS] = maxLength[U_AXIS] − minLength[U_AXIS];<br />

lengthDiff[V_AXIS] = maxLength[V_AXIS] − minLength[V_AXIS];<br />

aspectRatio[U_AXIS] = maxLength[U_AXIS] / maxLength[V_AXIS];<br />

aspectRatio[V_AXIS] = maxLength[V_AXIS] / maxLength[U_AXIS];<br />

175 if( lengthDiff[U_AXIS] > lengthDiffLimit || lengthDiff[V_AXIS] > lengthDiffLimit )<br />

{<br />

*divAxis = (lengthDiff[U_AXIS] > lengthDiff[V_AXIS])? U_AXIS : V_AXIS;<br />

return false;<br />

}<br />

180<br />

if( aspectRatio[U_AXIS] < aspectRatioLimit || aspectRatio[V_AXIS] < aspectRatioLimit )<br />

{<br />

*divAxis = (aspectRatio[U_AXIS] < aspectRatio[V_AXIS])? U_AXIS : V_AXIS;<br />

return false;<br />

185 }<br />

if( maxCurvature[U_AXIS] > curvatureLimit || maxCurvature[V_AXIS] > curvatureLimit )<br />

{<br />

*divAxis = (maxCurvature[U_AXIS] > maxCurvature[V_AXIS])? U_AXIS : V_AXIS;<br />

190 return false;<br />

}<br />

if( exceedingPnt[U_AXIS] || signChange[U_AXIS] )<br />

{<br />

195 *divAxis = V_AXIS;<br />

return false;<br />

}<br />

if( exceedingPnt[V_AXIS] || signChange[V_AXIS] )<br />

200 {


}<br />

}<br />

205 return true;<br />

}<br />

*divAxis = U_AXIS;<br />

return false;<br />

/* Do a surface split along given axis at parameter t.<br />

*/<br />

210 //**********************************************************************************************************<br />

bool BezierSurface::divide( Axis divAxis, Real t, FreeformSurface **subLeft, FreeformSurface **subRight )<br />

//**********************************************************************************************************<br />

{<br />

unsigned int i, j;<br />

215 BezierCurve *subCurveLeft, *subCurveRight;<br />

*subLeft = new BezierSurface( m, n );<br />

*subRight = new BezierSurface( m, n );<br />

220 if( *subLeft == NULL || *subRight == NULL ) return false;<br />

/**<br />

* Split surface along u−axis.<br />

*/<br />

225 if( divAxis == U_AXIS )<br />

{<br />

BezierCurve tmpCurve( n );<br />

for( i = 0; i


5<br />

#ifndef __OCTREE_H<br />

#define __OCTREE_H<br />

#include "Types.h"<br />

#define MAXSPLITLEVEL 4<br />

#define MAXCELLOBJ 4<br />

// forward declarations<br />

10 struct __Node;<br />

struct __ObjectHit;<br />

class Model;<br />

class Object;<br />

15 typedef struct {<br />

Vector min;<br />

bool flag;<br />

Object **objectList;<br />

struct __Node *next;<br />

20 //********<br />

} Cell;<br />

//********<br />

typedef struct __Node {<br />

25 Cell cell[8];<br />

Real length;<br />

Vector center;<br />

//********<br />

} Node;<br />

30 //********<br />

/* The Octree struct<br />

*/<br />

typedef struct __Octree {<br />

35 Node root;<br />

Node *curNode;<br />

Byte curNum;<br />

Vector plane;<br />

40 Vector min;<br />

Vector max;<br />

Real minLength;<br />

//**********<br />

45 } Octree;<br />

//**********<br />

/* octree creation<br />

*/<br />

50 void initOctree( Octree *octree );<br />

bool buildOctree( Model *model );<br />

bool getNodeList( Object **objectList, Node *node, Byte cellNr, Object **tmpList );<br />

void getNodeMinCorner( const Node &node, Byte cellNr, Vector *v );<br />

void split( Octree *octree, Node *node, Byte level, Byte cellNr, Object **tmpList );<br />

55<br />

/* octree usage<br />

*/<br />

void findPoint( const Octree &octree, const <strong>Ray</strong> &ray, Vector *v );<br />

bool octreeIntersect( Model *model, const <strong>Ray</strong> &ray, struct __ObjectHit *hit );<br />

60 void nextPoint( const Octree &octree, const <strong>Ray</strong> &ray, Vector *v );<br />

void findNode( Octree *octree, const Vector &point );<br />

/* octree destruction<br />

*/<br />

65 void deleteOctree( struct __Octree *octree );<br />

void clearNode( Node *node );<br />

#endif /* __OCTREE_H */


#include <br />

#include <br />

#include <br />

5 #include <br />

#include "Octree.h"<br />

#include "Object.h"<br />

#include "Model.h"<br />

#include "utils.h"<br />

10<br />

/* Initialize the octree<br />

*/<br />

//**********************************<br />

void initOctree( Octree *octree )<br />

15 //**********************************<br />

{<br />

// initialize root cells<br />

for( unsigned int i = 0; i < 8; ++i )<br />

{<br />

20 octree−>root.cell[i].min = nullVector;<br />

octree−>root.cell[i].flag = false;<br />

octree−>root.cell[i].objectList = NULL;<br />

octree−>root.cell[i].next = NULL;<br />

}<br />

25 octree−>root.length = 0.0;<br />

octree−>root.center = nullVector;<br />

octree−>curNode = NULL;<br />

octree−>curNum = 0;<br />

30 octree−>plane =<br />

octree−>min =<br />

octree−>max = nullVector;<br />

octree−>minLength = 0.0;<br />

}<br />

35<br />

/* Build the octree.<br />

*/<br />

//*********************************<br />

bool buildOctree( Model *model )<br />

40 //*********************************<br />

{<br />

Object **tmpList;<br />

Object **objectList;<br />

ObjectNode *curObj;<br />

45 Octree *octree = model−>octree;<br />

Node *root = &octree−>root;<br />

unsigned int objects = model−>scene.numOfObjects;<br />

unsigned int objInside;<br />

unsigned int i;<br />

50<br />

fprintf( stdout, "building octree ... " );<br />

if( (tmpList = new Object *[objects + 1]) != NULL )<br />

{<br />

55 root−>length = (octree−>max.x − octree−>min.x) / 2;<br />

octree−>minLength = root−>length;<br />

translateVector( octree−>min, root−>length, root−>center );<br />

translateVector( octree−>max, RAYEPS, octree−>max );<br />

60 translateVector( octree−>min, −RAYEPS, octree−>min );<br />

curObj = model−>scene.objects;<br />

// test all objects for being inside the cube ...<br />

for( objInside = 0; curObj != NULL; curObj = curObj−>next )<br />

65 if( curObj−>object−>test( octree−>min, octree−>max ) )<br />

tmpList[objInside++] = curObj−>object;<br />

70<br />

// terminate list<br />

tmpList[objInside] = NULL;<br />

if( (objectList = new Object *[objInside + 1]) != NULL )<br />

{<br />

// copy all objects being inside the cube ...<br />

for( i = 0; i cell[i].flag = false;<br />

root−>cell[i].next = NULL;<br />

if( getNodeList( objectList, root, i, tmpList ) )<br />

{<br />

85 if( root−>cell[i].flag )<br />

split( octree, root, 1, i, tmpList );<br />

}<br />

else break;<br />

}<br />

90<br />

}<br />

octree−>minLength /= 2;<br />

delete[] objectList;<br />

95 delete[] tmpList;<br />

// all cells OK?<br />

return (i == 8);<br />

}<br />

100 return false;


}<br />

/* Determines all objects enclosed by the cell specified by the<br />

* parameters node and cellNr.<br />

105 */<br />

//***********************************************************************************<br />

bool getNodeList( Object **objectList, Node *node, Byte cellNr, Object **tmpList )<br />

//***********************************************************************************<br />

{<br />

110 unsigned int objInside;<br />

Vector maxCorner;<br />

Cell *curCell = &node−>cell[cellNr];<br />

getNodeMinCorner( *node, cellNr, &curCell−>min );<br />

115 translateVector( curCell−>min, node−>length, maxCorner );<br />

// test all objects for being inside the cube ...<br />

for( objInside = 0; *objectList != NULL; objectList++ )<br />

if( (*objectList)−>test( curCell−>min, maxCorner ) )<br />

120 tmpList[objInside++] = *objectList;<br />

// terminate list<br />

tmpList[objInside] = NULL;<br />

125 if( (curCell−>objectList = new Object *[objInside + 1]) != NULL )<br />

{<br />

// copy all objects being inside the cube ...<br />

for( register unsigned int i = 0; i objectList[i] = tmpList[i];<br />

130<br />

}<br />

curCell−>flag = (objInside > MAXCELLOBJ);<br />

return true;<br />

135 return false;<br />

}<br />

/* Determines the corner of an octree cell starting<br />

* at the smallest coordinates.<br />

140 */<br />

//******************************************************************<br />

void getNodeMinCorner( const Node &node, Byte cellNr, Vector *v )<br />

//******************************************************************<br />

{<br />

145 *v = node.center;<br />

if( (cellNr == 0) || (cellNr == 2) || (cellNr == 4) || (cellNr == 6) )<br />

v−>x −= node.length;<br />

if( (cellNr == 2) || (cellNr == 3) || (cellNr == 6) || (cellNr == 7) )<br />

v−>y −= node.length;<br />

150 if( (cellNr == 4) || (cellNr == 5) || (cellNr == 6) || (cellNr == 7) )<br />

v−>z −= node.length;<br />

}<br />

155 /* Splits a cell if it contains too many objects.<br />

* The cell is specified by the parameters node and cellNr.<br />

*/<br />

//************************************************************************************<br />

void split( Octree *octree, Node *node, Byte level, Byte cellNr, Object **tmpList )<br />

160 //************************************************************************************<br />

{<br />

Node *newNode;<br />

Cell *curCell = &node−>cell[cellNr];<br />

unsigned int i;<br />

165<br />

if( level length = node−>length / 2;<br />

translateVector( curCell−>min, newNode−>length, newNode−>center );<br />

if( newNode−>length < octree−>minLength )<br />

175 octree−>minLength = newNode−>length;<br />

// create objectLists for cells<br />

for( i = 0; i < 8; ++i )<br />

{<br />

180 newNode−>cell[i].flag = false;<br />

newNode−>cell[i].next = NULL;<br />

185 }<br />

if( !getNodeList( curCell−>objectList, newNode, i, tmpList ) )<br />

break;<br />

// all cells OK?<br />

if( i == 8 )<br />

{<br />

190 // hook new subnode<br />

curCell−>next = newNode;<br />

// delete old objectList<br />

delete[] curCell−>objectList;<br />

195 curCell−>objectList = NULL;<br />

200<br />

for( i = 0; i < 8; ++i )<br />

if( newNode−>cell[i].flag )<br />

split( octree, newNode, level + 1, i, tmpList );


205 }<br />

}<br />

210<br />

}<br />

}<br />

return;<br />

delete newNode;<br />

curCell−>flag = false;<br />

/* Determines the intersection point of a ray and the octree cube.<br />

*/<br />

//******************************************************************<br />

void findPoint( const Octree &octree, const <strong>Ray</strong> &ray, Vector *v )<br />

215 //******************************************************************<br />

{<br />

Real dist;<br />

Real distMax = 0.0;<br />

220 if( ray.direction.x != 0.0 )<br />

{<br />

dist = (octree.plane.x − ray.origin.x) / ray.direction.x;<br />

if( dist > distMax ) distMax = dist;<br />

}<br />

225<br />

if( ray.direction.y != 0.0 )<br />

{<br />

dist = (octree.plane.y − ray.origin.y) / ray.direction.y;<br />

if( dist > distMax ) distMax = dist;<br />

230 }<br />

if( ray.direction.z != 0.0 )<br />

{<br />

dist = (octree.plane.z − ray.origin.z) / ray.direction.z;<br />

235 if( dist > distMax ) distMax = dist;<br />

}<br />

240<br />

}<br />

rayPoint( ray, distMax, *v );<br />

/* Determines the first object being intersected by a ray.<br />

*/<br />

//*********************************************************************<br />

bool octreeIntersect( Model *model, const <strong>Ray</strong> &ray, ObjectHit *hit )<br />

245 //*********************************************************************<br />

{<br />

Vector min;<br />

Vector max;<br />

Vector point;<br />

250 Real distance;<br />

bool hitsObject = false;<br />

Octree *octree = model−>octree;<br />

Object **curObj;<br />

255 hit−>distance = DBL_MAX;<br />

while( octree−>curNode != NULL && !hitsObject )<br />

{<br />

curObj = octree−>curNode−>cell[octree−>curNum].objectList;<br />

260 while( *curObj != NULL )<br />

{<br />

model−>statistics.intersectionCnt++;<br />

if( (*curObj)−>intersect( ray, &distance ) )<br />

265 if( distance < hit−>distance )<br />

{<br />

rayPoint( ray, distance, point );<br />

min = octree−>curNode−>cell[octree−>curNum].min;<br />

translateVector( min, octree−>curNode−>length, max );<br />

270<br />

if( (point.x − RAYEPS = min.x) &&<br />

(point.y − RAYEPS = min.y) &&<br />

(point.z − RAYEPS = min.z) )<br />

{<br />

275 hitsObject = true;<br />

hit−>object = *curObj;<br />

hit−>distance = distance;<br />

}<br />

}<br />

280<br />

}<br />

curObj++;<br />

if( !hitsObject )<br />

285 {<br />

nextPoint( *octree, ray, &point );<br />

findNode( octree, point );<br />

}<br />

}<br />

290<br />

}<br />

return hitsObject;<br />

/* Determines the point in which a ray is leaving the current cell.<br />

295 */<br />

//******************************************************************<br />

void nextPoint( const Octree &octree, const <strong>Ray</strong> &ray, Vector *v )<br />

//******************************************************************<br />

{<br />

300 Vector min;


305<br />

Vector max;<br />

Vector add = { 0.0, 0.0, 0.0 };<br />

Real dist;<br />

Real distMin = DBL_MAX;<br />

min = octree.curNode−>cell[octree.curNum].min;<br />

translateVector( min, octree.curNode−>length, max );<br />

if( ray.direction.x != 0.0 )<br />

310 if( ray.direction.x < 0.0 )<br />

distMin = (min.x − ray.origin.x) / ray.direction.x;<br />

else<br />

distMin = (max.x − ray.origin.x) / ray.direction.x;<br />

315 if( ray.direction.y != 0.0 )<br />

if( ray.direction.y < 0.0 )<br />

{<br />

dist = (min.y − ray.origin.y) / ray.direction.y;<br />

if( dist < distMin ) distMin = dist;<br />

320 }<br />

else<br />

{<br />

dist = (max.y − ray.origin.y) / ray.direction.y;<br />

if( dist < distMin ) distMin = dist;<br />

325 }<br />

if( ray.direction.z != 0.0 )<br />

if( ray.direction.z < 0.0 )<br />

{<br />

330 dist = (min.z − ray.origin.z) / ray.direction.z;<br />

if( dist < distMin ) distMin = dist;<br />

}<br />

else<br />

{<br />

335 dist = (max.z − ray.origin.z) / ray.direction.z;<br />

if( dist < distMin ) distMin = dist;<br />

}<br />

340<br />

rayPoint( ray, distMin, *v );<br />

if( (fabs( v−>x − min.x ) < RAYEPS) || (fabs( v−>x − max.x ) < RAYEPS) )<br />

add.x = (ray.direction.x > 0.0)? octree.minLength : −octree.minLength;<br />

if( (fabs( v−>y − min.y ) < RAYEPS) || (fabs( v−>y − max.y ) < RAYEPS) )<br />

345 add.y = (ray.direction.y > 0.0)? octree.minLength : −octree.minLength;<br />

if( (fabs( v−>z − min.z ) < RAYEPS) || (fabs( v−>z − max.z ) < RAYEPS) )<br />

add.z = (ray.direction.z > 0.0)? octree.minLength : −octree.minLength;<br />

350 addVector( *v, add, *v );<br />

}<br />

/* Determines the cell of the octree enclosing<br />

* the point specified by the parameter p.<br />

355 */<br />

//*************************************************<br />

void findNode( Octree *octree, const Vector &p )<br />

//*************************************************<br />

{<br />

360 Node *node;<br />

octree−>curNode = NULL;<br />

if( (p.x >= octree−>min.x) && (p.x max.x) &&<br />

365 (p.y >= octree−>min.y) && (p.y max.y) &&<br />

(p.z >= octree−>min.z) && (p.z max.z) )<br />

{<br />

node = &octree−>root;<br />

do<br />

370 {<br />

if( p.x > node−>center.x )<br />

if( p.y > node−>center.y )<br />

if( p.z > node−>center.z )<br />

octree−>curNum = 1;<br />

375 else<br />

octree−>curNum = 5;<br />

else<br />

if( p.z > node−>center.z )<br />

octree−>curNum = 3;<br />

380 else<br />

octree−>curNum = 7;<br />

else<br />

if( p.y > node−>center.y )<br />

if( p.z > node−>center.z )<br />

385 octree−>curNum = 0;<br />

else<br />

octree−>curNum = 4;<br />

else<br />

if( p.z > node−>center.z )<br />

390 octree−>curNum = 2;<br />

else<br />

octree−>curNum = 6;<br />

octree−>curNode = node;<br />

395 node = node−>cell[octree−>curNum].next;<br />

400<br />

}<br />

}<br />

} while( node != NULL );


* Deletes an octree.<br />

*/<br />

//************************************<br />

void deleteOctree( Octree *octree )<br />

405 //************************************<br />

{<br />

clearNode( &octree−>root );<br />

delete octree;<br />

}<br />

410<br />

/* Clears a node and its subnodes.<br />

* Deletes all objects hooked in list.<br />

*/<br />

//*****************************<br />

415 void clearNode( Node *node )<br />

//*****************************<br />

{<br />

for( unsigned int i = 0; i < 8; ++i )<br />

{<br />

420 if( node−>cell[i].next != NULL )<br />

{<br />

clearNode( node−>cell[i].next );<br />

delete node−>cell[i].next;<br />

}<br />

425 else<br />

delete[] node−>cell[i].objectList;<br />

}<br />

}


#ifndef __PARSER_H<br />

#define __PARSER_H<br />

#include <br />

5 #include "Types.h"<br />

#include "Scanner.h"<br />

#include "Model.h"<br />

#include "SimpleList.h"<br />

#include "PatchBuffer.h"<br />

10 #include "Object.h"<br />

#include "PointMapper.h"<br />

#include "Noise.h"<br />

#include "Texture.h"<br />

#include "BumpTexture.h"<br />

15 #include "BitmapTexture.h"<br />

#include "ReflectionTexture.h"<br />

#include "CheckerTexture.h"<br />

#include "BrickTexture.h"<br />

#include "MarbleTexture.h"<br />

20 #include "WoodTexture.h"<br />

#define MAX_NESTING_DEPTH 10<br />

#define MAX_TEX_CHNL 2<br />

25 typedef enum {<br />

// critical errors<br />

NO_SCANNER, NO_MODEL,<br />

NO_OCTREE, NO_LIGHT,<br />

NO_OBJECT, NO_TEXTURE,<br />

30 NO_DEFINITION, NO_INVERSE_MATRIX,<br />

NO_PATCH, NO_BUFFER,<br />

CRITICAL_ERRORS,<br />

35 // parse errors<br />

END_EXPECTED = CRITICAL_ERRORS,<br />

NUMBER_EXPECTED, REAL_EXPECTED,<br />

COORD_EXPECTED,<br />

OBJECT_EXPECTED, MISSING_KEYWORD,<br />

40 MISSING_FILENAME, FILE_NESTING_TOO_DEEP,<br />

BAD_VECTOR, SYNTAX_ERROR,<br />

MISSING_NAME, LIGHT_INTENSITY,<br />

SECOND_COLOR_EXPECTED, SPHERE_RAD,<br />

PLANE_DISTANCE, CYLINDER_RAD,<br />

45 CYLINDER_HEIGHT, CONE_SML_RAD,<br />

CONE_LRG_RAD, CONE_HEIGHT,<br />

MATERIAL_NOT_DEFINED, TEXTURE_NOT_DEFINED,<br />

COLOR_NOT_DEFINED, BUFFER_NOT_DEFINED,<br />

ILLEGAL_DEFINITION, BAD_FORMAT,<br />

50 NUMBER_OUT_OF_BOUNDS, NO_FILE,<br />

NO_SUCH_VERTEX, MISSING_BRACE,<br />

PARSE_ERRORS,<br />

55 // warnings<br />

OCTREE_FAILED = PARSE_ERRORS,<br />

OCTREE_REDEFINITION, SKIP_REDEFINITION,<br />

EMPTY_BUFFER,<br />

60 NUMBER_OF_MESSAGES<br />

//**************<br />

} ParseError;<br />

//**************<br />

65 typedef struct {<br />

Texture::ParseInfo texture;<br />

BumpTexture::ParseInfo bump;<br />

// parse buffer<br />

70 Color color[MAX_TEX_CHNL];<br />

Material material[MAX_TEX_CHNL];<br />

// pointers for current buffer cell<br />

unsigned int colPtr;<br />

unsigned int matPtr;<br />

75<br />

// texture specific data<br />

union {<br />

BitmapTexture::ParseInfo bitmap;<br />

ReflectionTexture::ParseInfo reflection;<br />

80 CheckerTexture::ParseInfo checker;<br />

BrickTexture::ParseInfo brick;<br />

MarbleTexture::ParseInfo marble;<br />

WoodTexture::ParseInfo wood;<br />

};<br />

85<br />

90<br />

Symbol textureType;<br />

//***************<br />

} TextureData;<br />

//***************<br />

// default texture data<br />

extern const TextureData defaultTextureData;<br />

//*****************************************<br />

95 typedef SimpleList VertexBuffer;<br />

//*****************************************<br />

typedef struct __DefNode {<br />

union {<br />

100 Color colorDef;


TextureData textureDataDef;<br />

Material materialDef;<br />

PatchBuffer *patchBuffer;<br />

VertexBuffer *vertexBuffer;<br />

105 };<br />

Symbol type;<br />

110<br />

// definition identifier<br />

char *ident;<br />

struct __DefNode *left;<br />

struct __DefNode *right;<br />

//***********<br />

} DefNode;<br />

115 //***********<br />

/* The Parser class<br />

*/<br />

//***************<br />

120 class Parser {<br />

//***************<br />

public:<br />

Parser( const char *fileName, Model *modelPtr, FILE *logging = NULL, int nesting = 1,<br />

DefNode **def_hook = NULL );<br />

125 ~Parser();<br />

private:<br />

// functions parsing definitions<br />

void getModel();<br />

130 void getScene();<br />

void getView();<br />

void getOctree();<br />

void getLights();<br />

void getObjects();<br />

135<br />

// functions parsing substructures<br />

void getPointLight( PointLight *pntLght );<br />

void getSpotLight( SpotLight *spotLght );<br />

void getObjectData( Object *object );<br />

140 void getTransform( Matrix *transformMatrix );<br />

void getTurbulence( Noise::Turbulence * );<br />

void getMapData( PointMapper::ParseInfo *mapData );<br />

// parsing functions for substructures supporting predefinition<br />

145 void getMaterial( Material *material, bool useDef = false );<br />

void getTextureData( TextureData *textureData, bool useDef = false );<br />

void getVertex( VertexBuffer *vertexBuff, bool useDef = false );<br />

void getPatch( PatchBuffer *patchBuff, bool useDef = false);<br />

void getColor( Color *color, bool useDef = false);<br />

150<br />

// auxilliary parsing functions<br />

Vector getVector();<br />

Real getReal();<br />

int getNumber( int lowerBo<strong>und</strong> = 1, int upperBo<strong>und</strong> = −1 );<br />

155 Coordinate getCoordinate();<br />

160<br />

// initializing functions<br />

void buildModel();<br />

void buildTexture( Object *object, TextureData *texData );<br />

// function handling warnings and errors<br />

void Error( ParseError, const char *ext = NULL );<br />

// functions handling predefinitions<br />

165 void setDef( Symbol type, const char *ident, DefNode **root );<br />

DefNode *getDef( Symbol type, const char *ident, DefNode **root );<br />

void delDefs( DefNode *root );<br />

170<br />

static const char *errorMessage[NUMBER_OF_MESSAGES];<br />

// Parser’s properties<br />

Scanner *scan;<br />

Model *model;<br />

int nestingDepth;<br />

175 DefNode *defTreeRoot;<br />

DefNode **defHook;<br />

180 };<br />

// logging<br />

FILE *log;<br />

#endif /* __PARSER_H */


#include <br />

#include <br />

#include <br />

5 #include "Parser.h"<br />

#include "Model.h"<br />

#include "Octree.h"<br />

#include "shade.h"<br />

#include "utils.h"<br />

10<br />

#include "Object.h"<br />

#include "Sphere.h"<br />

#include "Plane.h"<br />

#include "Box.h"<br />

15 #include "Cylinder.h"<br />

#include "Cone.h"<br />

#include "BezierSurface.h"<br />

// initialize default texture data<br />

20 const TextureData defaultTextureData = { Texture::defaultData, BumpTexture::defaultData,<br />

{white, black}, {defaultMaterial, defaultMaterial}, 0, 0,<br />

PointMapper::defaultData, BUMPSYM<br />

};<br />

25 /* Initialize error translation table<br />

*/<br />

//*********************************************************<br />

const char *Parser::errorMessage[NUMBER_OF_MESSAGES] = {<br />

//*********************************************************<br />

30 // critical errors<br />

"Scanner not availible", "Can’t build model",<br />

"Can’t build octree", "Can’t build light",<br />

"Can’t build object", "Can’t build texture",<br />

"Can’t build definition", "Can’t build inverse of transformation matrix",<br />

35 "Can’t get patch", "Can’t get buffer",<br />

// parse errors<br />

"END expected",<br />

"Number expected", "Real number expected",<br />

40 "Coordinate expected",<br />

"Object expected", "Missing keyword",<br />

"Missing filename", "File nesting too deep",<br />

"Bad vector", "Syntax error",<br />

"Missing name", "Light intensity exprected",<br />

45 "Second color definition expected", "Sphere radius expected",<br />

"Plane distance expected", "Cylinder radius expected",<br />

"Cylinder height expected", "Small cone radius expected",<br />

"Large cone radius expected", "Cone height expected",<br />

"Material not defined:", "Texture not defined:",<br />

50 "Color not defined:", "Buffer not defined:",<br />

"Invalid type in definition", "Bad format",<br />

"Number out of bo<strong>und</strong>s", "Can’t open file",<br />

"No such vertex", "Missing curly brace",<br />

55 // warnings<br />

"failed building octree − using default intersection",<br />

"octree redefinition − skip", "try to skip redefinition of",<br />

"empty buffer"<br />

};<br />

60<br />

/* Constructor<br />

*/<br />

//********************************************************************************************************<br />

Parser::Parser( const char *fileName, Model *modelPtr, FILE *logging, int nesting, DefNode **def_hook )<br />

65 //********************************************************************************************************<br />

{<br />

model = modelPtr;<br />

nestingDepth = nesting;<br />

defTreeRoot = NULL;<br />

70 log = logging;<br />

// disable buffering of stdout<br />

setvbuf( stdout, NULL, _IONBF, 0 );<br />

75 defHook = (def_hook == NULL)? &defTreeRoot : def_hook;<br />

if( (scan = new Scanner( fileName )) == NULL )<br />

Error( NO_SCANNER );<br />

else<br />

80 scan−>getSymbol();<br />

85<br />

}<br />

fprintf( stdout, "parsing file ’%s’ ... \n", fileName );<br />

// parse model definition<br />

getModel();<br />

// do initialization<br />

buildModel();<br />

90 /* Destructor<br />

*/<br />

//******************<br />

Parser::~Parser()<br />

//******************<br />

95 { if( scan != NULL ) delete scan;<br />

100 }<br />

if( defTreeRoot != NULL )<br />

delDefs( defTreeRoot );


* Starting point for recursive descent parsing<br />

* of the scene definition file.<br />

* | | <br />

105 */<br />

//************************<br />

void Parser::getModel()<br />

//************************<br />

{<br />

110 Symbol symbl;<br />

Parser *subParser;<br />

while( scan−>sym != EOFSYM )<br />

{<br />

115 switch( scan−>sym )<br />

{<br />

// := include <br />

case INCLUDESYM: scan−>getSymbol();<br />

// get include file<br />

120 if( scan−>sym != STRINGSYM ) Error( MISSING_FILENAME );<br />

)<br />

if( nestingDepth < MAX_NESTING_DEPTH &&<br />

(subParser=new Parser(scan−>string, model, log, nestingDepth+1, defHook))!=NULL<br />

delete subParser;<br />

125 else<br />

Error( FILE_NESTING_TOO_DEEP );<br />

130<br />

scan−>getSymbol();<br />

break;<br />

// := def { ... definition ... }<br />

case DEFSYM: scan−>getSymbol();<br />

symbl = scan−>sym;<br />

135 // get identifier<br />

scan−>getSymbol();<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

// set definition<br />

140 setDef( symbl, scan−>string, defHook );<br />

break;<br />

// := scene { ... definition ... }<br />

case SCENESYM: scan−>getSymbol();<br />

145 getScene();<br />

break;<br />

150 }<br />

}<br />

}<br />

default: Error( SYNTAX_ERROR );<br />

/* Parse scene definition<br />

* scene { | | | }<br />

155 */<br />

//************************<br />

void Parser::getScene()<br />

//************************<br />

{<br />

160 // check for scene block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

165 while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

switch( scan−>sym )<br />

{<br />

// := view { ... definition ... }<br />

170 case VIEWSYM: scan−>getSymbol();<br />

getView();<br />

break;<br />

// := objects { ... definition ... }<br />

175 case OBJECTSSYM: scan−>getSymbol();<br />

getObjects();<br />

break;<br />

// := octree { ... definition ... }<br />

180 case OCTREESYM: scan−>getSymbol();<br />

getOctree();<br />

break;<br />

// := lights { ... definition ... }<br />

185 case LIGHTSSYM: scan−>getSymbol();<br />

getLights();<br />

break;<br />

default: Error( SYNTAX_ERROR );<br />

190 }<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

195<br />

/* Parse point of view definition<br />

* := view { | | | | }<br />

*/<br />

//***********************


200 void Parser::getView()<br />

//***********************<br />

{<br />

// check for view block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

205 // ... and skip<br />

scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

210 switch( scan−>sym )<br />

{<br />

// := location <br />

case LOCATIONSYM: scan−>getSymbol();<br />

model−>scene.view.location = getVector();<br />

215 break;<br />

// := direction <br />

case DIRECTIONSYM: scan−>getSymbol();<br />

model−>scene.view.direction = getVector();<br />

220 break;<br />

// := up <br />

case UPSYM: scan−>getSymbol();<br />

model−>scene.view.up = getVector();<br />

225 break;<br />

// := right <br />

case RIGHTSYM: scan−>getSymbol();<br />

model−>scene.view.right = getVector();<br />

230 break;<br />

// := supersample <br />

case SUPERSAMPLESYM: scan−>getSymbol();<br />

if( scan−>sym != TRUESYM && scan−>sym != FALSESYM )<br />

235 Error( MISSING_KEYWORD, "for mode: [true, false]" );<br />

model−>scene.view.superSampling = (scan−>sym == TRUESYM);<br />

scan−>getSymbol();<br />

break;<br />

240 default: Error( SYNTAX_ERROR );<br />

}<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

245 }<br />

/* Parse octree definition<br />

* := octree { | }<br />

*/<br />

250 //*************************<br />

void Parser::getOctree()<br />

//*************************<br />

{<br />

Real size = 0.0;<br />

255 Vector min = nullVector;<br />

// check for octree block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

260 scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

switch( scan−>sym )<br />

265 {<br />

// := size <br />

case SIZESYM: scan−>getSymbol();<br />

size = getReal();<br />

break;<br />

270<br />

275<br />

// := location <br />

case LOCATIONSYM: scan−>getSymbol();<br />

min = getVector();<br />

break;<br />

default: Error( SYNTAX_ERROR );<br />

}<br />

}<br />

// skip right curly brace<br />

280 scan−>getSymbol();<br />

// return on illegal size<br />

if( size octree == NULL )<br />

{<br />

if( (model−>octree = new Octree) == NULL ) Error( NO_OCTREE );<br />

initOctree( model−>octree );<br />

290 model−>octree−>min = min;<br />

translateVector( min, size, model−>octree−>max );<br />

295 }<br />

}<br />

else Error( OCTREE_REDEFINITION );<br />

/* Parse lights block<br />

* := lights { | }<br />

*/


300 //*************************<br />

void Parser::getLights()<br />

//*************************<br />

{<br />

Light *curLight;<br />

305<br />

310<br />

315<br />

// check for light block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

// get all light sources<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

if( (curLight = model−>addLight()) == NULL ) Error( NO_LIGHT );<br />

switch( scan−>sym )<br />

{<br />

// := point { ... definition ... }<br />

case POINTSYM: scan−>getSymbol();<br />

320 getPointLight( (PointLight *)curLight );<br />

break;<br />

// := spot { ... definition ... }<br />

case SPOTSYM: scan−>getSymbol();<br />

325 getSpotLight( (SpotLight *)curLight );<br />

break;<br />

default: Error( SYNTAX_ERROR, "[illegal light source]");<br />

}<br />

330 }<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

335 /* Parse point light definition<br />

* := point { (color) | | }<br />

*/<br />

//************************************************<br />

void Parser::getPointLight( PointLight *light )<br />

340 //************************************************<br />

{<br />

bool useDef;<br />

// check for point light block begin ...<br />

345 if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

// initialize<br />

350 light−>location = nullVector;<br />

light−>color = white;<br />

// get point light data<br />

while( scan−>sym != CURLBRACERIGHT )<br />

355 {<br />

// := use color <br />

if( (useDef = (scan−>sym == USESYM)) )<br />

{<br />

scan−>getSymbol();<br />

360 if( scan−>sym != COLORSYM ) Error( SYNTAX_ERROR, "[illegal use]" );<br />

}<br />

switch( scan−>sym )<br />

{<br />

365 // := color { ... definition ... }<br />

case COLORSYM: scan−>getSymbol();<br />

getColor( &light−>color, useDef );<br />

break;<br />

370 // := location <br />

case LOCATIONSYM: scan−>getSymbol();<br />

light−>location = getVector();<br />

break;<br />

375 default: Error( SYNTAX_ERROR );<br />

}<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

380<br />

}<br />

light−>type = POINTLIGHT;<br />

/* Parse spot light definition<br />

385 * := spot { (color) | | | | }<br />

*/<br />

//**********************************************<br />

void Parser::getSpotLight( SpotLight *light )<br />

//**********************************************<br />

390 {<br />

bool useDef;<br />

Real angle;<br />

// check for spot light block begin ...<br />

395 if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

// initialize with default


400 light−>location = nullVector;<br />

light−>color = white;<br />

light−>direction = baseVector[Z];<br />

light−>spotBo<strong>und</strong> = 0.0; // cosine of half of beam spread (here: 90 degrees)<br />

405 // get all light sources<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

// := use color <br />

if( (useDef = (scan−>sym == USESYM)) )<br />

410 {<br />

scan−>getSymbol();<br />

if( scan−>sym != COLORSYM ) Error( SYNTAX_ERROR, "[illegal use]" );<br />

}<br />

415 switch( scan−>sym )<br />

{<br />

// := color { ... definition ... }<br />

case COLORSYM: scan−>getSymbol();<br />

getColor( &light−>color, useDef );<br />

420 break;<br />

// := location <br />

case LOCATIONSYM: scan−>getSymbol();<br />

light−>location = getVector();<br />

425 break;<br />

// := direction <br />

case DIRECTIONSYM: scan−>getSymbol();<br />

light−>direction = getVector();<br />

430 normalizeVector( light−>direction, light−>direction );<br />

// negate vector for faster calulation at run time<br />

// [q.v.] shade.cpp −> shadowTest()<br />

negateVector( light−>direction, light−>direction );<br />

435 break;<br />

// := angle [0,180]<br />

case ANGLESYM: scan−>getSymbol();<br />

angle = getReal();<br />

440 if( angle < 0.0 || angle > 180.0 )<br />

Error( NUMBER_OUT_OF_BOUNDS, "[0 180]" );<br />

// determine spot bo<strong>und</strong><br />

light−>spotBo<strong>und</strong> = cos( DEGToRAD( angle / 2.0 ) );<br />

445 break;<br />

default: Error( SYNTAX_ERROR );<br />

}<br />

}<br />

450 // skip right curly brace<br />

scan−>getSymbol();<br />

455<br />

}<br />

light−>type = SPOTLIGHT;<br />

/* Parse objects block<br />

* := objects { | | | | | | }<br />

*/<br />

//**************************<br />

460 void Parser::getObjects()<br />

//**************************<br />

{<br />

Symbol objType;<br />

Vector size;<br />

465 Object *curObject, *prototype;<br />

VertexBuffer vertexBuff;<br />

PatchBuffer patchBuff;<br />

PatchItem patchItem;<br />

470 unsigned int i, j, vertexIdx;<br />

char msgBuff[30];<br />

bool useDef;<br />

PatchBuffer::iterator it;<br />

475 // check for objects block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

480 // get all objects of scene<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

objType = scan−>sym;<br />

scan−>getSymbol();<br />

485<br />

490<br />

// check for object begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

switch( objType )<br />

{<br />

// := sphere { }<br />

case SPHERESYM: curObject = new Sphere( /*size.x*/ );<br />

495 break;<br />

// := cone { size }<br />

case CONESYM: // check for keyword ’size’ ...<br />

if( scan−>sym != SIZESYM ) Error( MISSING_KEYWORD, "’size’" );


500 // ... and skip<br />

scan−>getSymbol();<br />

size.x = getReal();<br />

size.y = getReal();<br />

size.z = getReal();<br />

505 curObject = new Cone( size.x, size.y, size.z );<br />

break;<br />

// := cylinder { }<br />

case CYLINDERSYM: curObject = new Cylinder();<br />

510 break;<br />

515<br />

// := box { }<br />

case BOXSYM: curObject = new Box();<br />

break;<br />

// := plane { }<br />

case PLANESYM: curObject = new Plane();<br />

break;<br />

520 // := bezier { (vertex) | vertex (patch) | patch }<br />

case BEZIERSYM: if( (useDef = (scan−>sym == USESYM)) ) scan−>getSymbol();<br />

if( scan−>sym != VERTEXSYM ) Error( MISSING_KEYWORD, "’vertex’" );<br />

scan−>getSymbol();<br />

vertexBuff.clear();<br />

525 getVertex( &vertexBuff, useDef );<br />

if( (useDef = (scan−>sym == USESYM)) ) scan−>getSymbol();<br />

if( scan−>sym != PATCHSYM ) Error( MISSING_KEYWORD, "’patch’" );<br />

scan−>getSymbol();<br />

530 patchBuff.clear();<br />

getPatch( &patchBuff, useDef );<br />

535<br />

// check patchbuffer<br />

if( !(it = patchBuff.begin()).isOK() ) Error( NO_OBJECT, "[no patch]" );<br />

// ititialize pointer to first bezierobject<br />

prototype = NULL;<br />

/**<br />

540 * Create bezier surfaces<br />

*/<br />

do {<br />

patchItem = it.get();<br />

545 // alloc new bezier surface ...<br />

curObject = new BezierSurface( patchItem.size.m − 1, patchItem.size.n − 1 );<br />

if( curObject == NULL ) Error( NO_OBJECT );<br />

// ... and fill with control points got from vertex buffer<br />

550 for( i = 0; i < patchItem.size.m; ++i )<br />

for( j = 0; j < patchItem.size.n; ++j )<br />

{<br />

// get vertex number from patch<br />

vertexIdx = patchItem.patchData[i * patchItem.size.m + j];<br />

555<br />

// set vertex if number is valid<br />

if( vertexIdx > 0 && vertexIdx sym != CURLBRACERIGHT ) Error( MISSING_BRACE );<br />

scan−>getSymbol();<br />

580 }<br />

else<br />

{ // copy data from prototype<br />

curObject−>ambience = prototype−>ambience;<br />

curObject−>texture = prototype−>texture;<br />

585 curObject−>toObject = prototype−>toObject;<br />

curObject−>toWorld = prototype−>toWorld;<br />

// register texture reference<br />

curObject−>texture−>registerHandle();<br />

}<br />

590<br />

/**<br />

* Build search tree<br />

*/<br />

if( log != NULL )<br />

595 fprintf( log, "build search tree for patch %d ... ", it.getPos() + 1 );<br />

((BezierSurface *)curObject)−>buildSearchTree();<br />

if( log != NULL )


600 fprintf( log, "OK (%d divisions)\n",<br />

((BezierSurface *)curObject)−>getDivisions() );<br />

// add object to model<br />

if( !model−>addObject( curObject ) )<br />

605 {<br />

delete curObject;<br />

Error( NO_OBJECT );<br />

}<br />

} while( it.next() );<br />

610 continue;<br />

// := csg { ... definition ... }<br />

case CSGSYM: break; // TODO: CSG object combination<br />

615 default: Error( SYNTAX_ERROR, "[no such object]" );<br />

}<br />

if( curObject == NULL ) Error( NO_OBJECT );<br />

620 // get generic object data<br />

getObjectData( curObject );<br />

// skip right curly brace − one object’s end<br />

if( scan−>sym != CURLBRACERIGHT ) Error( MISSING_BRACE );<br />

625 scan−>getSymbol();<br />

// add object to model<br />

if( !model−>addObject( curObject ) )<br />

{<br />

630 delete curObject;<br />

Error( NO_OBJECT );<br />

}<br />

}<br />

// skip right curly brace − objects block end<br />

635 scan−>getSymbol();<br />

}<br />

/* Parse object data<br />

* := (texture) | | | <br />

640 */<br />

//*********************************************<br />

void Parser::getObjectData( Object *object )<br />

//*********************************************<br />

{<br />

645 TextureData textureData = defaultTextureData; // initialize data buffer<br />

bool objectDataEnd = false;<br />

while( scan−>sym != CURLBRACERIGHT )//!objectDataEnd )<br />

{<br />

650 switch( scan−>sym )<br />

{<br />

// := use (texture) <br />

case USESYM: scan−>getSymbol();<br />

switch( scan−>sym )<br />

655 {<br />

case TEXTURESYM: scan−>getSymbol();<br />

getTextureData( &textureData, true );<br />

break;<br />

660 default: Error( SYNTAX_ERROR, "[illegal use]" );<br />

}<br />

break;<br />

// := ambience <br />

665 case AMBIENCESYM: scan−>getSymbol();<br />

for( const ColorChannel *i = RGBfirst; *i != ALPHA; i++ )<br />

object−>ambience.channel[*i] = getReal();<br />

break;<br />

670 // := transform { ... definition ... }<br />

case TRANSFORMSYM: scan−>getSymbol();<br />

getTransform( &object−>toObject );<br />

break;<br />

675 // := texture { ... definition ... }<br />

case TEXTURESYM: scan−>getSymbol();<br />

getTextureData( &textureData );<br />

break;<br />

680 default: Error( SYNTAX_ERROR, "[illegal object data]" ); //objectDataEnd = true;<br />

}<br />

}<br />

// evaluate inverse matrix<br />

685 if( !invertMatrix( object−>toObject, &object−>toWorld ) )<br />

Error( NO_INVERSE_MATRIX );<br />

690 }<br />

// hook appropriate texture object<br />

buildTexture( object, &textureData );<br />

/* Parse transformation block<br />

* := transform { | | | }<br />

695 */<br />

//*****************************************************<br />

void Parser::getTransform( Matrix *transformMatrix )<br />

//*****************************************************<br />

{


700 Vector vec;<br />

Real sh1, sh2;<br />

Coordinate coord;<br />

// check for transform block begin ...<br />

705 if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

710 {<br />

switch( scan−>sym )<br />

{<br />

// := move <br />

case MOVESYM: scan−>getSymbol();<br />

715 vec = getVector();<br />

negateVector( vec, vec );<br />

translateMatrix( transformMatrix, vec );<br />

break;<br />

720 // := rotate <br />

// . o O (rotate about − in this order!)<br />

case ROTATESYM: scan−>getSymbol();<br />

vec = getVector();<br />

negateVector( vec, vec );<br />

725 rotateMatrix( transformMatrix, vec );<br />

break;<br />

// := scale <br />

// . o O (scale axis’ at )<br />

730 case SCALESYM: scan−>getSymbol();<br />

vec = getVector();<br />

vec.x = (vec.x != 0.0)? 1.0 / vec.x : 1.0;<br />

vec.y = (vec.y != 0.0)? 1.0 / vec.y : 1.0;<br />

vec.z = (vec.z != 0.0)? 1.0 / vec.z : 1.0;<br />

735 scaleMatrix( transformMatrix, vec );<br />

break;<br />

// := shear <br />

case SHEARSYM: scan−>getSymbol();<br />

740 coord = getCoordinate();<br />

sh1 = getReal();<br />

sh2 = getReal();<br />

shearMatrix( transformMatrix, coord, sh1, sh2 );<br />

break;<br />

745<br />

default: Error( SYNTAX_ERROR, "[illegal transformation]" );<br />

}<br />

}<br />

// skip right curly brace<br />

750 scan−>getSymbol();<br />

}<br />

/* Parse material definition<br />

* := material { | | | | |<br />

755 | }<br />

*/<br />

//************************************************************<br />

void Parser::getMaterial( Material *material, bool useDef )<br />

//************************************************************<br />

760 { Real realVal;<br />

DefNode *defMaterial;<br />

Material tmp;<br />

Material *materialPtr = (material == NULL)? &tmp : material;<br />

765 const ColorChannel *i;<br />

770<br />

if( useDef )<br />

{<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

// try to get material from definition tree<br />

if( (defMaterial = getDef( MATERIALSYM, scan−>string, defHook )) == NULL )<br />

Error( MATERIAL_NOT_DEFINED, scan−>string );<br />

775 *materialPtr = defMaterial−>materialDef;<br />

scan−>getSymbol();<br />

}<br />

else<br />

{<br />

780 if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

785 switch( scan−>sym )<br />

{<br />

// := ambient <br />

case AMBIENTSYM: scan−>getSymbol();<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

790 {<br />

realVal = getReal();<br />

if( realVal > 1.0 ) realVal = 1.0;<br />

if( realVal < 0.0 ) realVal = 0.0;<br />

materialPtr−>ambient.channel[*i] = realVal;<br />

795 }<br />

break;<br />

// := diffuse <br />

case DIFFUSESYM: scan−>getSymbol();


800 for( i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

realVal = getReal();<br />

if( realVal > 1.0 ) realVal = 1.0;<br />

if( realVal < 0.0 ) realVal = 0.0;<br />

805 materialPtr−>diffuse.channel[*i] = realVal;<br />

}<br />

break;<br />

// := specular <br />

810 case SPECULARSYM: scan−>getSymbol();<br />

for( i = RGBfirst; *i != ALPHA; i++ )<br />

{<br />

realVal = getReal();<br />

if( realVal > 1.0 ) realVal = 1.0;<br />

815 if( realVal < 0.0 ) realVal = 0.0;<br />

materialPtr−>specular.channel[*i] = realVal;<br />

}<br />

break;<br />

820 // := specularExp <br />

case SPECULAREXPSYM: scan−>getSymbol();<br />

realVal = getReal();<br />

if( realVal < 1.0 ) realVal = 1.0;<br />

materialPtr−>specularExp = realVal;<br />

825 break;<br />

// := reflect <br />

case REFLECTSYM: scan−>getSymbol();<br />

realVal = getReal();<br />

830 if( realVal > 1.0 ) realVal = 1.0;<br />

if( realVal < 0.0 ) realVal = 0.0;<br />

materialPtr−>reflect = realVal;<br />

break;<br />

835 // := transparency <br />

case TRANSPARENCYSYM: scan−>getSymbol();<br />

realVal = getReal();<br />

if( realVal > 1.0 ) realVal = 1.0;<br />

if( realVal < 0.0 ) realVal = 0.0;<br />

840 materialPtr−>transparency = realVal;<br />

break;<br />

// := index <br />

case INDEXSYM: scan−>getSymbol();<br />

845 realVal = getReal();<br />

if( realVal < 1.0 ) realVal = 1.0;<br />

materialPtr−>refractIndex = realVal;<br />

break;<br />

850 default: Error( SYNTAX_ERROR, "[illegal material data]" );<br />

}<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

855 }<br />

}<br />

/* Parse texture data<br />

* := texture { (color | material) | | | | | |<br />

860 * | | | | | }<br />

*/<br />

//*********************************************************************<br />

void Parser::getTextureData( TextureData *textureData, bool useDef )<br />

//*********************************************************************<br />

865 { DefNode *defTextureData;<br />

TextureData tmp;<br />

TextureData *textureDataPtr = (textureData == NULL)? &tmp : textureData;<br />

Symbol symbl;<br />

870 Vector size;<br />

875<br />

if( useDef )<br />

{<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

// try to get texture data from definition tree<br />

if( (defTextureData = getDef( TEXTURESYM, scan−>string, defHook )) == NULL )<br />

Error( TEXTURE_NOT_DEFINED, scan−>string );<br />

880 *textureDataPtr = defTextureData−>textureDataDef;<br />

scan−>getSymbol();<br />

}<br />

else<br />

{<br />

885 // check for texture block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

890 while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

symbl = scan−>sym;<br />

scan−>getSymbol();<br />

895 // check for texture modifier begin ...<br />

switch( symbl )<br />

{<br />

case BUMPSYM: case BITMAPSYM: case REFLECTIONSYM:<br />

case CHECKERSYM: case BRICKSYM: case MARBLESYM: case WOODSYM:


900 // ... and skip<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

scan−>getSymbol();<br />

905 }<br />

default: break;<br />

// check for size of some texture types<br />

switch( symbl )<br />

{<br />

910 case BUMPSYM: case CHECKERSYM: case BRICKSYM: case WOODSYM:<br />

915 default: break;<br />

}<br />

if( scan−>sym != SIZESYM ) Error( MISSING_KEYWORD, "’size’" );<br />

scan−>getSymbol();<br />

switch( symbl )<br />

{<br />

920 // := use (material | color) <br />

case USESYM: //scan−>getSymbol();<br />

switch( scan−>sym )<br />

{<br />

case MATERIALSYM:<br />

925 scan−>getSymbol();<br />

getMaterial(&(textureDataPtr−>material[textureDataPtr−>matPtr]),true<br />

);<br />

textureDataPtr−>matPtr = (textureDataPtr−>matPtr+1)%MAX_TEX_CHNL;<br />

break;<br />

930 case COLORSYM:<br />

scan−>getSymbol();<br />

getColor( &(textureDataPtr−>color[textureDataPtr−>colPtr]), true );<br />

textureDataPtr−>colPtr = (textureDataPtr−>colPtr+1)%MAX_TEX_CHNL;<br />

break;<br />

935<br />

default: Error( SYNTAX_ERROR, "[illegal use]" );<br />

}<br />

break;<br />

940 case MATERIALSYM: //scan−>getSymbol();<br />

getMaterial( &(textureDataPtr−>material[textureDataPtr−>matPtr]) );<br />

textureDataPtr−>matPtr = (textureDataPtr−>matPtr+1)%MAX_TEX_CHNL;<br />

break;<br />

945 case COLORSYM: //scan−>getSymbol();<br />

getColor( &(textureDataPtr−>color[textureDataPtr−>colPtr]) );<br />

textureDataPtr−>colPtr = (textureDataPtr−>colPtr+1)%MAX_TEX_CHNL;<br />

break;<br />

950 // . o O (texture transformations)<br />

// := transform { ... definition ... }<br />

case TRANSFORMSYM: getTransform( &textureDataPtr−>texture.transform );<br />

break;<br />

955 // . o O (color turbulence)<br />

// := turbulence { ... definition ... }<br />

case TURBULENCESYM: getTurbulence( &textureDataPtr−>texture.turbulence );<br />

break;<br />

960 // . o O (bump mapping)<br />

// := bump { size [] [] }<br />

case BUMPSYM: textureDataPtr−>bump.bumpSize = getReal();<br />

if( scan−>sym == FILESYM )<br />

965 getMapData( &textureDataPtr−>bump.mapperInfo );<br />

if( scan−>sym == TURBULENCESYM )<br />

{<br />

scan−>getSymbol();<br />

970 getTurbulence( &textureDataPtr−>bump.turbulence );<br />

}<br />

break;<br />

975 /**<br />

* Textures<br />

*/<br />

// := bitmap { }<br />

case BITMAPSYM: getMapData( &textureDataPtr−>bitmap );<br />

980 textureDataPtr−>textureType = BITMAPSYM;<br />

break;<br />

// := reflection { }<br />

case REFLECTIONSYM: getMapData( &textureDataPtr−>reflection );<br />

985 textureDataPtr−>textureType = REFLECTIONSYM;<br />

break;<br />

// := checker { size ’X’ ’Y’ }<br />

case CHECKERSYM: // get checker size<br />

990 textureDataPtr−>checker.checkerSize = absVal( getReal() );<br />

if( scan−>sym != XSYM ) Error( COORD_EXPECTED, "{X}" );<br />

scan−>getSymbol();<br />

textureDataPtr−>checker.quota[X] = absVal( getReal() );<br />

if( scan−>sym != YSYM ) Error( COORD_EXPECTED, "{Y}" );<br />

995 scan−>getSymbol();<br />

textureDataPtr−>checker.quota[Y] = absVal( getReal() );<br />

textureDataPtr−>textureType = CHECKERSYM;


1000<br />

break;<br />

// := brick { size }<br />

case BRICKSYM: // get brick size<br />

if( (size.x = getReal()) sym != TURBULENCESYM ) Error( MISSING_KEYWORD, "’turbulence’" );<br />

scan−>getSymbol();<br />

getTurbulence( &textureDataPtr−>marble.turbulence );<br />

textureDataPtr−>textureType = MARBLESYM;<br />

break;<br />

// := wood { size }<br />

1020 case WOODSYM: // get wood sharpness<br />

textureDataPtr−>wood.sharpness = getReal();<br />

if( scan−>sym != TURBULENCESYM ) Error( MISSING_KEYWORD, "’turbulence’" );<br />

scan−>getSymbol();<br />

getTurbulence( &textureDataPtr−>wood.turbulence );<br />

1025<br />

1030 }<br />

textureDataPtr−>textureType = WOODSYM;<br />

break;<br />

default: Error( SYNTAX_ERROR, "[illegal texture data]" );<br />

// check for texture’s block end<br />

switch( symbl )<br />

{<br />

1035 case BUMPSYM: case BITMAPSYM: case REFLECTIONSYM:<br />

case CHECKERSYM: case BRICKSYM: case MARBLESYM: case WOODSYM:<br />

// skip right curly brace<br />

if( scan−>sym != CURLBRACERIGHT ) Error( MISSING_BRACE );<br />

1040 scan−>getSymbol();<br />

default: break;<br />

}<br />

}<br />

1045 // skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

}<br />

1050 /* Parse vertex block<br />

* := vertex { ( | ) ... }<br />

*/<br />

//****************************************************************<br />

void Parser::getVertex( VertexBuffer *vertexBuff, bool useDef )<br />

1055 //****************************************************************<br />

{<br />

int fileVertices;<br />

char msgBuff[25];<br />

Vector vertex;<br />

1060 FILE *vertexFile;<br />

DefNode *defVtxBuff;<br />

if( useDef )<br />

{<br />

1065 if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

1070<br />

// try to get vertexbuffer from definition tree<br />

if( (defVtxBuff = getDef( VERTEXSYM, scan−>string, defHook )) == NULL )<br />

Error( BUFFER_NOT_DEFINED, scan−>string );<br />

// copy content of referenced vertexbuffer<br />

if( defVtxBuff−>vertexBuffer−>length() && vertexBuff != NULL )<br />

{<br />

VertexBuffer::iterator it = defVtxBuff−>vertexBuffer−>begin();<br />

1075 do{<br />

*(vertexBuff−>append()) = it.get();<br />

} while( it.next() );<br />

}<br />

scan−>getSymbol();<br />

1080 }<br />

else<br />

{<br />

// check for vertex block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

1085 // ... and skip<br />

scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

1090 if( scan−>sym == FILESYM )<br />

{ // get vertex definition from file<br />

scan−>getSymbol();<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_FILENAME );<br />

1095 if( vertexBuff != NULL )<br />

{<br />

// try to open vertex file<br />

if( (vertexFile = fopen( scan−>string, "r")) == NULL ) Error( NO_FILE, scan−>string );


1100 if( log != NULL )<br />

fprintf( log, "import vertices from file ’%s’ ... ", scan−>string );<br />

/**<br />

* Read vertices from file<br />

1105 */<br />

for( fileVertices = 0; !feof( vertexFile ); ++fileVertices )<br />

{<br />

if( fscanf( vertexFile, " %lf %lf %lf ", &vertex.x, &vertex.y, &vertex.z ) < 3 )<br />

{<br />

1110 snprintf( msgBuff, 25, "[%d vertices ok]", fileVertices );<br />

fprintf( stderr, "\n\t" );<br />

Error( BAD_FORMAT, msgBuff );<br />

}<br />

// save vertex<br />

1115 *(vertexBuff−>append()) = vertex;<br />

}<br />

fclose( vertexFile );<br />

if( log != NULL )<br />

1120 fprintf( log, "got %d vertices\n", fileVertices );<br />

}<br />

// skip filename<br />

scan−>getSymbol();<br />

}<br />

1125 else if( scan−>sym == LSS )<br />

{ // get vertex definition<br />

if( vertexBuff != NULL )<br />

*(vertexBuff−>append()) = getVector();<br />

else<br />

1130 getVector();<br />

}<br />

else<br />

Error( SYNTAX_ERROR );<br />

}<br />

1135 // skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

if( vertexBuff != NULL && vertexBuff−>length() == 0 )<br />

Error( EMPTY_BUFFER, "[vertex]" );<br />

1140 }<br />

/* Parse patch block<br />

* := patch { size ( | ’’) }<br />

*/<br />

1145 //*************************************************************<br />

void Parser::getPatch( PatchBuffer *patchBuff, bool useDef )<br />

//*************************************************************<br />

{<br />

int nrVertices, i;<br />

1150 int filePatches;<br />

char msgBuff[60];<br />

FILE *patchFile;<br />

Patch curPatch;<br />

PatchSize patchSize;<br />

1155 DefNode *defPchBuff;<br />

1160<br />

if( useDef )<br />

{<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

// try to get patchbuffer from definition tree<br />

if( (defPchBuff = getDef( PATCHSYM, scan−>string, defHook )) == NULL )<br />

Error( BUFFER_NOT_DEFINED, scan−>string );<br />

1165 // copy content of referenced patchbuffer<br />

patchBuff−>appendPatchBuffer( defPchBuff−>patchBuffer );<br />

scan−>getSymbol();<br />

}<br />

1170 else<br />

{<br />

// check for patch block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

1175 scan−>getSymbol();<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

if( scan−>sym != SIZESYM ) Error( MISSING_KEYWORD, "’size’" );<br />

1180 scan−>getSymbol();<br />

// get patch dimensions<br />

patchSize.m = getNumber( 1, SURFACE_MAX_DEGREE );<br />

patchSize.n = getNumber( 1, SURFACE_MAX_DEGREE );<br />

1185 nrVertices = patchSize.m * patchSize.n;<br />

if( patchSize.m > patchSize.n )<br />

swapXOR( patchSize.m, patchSize.n );<br />

/**<br />

1190 * Get patches from file<br />

*/<br />

if( scan−>sym == FILESYM )<br />

{<br />

scan−>getSymbol();<br />

1195 if( scan−>sym != STRINGSYM ) Error( MISSING_FILENAME );<br />

if( patchBuff != NULL )<br />

{


try to open patch file<br />

1200 if( (patchFile = fopen( scan−>string, "r")) == NULL )<br />

Error( NO_FILE, scan−>string );<br />

1205<br />

if( log != NULL )<br />

fprintf( log, "import patches from file ’%s’ ... ", scan−>string );<br />

// read patches from file<br />

for( filePatches = 0; !feof( patchFile ); ++filePatches )<br />

{<br />

// try to get new patch<br />

1210 if( (curPatch = patchBuff−>getNewPatch( patchSize )) == NULL )<br />

Error( NO_PATCH );<br />

// read vertex order from file<br />

for( i = 0; i < nrVertices; ++i )<br />

1215 if( fscanf( patchFile, " %d ", &curPatch[i] ) < 1 )<br />

{<br />

snprintf( msgBuff, 60, "[%d patch(es) of size ok]",<br />

filePatches, patchSize.m, patchSize.n );<br />

fprintf( stderr, "\n\t" );<br />

1220 Error( BAD_FORMAT, msgBuff );<br />

}<br />

}<br />

fclose( patchFile );<br />

if( log != NULL )<br />

1225 fprintf( log, "got %d patches\n", filePatches );<br />

}<br />

// skip filename<br />

scan−>getSymbol();<br />

}<br />

1230 else if( scan−>sym == LSS )<br />

{<br />

if( patchBuff != NULL )<br />

// try to get new patch<br />

if( (curPatch = patchBuff−>getNewPatch( patchSize )) == NULL )<br />

1235 Error( NO_PATCH );<br />

scan−>getSymbol();<br />

// get patch data<br />

1240 for( i = 0; i < nrVertices; ++i )<br />

if( patchBuff != NULL )<br />

curPatch[i] = getNumber( 0, INT_MAX );<br />

else<br />

getNumber( 0, INT_MAX );<br />

1245<br />

// check patchdef termination<br />

if( scan−>sym != GTR )<br />

{<br />

if( scan−>sym == NUMBER )<br />

1250 snprintf( msgBuff, 60, "[patch too long − size of expected]",<br />

patchSize.m, patchSize.n );<br />

else<br />

snprintf( msgBuff, 60, "[end mark ’>’ expected]" );<br />

1255 Error( BAD_FORMAT, msgBuff );<br />

}<br />

scan−>getSymbol();<br />

}<br />

else<br />

1260 Error( SYNTAX_ERROR );<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

1265 if( patchBuff != NULL && patchBuff−>length() == 0 )<br />

Error( EMPTY_BUFFER, "[patch]" );<br />

}<br />

/* Parse color definition<br />

1270 * := color { | | }<br />

*/<br />

//***************************************************<br />

void Parser::getColor( Color *color, bool useDef )<br />

//***************************************************<br />

1275 { DefNode *defColor;<br />

Color tmp;<br />

Color *colorPtr = (color == NULL)? &tmp : color;<br />

1280 if( useDef )<br />

{<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_NAME );<br />

// try to get color from definition tree<br />

1285 if( (defColor = getDef( COLORSYM, scan−>string, defHook )) == NULL )<br />

Error( COLOR_NOT_DEFINED, scan−>string );<br />

*colorPtr = defColor−>colorDef;<br />

scan−>getSymbol();<br />

1290 }<br />

else<br />

{<br />

// check for color block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

1295 // ... and skip<br />

scan−>getSymbol();<br />

// initialize color


1300<br />

*color = black;<br />

while( scan−>sym != CURLBRACERIGHT )<br />

{<br />

switch( scan−>sym )<br />

{<br />

1305 // := red <br />

case REDSYM: scan−>getSymbol();<br />

colorPtr−>channel[RED] = getReal();<br />

break;<br />

1310 // := green <br />

case GREENSYM: scan−>getSymbol();<br />

colorPtr−>channel[GREEN] = getReal();<br />

break;<br />

1315 // := blue <br />

case BLUESYM: scan−>getSymbol();<br />

colorPtr−>channel[BLUE] = getReal();<br />

break;<br />

1320 default: Error( SYNTAX_ERROR, "[illegal color channel]" );<br />

}<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

1325 }<br />

}<br />

/* Parse pixmap data<br />

* := ’file’ | | <br />

1330 */<br />

//***********************************************************<br />

void Parser::getMapData( PointMapper::ParseInfo *mapData )<br />

//***********************************************************<br />

{<br />

1335 // check for keyword ’file’ ...<br />

if( scan−>sym != FILESYM ) Error( MISSING_KEYWORD, "’file’" );<br />

scan−>getSymbol();<br />

// ... and get filename<br />

if( scan−>sym != STRINGSYM ) Error( MISSING_FILENAME );<br />

1340 strcpy( mapData−>filename, scan−>string );<br />

scan−>getSymbol();<br />

1345<br />

// initialize scaling factors<br />

mapData−>scale = oneVector;<br />

// check for map scaling<br />

// := ’scale’ <br />

if( scan−>sym == SCALESYM )<br />

{<br />

1350 scan−>getSymbol();<br />

mapData−>scale.x = getReal();<br />

mapData−>scale.y = getReal();<br />

}<br />

1355 // get map layout<br />

// := ’tile’ | ’stretch’<br />

switch( scan−>sym )<br />

{<br />

case TILESYM: mapData−>layout = PointMapper::LAYOUT_TILE;<br />

1360 break;<br />

case STRETCHSYM: mapData−>layout = PointMapper::LAYOUT_STRETCH;<br />

break;<br />

1365 default: Error( MISSING_KEYWORD, "for layout: [tile | stretch]" );<br />

}<br />

scan−>getSymbol();<br />

}<br />

1370<br />

/* Parse turbulence definition<br />

* := { | | | }<br />

*/<br />

//************************************************************<br />

1375 void Parser::getTurbulence( Noise::Turbulence *turbulence )<br />

//************************************************************<br />

{<br />

Real scale;<br />

1380 // check for turbulence block begin ...<br />

if( scan−>sym != CURLBRACELEFT ) Error( MISSING_BRACE );<br />

// ... and skip<br />

scan−>getSymbol();<br />

1385 // initialize turbulence<br />

*turbulence = Noise::defaultTurbulence;<br />

// get turbulence properties<br />

while( scan−>sym != CURLBRACERIGHT )<br />

1390 {<br />

switch( scan−>sym )<br />

{<br />

// := ’size’ <br />

case SIZESYM: scan−>getSymbol();<br />

1395 turbulence−>size = getReal();<br />

break;<br />

// := ’scale’


case SCALESYM: scan−>getSymbol();<br />

1400 scale = getReal();<br />

turbulence−>scale = (scale != 0.0)? 1 / scale : 1.0;<br />

break;<br />

// := ’frequencies’ <br />

1405 case FREQUENCIESSYM: scan−>getSymbol();<br />

turbulence−>frequencies = (int)getReal();<br />

break;<br />

// := ’exponent’ <br />

1410 case EXPONENTSYM: scan−>getSymbol();<br />

turbulence−>exponent = getReal();<br />

break;<br />

default: Error( SYNTAX_ERROR, "[illegal turbulence data]" );<br />

1415 }<br />

}<br />

// skip right curly brace<br />

scan−>getSymbol();<br />

}<br />

1420<br />

1425<br />

/**<br />

* Auxilliary parsing functions<br />

*/<br />

/* Parse a vector definition<br />

* := ’’<br />

*/<br />

//**************************<br />

1430 Vector Parser::getVector()<br />

//**************************<br />

{<br />

Vector v;<br />

1435 if( scan−>sym == LSS )<br />

{<br />

scan−>getSymbol();<br />

v.x = getReal();<br />

v.y = getReal();<br />

1440 v.z = getReal();<br />

if( scan−>sym == GTR ) scan−>getSymbol();<br />

else Error( BAD_VECTOR );<br />

}<br />

else Error( BAD_VECTOR );<br />

1445<br />

}<br />

return v;<br />

/* Parse signed number or real number<br />

1450 * := [+|−] . o O (NUMBER notified by scanner)<br />

* := [+|−] . o O (REAL notified by scanner)<br />

*/<br />

//***********************<br />

Real Parser::getReal()<br />

1455 //***********************<br />

{<br />

Real realVal;<br />

switch( scan−>sym )<br />

1460 {<br />

case PLUS: realVal = 1.0;<br />

scan−>getSymbol();<br />

break;<br />

1465 case MINUS: realVal = −1.0;<br />

scan−>getSymbol();<br />

break;<br />

1470 }<br />

default: realVal = 1.0;<br />

if( scan−>sym == REAL || scan−>sym == NUMBER )<br />

{<br />

realVal *= (scan−>sym == REAL)? scan−>real : scan−>number;<br />

1475 scan−>getSymbol();<br />

return realVal;<br />

}<br />

else Error( REAL_EXPECTED );<br />

return 0;<br />

1480 }<br />

/* Parse a signed number and test for given bo<strong>und</strong>s<br />

* := [+|−] . o O (NUMBER notified by scanner)<br />

*/<br />

1485 //********************************************************<br />

int Parser::getNumber( int lowerBo<strong>und</strong>, int upperBo<strong>und</strong> )<br />

//********************************************************<br />

{<br />

char bo<strong>und</strong>s[25];<br />

1490 int numVal;<br />

switch( scan−>sym )<br />

{<br />

case PLUS: numVal = 1;<br />

1495 scan−>getSymbol();<br />

break;<br />

case MINUS: numVal = −1;


scan−>getSymbol();<br />

1500 break;<br />

}<br />

default: numVal = 1;<br />

1505 if( scan−>sym != NUMBER ) Error( NUMBER_EXPECTED );<br />

numVal *= scan−>number;<br />

// check bo<strong>und</strong>s on valid interval<br />

1510 if( lowerBo<strong>und</strong> upperBo<strong>und</strong> )<br />

{<br />

snprintf( bo<strong>und</strong>s, 25, "[%d %d]", lowerBo<strong>und</strong>, upperBo<strong>und</strong> );<br />

1515 Error( NUMBER_OUT_OF_BOUNDS, bo<strong>und</strong>s );<br />

}<br />

}<br />

scan−>getSymbol();<br />

return numVal;<br />

1520 }<br />

/* Parses a coordinate definition<br />

* := | | <br />

*/<br />

1525 //***********************************<br />

Coordinate Parser::getCoordinate()<br />

//***********************************<br />

{<br />

Symbol smbl = scan−>sym;<br />

1530<br />

scan−>getSymbol();<br />

switch( smbl )<br />

{<br />

1535 case XSYM: return X;<br />

case YSYM: return Y;<br />

case ZSYM: return Z;<br />

default: Error( COORD_EXPECTED, "{X,Y,Z}" );<br />

}<br />

1540 return X;<br />

}<br />

/**<br />

* Initializing functions<br />

1545 */<br />

// Build model components and determine<br />

// function for calculating intersections.<br />

//**************************<br />

void Parser::buildModel()<br />

1550 //**************************<br />

{<br />

// hook whitted−shading for default<br />

model−>trace = traceWhitted;<br />

1555 /**<br />

* Initialize octree − if defined<br />

*/<br />

if( model−>octree != NULL )<br />

{<br />

1560 Octree &octree = *model−>octree;<br />

Vector &viewLocation = model−>scene.view.location;<br />

octree.plane = viewLocation;<br />

1565 if( viewLocation.x < octree.min.x )<br />

octree.plane.x = octree.min.x;<br />

else if( viewLocation.x > octree.max.x )<br />

octree.plane.x = octree.max.x;<br />

1570 if( viewLocation.y < octree.min.y )<br />

octree.plane.y = octree.min.y;<br />

else if( viewLocation.y > octree.max.y )<br />

octree.plane.y = octree.max.y;<br />

1575 if( viewLocation.z < octree.min.z )<br />

octree.plane.z = octree.min.z;<br />

else if( viewLocation.z > octree.max.z )<br />

octree.plane.z = octree.max.z;<br />

1580 if( buildOctree( model ) )<br />

{<br />

fprintf( stdout, "OK\n" );<br />

// hook octree intersection function<br />

1585 model−>intersect = octreeIntersect;<br />

}<br />

else<br />

{<br />

fprintf( stdout, "failed!\n" );<br />

1590 Error( OCTREE_FAILED );<br />

}<br />

}<br />

else<br />

{<br />

1595 // hook default intersection function<br />

model−>intersect = Model::defaultIntersect;<br />

}<br />

}


1600<br />

// Build texture according to texture data<br />

//*****************************************************************<br />

void Parser::buildTexture( Object *object, TextureData *texDat )<br />

//*****************************************************************<br />

1605 {<br />

Texture *tex = NULL;<br />

Real surfaceDim[3];<br />

// get texture and set specific data ...<br />

1610 switch( texDat−>textureType )<br />

{<br />

case BUMPSYM: // plain texture<br />

tex = new BumpTexture();<br />

break;<br />

1615<br />

1620<br />

case BITMAPSYM: // Match mode for mapping hitpoints to 2D<br />

object−>matchMappingMode( texDat−>bitmap.mappingMode );<br />

tex = new BitmapTexture( texDat−>bitmap );<br />

break;<br />

case REFLECTIONSYM: // Match mode for mapping hitpoints to 2D<br />

// Reflection mapping requires spherical mapping mode.<br />

texDat−>reflection.mappingMode[X] =<br />

texDat−>reflection.mappingMode[Y] =<br />

1625 texDat−>reflection.mappingMode[Z] = PointMapper::MAP_SPHERIC;<br />

tex = new ReflectionTexture( texDat−>reflection );<br />

break;<br />

case CHECKERSYM: // Match mode for mapping hitpoints to 2D<br />

1630 object−>matchMappingMode( texDat−>checker.mapperInfo.mappingMode );<br />

tex = new CheckerTexture( texDat−>checker );<br />

break;<br />

case BRICKSYM: tex = new BrickTexture( texDat−>brick );<br />

1635 break;<br />

case MARBLESYM: tex = new MarbleTexture( texDat−>marble );<br />

break;<br />

1640 case WOODSYM: tex = new WoodTexture( texDat−>wood );<br />

break;<br />

default: break;<br />

}<br />

1645 if( tex == NULL ) Error( NO_TEXTURE );<br />

// set extra texture data for procedural textures<br />

switch( texDat−>textureType )<br />

{<br />

1650 case CHECKERSYM: case BRICKSYM: case MARBLESYM: case WOODSYM:<br />

((ProceduralTexture *)tex)−>setExtraColor( texDat−>color[MAX_TEX_CHNL − 1] );<br />

((ProceduralTexture *)tex)−>setExtraMaterial( texDat−>material[MAX_TEX_CHNL − 1] );<br />

1655 default: break;<br />

}<br />

// set object specific texture behavior<br />

object−>matchSurfaceDimension( surfaceDim );<br />

1660 object−>matchMappingMode( texDat−>bump.mapperInfo.mappingMode );<br />

// set attributes of bump mapping<br />

((BumpTexture *)tex)−>setBumpData( texDat−>bump );<br />

1665 // set general texture properties<br />

tex−>setupTextureSystem( texDat−>texture.transform );<br />

tex−>setTurbulence( texDat−>texture.turbulence );<br />

tex−>setBaseColor( texDat−>color[0] );<br />

tex−>setBaseMaterial( texDat−>material[0] );<br />

1670<br />

for( unsigned int i = X; i setSurfaceDimension( (Coordinate)i, surfaceDim[i] );<br />

// hook texture<br />

1675 object−>texture = tex;<br />

// register texture handle<br />

object−>texture−>registerHandle();<br />

}<br />

1680 /* Print out error messages and warnings and exits on errors.<br />

*/<br />

//******************************************************<br />

void Parser::Error( ParseError err, const char *ext )<br />

//******************************************************<br />

1685 { char empty = ’\0’;<br />

const char *extPtr = (ext != NULL)? ext : &empty;<br />

if( err < PARSE_ERRORS )<br />

1690 {<br />

if( err < CRITICAL_ERRORS )<br />

fprintf( stderr, "critical error: %s %s\n", errorMessage[err], extPtr );<br />

else<br />

fprintf( stderr, "parse error: file ’%s’, near line %d: %s %s, fo<strong>und</strong> %s\n",<br />

1695 scan−>getFileName(), scan−>getLine(), errorMessage[err], extPtr,<br />

scan−>getSymbolStr( scan−>sym ) );<br />

exit( 1 );


}<br />

1700 else<br />

fprintf( stdout, "warning: file ’%s’, near line %d: %s %s\n",<br />

scan−>getFileName(), scan−>getLine(), errorMessage[err], extPtr );<br />

}<br />

1705 /**<br />

* Functions handling predefinitions<br />

*/<br />

/* Try to parse a definition of demanded type and store it<br />

1710 * in the binary tree used for definition management.<br />

*/<br />

//**********************************************************************<br />

void Parser::setDef( Symbol type, const char *ident, DefNode **root )<br />

//**********************************************************************<br />

1715 {<br />

int cmpVal;<br />

if( *root != NULL )<br />

{<br />

1720 // scan tree of definitions<br />

cmpVal = strcmp( (*root)−>ident, ident );<br />

if( cmpVal > 0 ) setDef( type, ident, &(*root)−>right );<br />

else if( cmpVal < 0 ) setDef( type, ident, &(*root)−>left );<br />

else<br />

1725 {<br />

Error( SKIP_REDEFINITION, ident );<br />

// skip identifier<br />

scan−>getSymbol();<br />

1730 // skip definition<br />

switch( type )<br />

{<br />

case MATERIALSYM: getMaterial( NULL );<br />

break;<br />

1735<br />

case TEXTURESYM: getTextureData( NULL );<br />

break;<br />

case VERTEXSYM: getVertex( NULL );<br />

1740 break;<br />

case PATCHSYM: getPatch( NULL );<br />

break;<br />

1745 case COLORSYM: getColor( NULL );<br />

break;<br />

default: Error( ILLEGAL_DEFINITION );<br />

}<br />

1750 }<br />

}<br />

else<br />

{<br />

// create new definition node ...<br />

1755 if( (*root = new DefNode) == NULL ) Error( NO_DEFINITION );<br />

(*root)−>left = (*root)−>right = NULL;<br />

(*root)−>type = type;<br />

// set identifier<br />

1760 if( ((*root)−>ident = new char[ strlen( ident ) + 1 ]) == NULL )<br />

Error( NO_DEFINITION );<br />

else<br />

strcpy( (*root)−>ident, ident );<br />

1765 // skip identifier<br />

scan−>getSymbol();<br />

// parse definition<br />

switch( type )<br />

1770 {<br />

case MATERIALSYM: (*root)−>materialDef = defaultMaterial;<br />

getMaterial( &(*root)−>materialDef );<br />

break;<br />

1775 case TEXTURESYM: (*root)−>textureDataDef = defaultTextureData;<br />

getTextureData( &(*root)−>textureDataDef );<br />

break;<br />

case VERTEXSYM: if( ((*root)−>vertexBuffer = new VertexBuffer()) == NULL )<br />

1780 Error( NO_BUFFER, "[type: vertex]" );<br />

getVertex( (*root)−>vertexBuffer );<br />

break;<br />

1785 case PATCHSYM: if( ((*root)−>patchBuffer = new PatchBuffer()) == NULL )<br />

Error( NO_BUFFER, "[type: patch]" );<br />

1790<br />

1795 }<br />

}<br />

}<br />

getPatch( (*root)−>patchBuffer );<br />

break;<br />

case COLORSYM: getColor( &(*root)−>colorDef );<br />

break;<br />

default: Error( ILLEGAL_DEFINITION );


* Searches in the binary tree for definition<br />

1800 * of demanded type and identifier.<br />

*/<br />

//**************************************************************************<br />

DefNode *Parser::getDef( Symbol type, const char *ident, DefNode **root )<br />

//**************************************************************************<br />

1805 {<br />

int cmpVal;<br />

1810<br />

1815 }<br />

if( *root == NULL ) return NULL;<br />

cmpVal = strncmp( (*root)−>ident, ident, CHAR_LIMIT );<br />

if( cmpVal > 0 ) return getDef( type, ident, &(*root)−>right );<br />

else if( cmpVal < 0 ) return getDef( type, ident, &(*root)−>left );<br />

else if( type == (*root)−>type ) return (*root);<br />

else return NULL;<br />

/* Deletes the binary tree used<br />

* for definition management.<br />

*/<br />

1820 //**************************************<br />

void Parser::delDefs( DefNode *root )<br />

//**************************************<br />

{<br />

if( root−>left != NULL ) delDefs( root−>left );<br />

1825 if( root−>right != NULL ) delDefs( root−>right );<br />

switch ( root−>type )<br />

{<br />

case PATCHSYM: if( root−>patchBuffer != NULL )<br />

1830 delete root−>patchBuffer;<br />

break;<br />

case VERTEXSYM: if( root−>vertexBuffer != NULL )<br />

delete root−>vertexBuffer;<br />

1835 break;<br />

}<br />

default: break;<br />

1840 delete[] root−>ident;<br />

delete root;<br />

}


5<br />

#ifndef __SCANNER_H<br />

#define __SCANNER_H<br />

#include <br />

#define CHAR_LIMIT 15 /* character limit of keywords */<br />

#define LINE_LIMIT 256 /* character limit of line */<br />

typedef enum {<br />

10 // keywords<br />

AMBIENCESYM, AMBIENTSYM,<br />

ANGLESYM,<br />

ASPECTRATIOSYM, BEZIERSYM,<br />

BITMAPSYM, BLUESYM,<br />

15 BOXSYM, BRICKSYM,<br />

BUMPSYM, CAMERASYM,<br />

CHECKERSYM,<br />

COLORSYM, CONESYM,<br />

CSGSYM,<br />

20 CYLINDERSYM, DEFSYM,<br />

DIFFUSESYM, DIRECTIONSYM,<br />

EXPONENTSYM, FALSESYM,<br />

FILESYM,<br />

FREQUENCIESSYM, GREENSYM,<br />

25 INCLUDESYM, INDEXSYM,<br />

LIGHTSSYM, LOCATIONSYM,<br />

LOOKATSYM,<br />

MARBLESYM, MATERIALSYM,<br />

MOVESYM, OBJECTSSYM,<br />

30 OCTREESYM,<br />

PATCHSYM, PLAINSYM,<br />

PLANESYM, POINTSYM,<br />

REDSYM,<br />

REFLECTSYM, REFLECTIONSYM,<br />

35 RIGHTSYM, ROTATESYM,<br />

SCALESYM, SCENESYM,<br />

SHEARSYM,<br />

SIZESYM, SPECULARSYM,<br />

SPECULAREXPSYM, SPHERESYM,<br />

40 SPOTSYM,<br />

STRETCHSYM, SUPERSAMPLESYM,<br />

TEXTURESYM,<br />

TILESYM, TRANSFORMSYM,<br />

TRANSPARENCYSYM, TRUESYM,<br />

45 TURBULENCESYM,<br />

UPSYM, USESYM,<br />

VERTEXSYM, VIEWSYM,<br />

WOODSYM, XSYM,<br />

YSYM, ZSYM,<br />

50 ZOOMSYM,<br />

NUMBER_OF_KEYWORDS,<br />

// special<br />

55 UNKNOWNSYM = NUMBER_OF_KEYWORDS,<br />

IDENTSYM, STRINGSYM,<br />

NUMBER, REAL,<br />

LSS, GTR,<br />

MINUS, PLUS,<br />

60 TIMES, SLASH,<br />

CURLBRACELEFT, CURLBRACERIGHT,<br />

EOFSYM,<br />

NUMBER_OF_SYMBOLS<br />

65 //**********<br />

} Symbol;<br />

//**********<br />

/* The Scanner class<br />

70 */<br />

//****************<br />

class Scanner {<br />

//****************<br />

typedef enum {<br />

75 ALPHA, DIGIT, SPECIAL, OTHER<br />

//************<br />

} CharType;<br />

//************<br />

80 public:<br />

Scanner( const char *file_name );<br />

~Scanner();<br />

85<br />

void getSymbol();<br />

int getLine() { return lineNr; }<br />

const char *getFileName() { return fileName; }<br />

const char *getSymbolStr( Symbol s ) { return symbolString[s]; }<br />

90 // scanner results<br />

Symbol sym;<br />

const char *ident;<br />

const char *string;<br />

int number;<br />

95 double real;<br />

100<br />

private:<br />

void getChar();<br />

void stringToUpper( char *str );


translation tables<br />

static const char *symbolString[NUMBER_OF_SYMBOLS];<br />

CharType chType[256];<br />

105 FILE *file;<br />

char fileName[LINE_LIMIT];<br />

char lineBuffer[LINE_LIMIT];<br />

int lineNr;<br />

110 int lineLen;<br />

int curPos;<br />

unsigned char ch;<br />

char idBuffer[CHAR_LIMIT + 1];<br />

115 char strBuffer[LINE_LIMIT];<br />

};<br />

#endif /* __SCANNER_H */


#include <br />

#include <br />

#include <br />

5 #include <br />

#include <br />

#include "Scanner.h"<br />

10 /* Initialize translation table<br />

*/<br />

//**********************************************************<br />

const char *Scanner::symbolString[NUMBER_OF_SYMBOLS] = {<br />

//**********************************************************<br />

15 // keyword strings ...<br />

"AMBIENCE ", "AMBIENT ",<br />

"ANGLE ",<br />

"ASPECTRATIO ", "BEZIER ",<br />

"BITMAP ", "BLUE ",<br />

20 "BOX ", "BRICK ",<br />

"BUMP ", "CAMERA ",<br />

"CHECKER ",<br />

"COLOR ", "CONE ",<br />

"CSG ",<br />

25 "CYLINDER ", "DEF ",<br />

"DIFFUSE ", "DIRECTION ",<br />

"EXPONENT ", "FALSE ",<br />

"FILE ",<br />

"FREQUENCIES ", "GREEN ",<br />

30 "INCLUDE ", "INDEX ",<br />

"LIGHTS ", "LOCATION ",<br />

"LOOKAT ",<br />

"MARBLE ", "MATERIAL ",<br />

"MOVE ", "OBJECTS ",<br />

35 "OCTREE ",<br />

"PATCH ", "PLAIN ",<br />

"PLANE ", "POINT ",<br />

"RED ",<br />

"REFLECT ", "REFLECTION ",<br />

40 "RIGHT ", "ROTATE ",<br />

"SCALE ", "SCENE ",<br />

"SHEAR ",<br />

"SIZE ", "SPECULAR ",<br />

"SPECULAREXP ", "SPHERE ",<br />

45 "SPOT ",<br />

"STRETCH ", "SUPERSAMPLE ",<br />

"TEXTURE ",<br />

"TILE ", "TRANSFORM ",<br />

"TRANSPARENCY ", "TRUE ",<br />

50 "TURBULENCE ",<br />

"UP ", "USE ",<br />

"VERTEX ", "VIEW ",<br />

"WOOD ", "X ",<br />

"Y ", "Z ",<br />

55 "ZOOM ",<br />

// special strings ...<br />

"unknown Symbol",<br />

"identifier", "string",<br />

"number", "real number",<br />

60 "",<br />

"−", "+",<br />

"*", "/",<br />

"{", "}",<br />

"end of file"<br />

65 };<br />

/* Constructor<br />

*/<br />

70 //******************************************<br />

Scanner::Scanner( const char *file_name )<br />

//******************************************<br />

{<br />

register unsigned int c;<br />

75 char specialChars[] = { ’’, ’−’, ’+’, ’*’, ’/’, ’#’, ’"’, ’{’, ’}’, EOF, ’\0’ };<br />

char alphaChars[] = { ’_’, ’\0’ };<br />

// initialize char type table<br />

for( c = 0; c < 256; c++ )<br />

80 if( isalpha( c ) ) chType[c] = ALPHA;<br />

else if( isdigit( c ) ) chType[c] = DIGIT;<br />

else chType[c] = OTHER;<br />

for( c = 0; alphaChars[c]; c++ )<br />

85 chType[(unsigned char)alphaChars[c]] = ALPHA;<br />

for( c = 0; specialChars[c]; c++ )<br />

chType[(unsigned char)specialChars[c]] = SPECIAL;<br />

90 // try to open file<br />

if( (file = fopen( file_name, "r" )) == NULL )<br />

{<br />

fprintf( stderr, "%s: File not fo<strong>und</strong>!\n", file_name );<br />

exit( 1 );<br />

95 }<br />

strncpy( fileName, file_name, LINE_LIMIT − 1 );<br />

fileName[LINE_LIMIT − 1] = ’\0’;<br />

100 lineNr = lineLen = curPos = 0;


105 }<br />

idBuffer[0] = strBuffer[0] = ’\0’;<br />

ident = idBuffer;<br />

string = strBuffer;<br />

getChar();<br />

/* Destructor<br />

*/<br />

//********************<br />

110 Scanner::~Scanner()<br />

//********************<br />

{<br />

if( file != NULL ) fclose( file );<br />

}<br />

115<br />

/* Upper−case convertion<br />

*/<br />

//*****************************************<br />

void Scanner::stringToUpper( char *str )<br />

120 //*****************************************<br />

{<br />

for( ; *str; ++str )<br />

if( *str >= ’a’ && *str = 0 ) lower = i+1;<br />

if( cmpVal


do<br />

{<br />

digitVal = (unsigned char)(ch−’0’);<br />

205 if( (number < INT_MAX/10) || (number == INT_MAX/10) && (digitVal


getChar();<br />

break;<br />

case ’*’: sym = TIMES;<br />

305 getChar();<br />

break;<br />

case ’{’: sym = CURLBRACELEFT;<br />

getChar();<br />

310 break;<br />

315<br />

case ’}’: sym = CURLBRACERIGHT;<br />

getChar();<br />

break;<br />

case ’/’: // check for block comment<br />

getChar();<br />

if( ch == ’*’ )<br />

{<br />

320 do {<br />

for( getChar(); ch != ’*’; getChar() );<br />

getChar();<br />

} while( ch != ’/’ );<br />

getChar();<br />

325 goto begin;<br />

}<br />

else sym = SLASH;<br />

break;<br />

330 case ’#’: // line comment<br />

cmpVal = lineNr;<br />

for( getChar(); cmpVal == lineNr; getChar() );<br />

goto begin;<br />

335 case ’"’: for( getChar(), i = 0; ch != ’"’ && i < LINE_LIMIT − 1; getChar(), i++ )<br />

strBuffer[i] = ch;<br />

strBuffer[i] = ’\0’;<br />

340 // check for string too long ...<br />

if( ch != ’"’ )<br />

{<br />

fprintf( stderr, "Warning:%d: string too long − extra ignored!\n", lineNr );<br />

while( ch != ’"’ ) getChar();<br />

345 }<br />

350<br />

sym = STRINGSYM;<br />

getChar();<br />

break;<br />

case (unsigned char)EOF:<br />

sym = EOFSYM;<br />

break;<br />

355 default: sym = UNKNOWNSYM;<br />

getChar();<br />

}<br />

break;<br />

360 case OTHER: sym = UNKNOWNSYM;<br />

getChar();<br />

break;<br />

}<br />

}<br />

365


#ifndef __SIMPLELIST_H<br />

#define __SIMPLELIST_H<br />

#ifndef NULL<br />

5 #define NULL 0L<br />

#endif<br />

/* The SimpleList template<br />

*/<br />

10 template <br />

//*******************<br />

class SimpleList {<br />

//*******************<br />

typedef struct __ListItem {<br />

15 type data;<br />

__ListItem *next;<br />

//************<br />

} ListItem;<br />

//************<br />

20<br />

public:<br />

// The SimpleList iterator class<br />

//*****************<br />

class iterator {<br />

25 //*****************<br />

friend class SimpleList;<br />

public:<br />

// Constructor<br />

iterator() : curItem(NULL), pos(−1) {}<br />

30<br />

// Iterator check<br />

bool isOK() { return (curItem != NULL); }<br />

// Switch to next item<br />

35 //************<br />

bool next()<br />

//************<br />

{<br />

if(curItem != NULL) {<br />

40 curItem = curItem−>next;<br />

++pos;<br />

}<br />

return (curItem != NULL);<br />

}<br />

45<br />

// Get data of current item<br />

type &get() { return curItem−>data; }<br />

// Get current list position<br />

50 int getPos() { return pos; };<br />

private:<br />

// iterator’s properties<br />

iterator( ListItem *item ) : curItem(item), pos(0) {}<br />

ListItem *curItem;<br />

55 int pos;<br />

};<br />

60<br />

// Constructor<br />

SimpleList() : items(0), first(NULL), last(NULL), hook(NULL) {}<br />

// Destructor<br />

//************************<br />

virtual ~SimpleList() {<br />

//************************<br />

65 ListItem *tmp;<br />

for( tmp=first; tmp!=NULL; tmp=first ) {<br />

first = tmp−>next;<br />

delete tmp;<br />

70 }<br />

}<br />

// Append a new item<br />

//***************<br />

75 type *append()<br />

//***************<br />

{<br />

ListItem *tmp;<br />

80 // set tmp to new ListItem<br />

if( hook == NULL )<br />

{<br />

if( (tmp = new ListItem) == NULL )<br />

return NULL;<br />

85 }<br />

else<br />

{<br />

tmp = hook;<br />

hook = hook−>next;<br />

90 }<br />

tmp−>next = NULL;<br />

if( first == NULL )<br />

95 first = last = tmp;<br />

else<br />

{<br />

last−>next = tmp;<br />

last = tmp;<br />

100 }


105<br />

}<br />

++items;<br />

return &tmp−>data;<br />

// Delete item<br />

//************************************<br />

bool deleteItem( unsigned int pos )<br />

//************************************<br />

110 {<br />

ListItem **itemRun = &first;<br />

// search for n−th control point<br />

while( *itemRun != NULL && pos−− )<br />

115 itemRun = &((*itemRun)−>next);<br />

if( *itemRun != NULL ) {<br />

ListItem *keeper = (*itemRun)−>next;<br />

120 // hang into recycle list<br />

(*itemRun)−>next = hook;<br />

hook = *itemRun;<br />

// reconnect list<br />

125 *itemRun = keeper;<br />

−−items;<br />

130<br />

}<br />

}<br />

return true;<br />

return false;<br />

// SimpleList assignment<br />

135 //****************************************<br />

SimpleList &operator=( SimpleList &sl )<br />

//****************************************<br />

{<br />

ListItem *tmp;<br />

140 type *datPtr;<br />

if( this != &sl )<br />

{<br />

clear();<br />

145 for( tmp = sl.first; tmp != NULL; tmp = tmp−>next )<br />

if( (datPtr = append()) != NULL )<br />

*datPtr = tmp−>data;<br />

}<br />

return *this;<br />

150 }<br />

// Index data access<br />

//***********************************<br />

type &operator[]( unsigned int n )<br />

155 //***********************************<br />

{<br />

ListItem *tmp = first;<br />

// search for n−th control point<br />

160 while( tmp != NULL && n−− )<br />

tmp = tmp−>next;<br />

165<br />

}<br />

return tmp−>data;<br />

// Clear list<br />

//*************<br />

void clear()<br />

//*************<br />

170 {<br />

if( last != NULL )<br />

{<br />

last−>next = hook;<br />

hook = first;<br />

175 first = last = NULL;<br />

items = 0;<br />

}<br />

}<br />

180 // Get number of items<br />

unsigned int length() { return items; }<br />

185<br />

// Get iterator to first item of list<br />

iterator begin() { return iterator(first); }<br />

// Get data of first or last item<br />

type &getFirst() { return first−>data; }<br />

type &getLast() { return last−>data; }<br />

190 private:<br />

// list’s properties<br />

unsigned int items;<br />

ListItem *first, *last;<br />

ListItem *hook;<br />

195 };<br />

#endif /* __SIMPLELIST_H */


5<br />

#ifndef __PATCHBUFFER_H<br />

#define __PATCHBUFFER_H<br />

#include "SimpleList.h"<br />

//********************<br />

typedef int *Patch;<br />

//********************<br />

10 typedef struct {<br />

unsigned int m;<br />

unsigned int n;<br />

//*************<br />

} PatchSize;<br />

15 //*************<br />

typedef struct {<br />

Patch patchData;<br />

PatchSize size;<br />

20 //*************<br />

} PatchItem;<br />

//*************<br />

/* The PatchBuffer class<br />

25 */<br />

//***************************************************<br />

class PatchBuffer : public SimpleList {<br />

//***************************************************<br />

public:<br />

30 PatchBuffer() : SimpleList() {}<br />

// destructor<br />

~PatchBuffer() {<br />

if( length() )<br />

35 {<br />

SimpleList::iterator it = begin();<br />

do {<br />

delete it.get().patchData;<br />

40 } while( it.next() );<br />

}<br />

}<br />

// get new patch<br />

45 Patch getNewPatch( const PatchSize &size )<br />

{<br />

PatchItem *tmp = getNewPatchItem( size );<br />

return ((tmp != NULL)? tmp−>patchData : NULL);<br />

}<br />

50<br />

55<br />

60<br />

// append PatchBuffer<br />

bool appendPatchBuffer( PatchBuffer *patchBuff )<br />

{<br />

PatchSize size;<br />

if( patchBuff−>length() )<br />

{<br />

PatchItem *dest, *src;<br />

SimpleList::iterator it = patchBuff−>begin();<br />

do {<br />

src = &it.get();<br />

size = src−>size;<br />

65 if( (dest = getNewPatchItem( size )) != NULL )<br />

{<br />

for( unsigned int i = 0; i < size.m*size.n; ++i )<br />

{<br />

dest−>patchData[i] = src−>patchData[i];<br />

70 }<br />

}<br />

else return false;<br />

} while( it.next() );<br />

}<br />

75 return true;<br />

}<br />

80<br />

private:<br />

Patch *append() { return NULL; }<br />

PatchItem *getNewPatchItem( const PatchSize &size )<br />

{<br />

PatchItem *tmp = SimpleList::append();<br />

85 if( tmp != NULL )<br />

if( (tmp−>patchData = new int[size.m * size.n]) != NULL )<br />

tmp−>size = size;<br />

90 }<br />

};<br />

return tmp;<br />

#endif /* __PATCHBUFFER_H */


#ifndef __TYPES_H<br />

#define __TYPES_H<br />

#include <br />

5 #include <br />

10<br />

#ifndef NULL<br />

#define NULL 0L<br />

#endif<br />

//********************<br />

typedef double Real;<br />

//********************<br />

15 //****************************<br />

typedef unsigned char Byte;<br />

//****************************<br />

typedef struct {<br />

20 Real lower;<br />

Real upper;<br />

//************<br />

} Interval;<br />

//************<br />

25<br />

typedef struct {<br />

Real data[4][4];<br />

//**********<br />

} Matrix;<br />

30 //**********<br />

typedef enum {<br />

X, Y, Z<br />

//**************<br />

35 } Coordinate;<br />

//**************<br />

typedef enum {<br />

/* Note:<br />

40 * This order determines arrangement of color<br />

* channels in the pixel buffer!<br />

* (XImage want it this way!)<br />

*/<br />

BLUE, GREEN, RED, ALPHA<br />

45 //****************<br />

} ColorChannel;<br />

//****************<br />

typedef struct {<br />

50 Real channel[3];<br />

//*********<br />

} Color;<br />

//*********<br />

55 //***********************************<br />

typedef Color MaterialCoefficient;<br />

//***********************************<br />

typedef struct {<br />

60 MaterialCoefficient ambient;<br />

MaterialCoefficient diffuse;<br />

MaterialCoefficient specular;<br />

Real specularExp;<br />

65 Real reflect;<br />

Real transparency;<br />

Real refractIndex;<br />

//************<br />

} Material;<br />

70 //************<br />

typedef struct {<br />

Real x;<br />

Real y;<br />

75 Real z;<br />

//**********<br />

} Vector;<br />

//**********<br />

80 //**********************<br />

typedef Vector Point;<br />

//**********************<br />

typedef struct {<br />

85 Point origin;<br />

Vector direction;<br />

Color color;<br />

//*******<br />

} <strong>Ray</strong>;<br />

90 //*******<br />

95<br />

//****************************<br />

typedef Vector Vertices[8];<br />

//****************************<br />

//**************************<br />

#define RAYEPS 1e−7<br />

//**************************<br />

100 #endif /* __TYPES_H */


#ifndef __UTILS_H<br />

#define __UTILS_H<br />

#include <br />

5 #include "Types.h"<br />

/* Ro<strong>und</strong> to nearest integer<br />

*/<br />

inline int Ro<strong>und</strong>( Real x )<br />

10 {<br />

return ((int)((x > 0.0)? (x + 0.5) : (x − 0.5)));<br />

}<br />

/* Determine floor value<br />

15 */<br />

inline int floorVal( Real x )<br />

{<br />

return ((int)x − (x < 0.0 && x != (int)x));<br />

}<br />

20<br />

/* Determine absolute value<br />

*/<br />

template <br />

inline type absVal( const type &a )<br />

25 {<br />

return (a < 0)? −a : a;<br />

}<br />

/* Return maximum value<br />

30 */<br />

template <br />

inline type &maxVal( const type &a, const type &b )<br />

{<br />

return const_cast((a > b)? a : b);<br />

35 }<br />

/* Return minimum value<br />

*/<br />

template <br />

40 inline type &minVal( const type &a, const type &b )<br />

{<br />

return const_cast((a < b)? a : b);<br />

}<br />

45 /* convert to/from radian meassure<br />

*/<br />

#define DEGToRAD(angle) (M_PI * (angle) / 180.0)<br />

#define RADToDEG(angle) ((angle) * 180.0 / M_PI)<br />

50 // blend values<br />

#define blend(a,b,f) (a * f + (1.0 − f) * b)<br />

/* regular swap<br />

*/<br />

55 template <br />

inline void swap( type &a, type &b )<br />

{<br />

type tmp = a;<br />

a = b;<br />

60 b = tmp;<br />

}<br />

/* XOR swap<br />

*/<br />

65 inline void swapXOR( unsigned int &a, unsigned int &b )<br />

{<br />

a ^= b;<br />

b ^= a;<br />

a ^= b;<br />

70 }<br />

/**<br />

* Color/Material calculations<br />

*/<br />

75 inline Color addColor( const Color &a, const Color &b )<br />

{<br />

Color tmp;<br />

tmp.channel[RED] = a.channel[RED] + b.channel[RED];<br />

80 tmp.channel[GREEN] = a.channel[GREEN] + b.channel[GREEN];<br />

tmp.channel[BLUE] = a.channel[BLUE] + b.channel[BLUE];<br />

85<br />

}<br />

return tmp;<br />

inline Color subColor( const Color &a, const Color &b )<br />

{<br />

Color tmp;<br />

90 tmp.channel[RED] = a.channel[RED] − b.channel[RED];<br />

tmp.channel[GREEN] = a.channel[GREEN] − b.channel[GREEN];<br />

tmp.channel[BLUE] = a.channel[BLUE] − b.channel[BLUE];<br />

95 }<br />

100<br />

return tmp;<br />

inline Color scaleColor( const Color &a, Real fact )<br />

{<br />

Color tmp;


tmp.channel[RED] = a.channel[RED] * fact;<br />

tmp.channel[GREEN] = a.channel[GREEN] * fact;<br />

tmp.channel[BLUE] = a.channel[BLUE] * fact;<br />

105 return tmp;<br />

}<br />

inline Real grayValue( const Color &c )<br />

{<br />

110 return (c.channel[RED] + c.channel[GREEN] + c.channel[BLUE]) / 3.0;<br />

}<br />

inline void blendColor( const Color &a, const Color &b, Real f, Color *res )<br />

{<br />

115 res−>channel[RED] = blend( a.channel[RED], b.channel[RED], f );<br />

res−>channel[GREEN] = blend( a.channel[GREEN], b.channel[GREEN], f );<br />

res−>channel[BLUE] = blend( a.channel[BLUE], b.channel[BLUE], f );<br />

}<br />

120 inline void blendMaterial( const Material &a, const Material &b, Real f, Material *res )<br />

{<br />

blendColor( a.ambient, b.ambient, f, &res−>ambient );<br />

blendColor( a.diffuse, b.diffuse, f, &res−>diffuse );<br />

blendColor( a.specular, b.specular, f, &res−>specular );<br />

125<br />

130 }<br />

res−>specularExp = blend( a.specularExp, b.specularExp, f );<br />

res−>reflect = blend( a.reflect, b.reflect, f );<br />

res−>transparency = blend( a.transparency, b.transparency, f );<br />

res−>refractIndex = blend( a.refractIndex, b.refractIndex, f );<br />

/**<br />

* Interval arithmetics<br />

*/<br />

135 inline bool intersectInterval( const Interval &a, const Interval &b, Interval &intsct )<br />

{<br />

intsct.lower = (a.lower > b.lower)? a.lower : b.lower;<br />

intsct.upper = (a.upper < b.upper)? a.upper : b.upper;<br />

return (intsct.lower


Vector tmp;<br />

tmp.x = a.y * b.z − a.z * b.y;<br />

tmp.y = a.z * b.x − a.x * b.z;<br />

205 tmp.z = a.x * b.y − a.y * b.x;<br />

}<br />

res = tmp;<br />

210 inline Real vectorLength( const Vector &v )<br />

{<br />

return sqrt( v.x * v.x + v.y * v.y + v.z * v.z );<br />

}<br />

215 inline void translateVector( const Vector &v, Real dist, Vector &res )<br />

{<br />

res.x = v.x + dist;<br />

res.y = v.y + dist;<br />

res.z = v.z + dist;<br />

220 }<br />

225<br />

230<br />

inline void normalizeVector( const Vector &v, Vector &res )<br />

{<br />

register Real length = vectorLength( v );<br />

}<br />

res.x = v.x / length;<br />

res.y = v.y / length;<br />

res.z = v.z / length;<br />

inline void rayPoint( const <strong>Ray</strong> &ray, Real dist, Vector &res )<br />

{<br />

res.x = ray.origin.x + dist * ray.direction.x;<br />

res.y = ray.origin.y + dist * ray.direction.y;<br />

235 res.z = ray.origin.z + dist * ray.direction.z;<br />

}<br />

/* functions initializing transform matrices<br />

*/<br />

240 void multiplyMatrix( const Matrix &matrix, const Matrix &multMatrix, Matrix *result );<br />

void translateMatrix( Matrix *matrix, const Vector &transl );<br />

void rotateMatrix( Matrix *matrix, const Vector &rotat );<br />

void scaleMatrix( Matrix *matrix, const Vector &scale );<br />

void shearMatrix( Matrix *matrix, Coordinate c, Real sh1, Real sh2 );<br />

245 bool invertMatrix( const Matrix &matrix, Matrix *invMatrix );<br />

bool solveEquationSystem( Real *matrix, Real sol[], int n );<br />

/* functions transforming points<br />

*/<br />

250 void transformPoint( const Matrix &matrix, const Point &point, Point *result );<br />

void transformVector( const Matrix &matrix, const Vector &vec, Vector *result );<br />

void transformNormal( const Matrix &matrix, const Vector &normal, Vector *result );<br />

/* octree utility functions<br />

255 */<br />

void getVertices( const Vector &min, const Vector &max, Vertices vert );<br />

bool testVertices( Vertices vert, const Vector &min, const Vector &max );<br />

bool testEdge( const Vector &p1, const Vector &p2, const Vector &min, const Vector &max );<br />

bool testBox( Vertices vert, const Vector &min, const Vector &max );<br />

260<br />

/**<br />

* Predefinitions<br />

*/<br />

// colors ...<br />

265 extern const Color black;<br />

extern const Color white;<br />

extern const Material defaultMaterial;<br />

// vectors ...<br />

270 extern const Vector nullVector;<br />

extern const Vector oneVector;<br />

extern const Vector baseVector[3];<br />

// matrices ...<br />

275 extern const Matrix nullMatrix;<br />

extern const Matrix identityMatrix;<br />

// auxiliary pointer for color channels<br />

extern const ColorChannel RGBorder[4];<br />

280 extern const ColorChannel BGRorder[4];<br />

extern const ColorChannel * const RGBfirst;<br />

extern const ColorChannel * const BGRfirst;<br />

285<br />

#endif /* __UTILS_H */


#include <br />

#include <br />

#include <br />

5 #include "utils.h"<br />

/**<br />

* Predefinitions<br />

*/<br />

10 // colors ...<br />

const Color black = {{ 0.0, 0.0, 0.0 }};<br />

const Color white = {{ 1.0, 1.0, 1.0 }};<br />

const Material defaultMaterial = { {{ 0.2, 0.2, 0.2 }},<br />

{{ 0.2, 0.2, 0.2 }},<br />

15 {{ 0.1, 0.1, 0.1 }},<br />

1.0, 0.0, 0.0, 1.0 };<br />

// vectors ...<br />

const Vector nullVector = { 0.0, 0.0, 0.0 };<br />

20 const Vector oneVector = { 1.0, 1.0, 1.0 };<br />

const Vector baseVector[] = { { 1.0, 0.0, 0.0 },<br />

{ 0.0, 1.0, 0.0 },<br />

{ 0.0, 0.0, 1.0 } };<br />

25 // matrices ...<br />

const Matrix nullMatrix = {{ { 0.0, 0.0, 0.0, 0.0 },<br />

{ 0.0, 0.0, 0.0, 0.0 },<br />

{ 0.0, 0.0, 0.0, 0.0 },<br />

{ 0.0, 0.0, 0.0, 0.0 } }};<br />

30<br />

35<br />

const Matrix identityMatrix = {{ { 1.0, 0.0, 0.0, 0.0 },<br />

{ 0.0, 1.0, 0.0, 0.0 },<br />

{ 0.0, 0.0, 1.0, 0.0 },<br />

{ 0.0, 0.0, 0.0, 1.0 } }};<br />

// auxiliary pointer for color channels<br />

const ColorChannel RGBorder[] = { RED, GREEN, BLUE, ALPHA };<br />

const ColorChannel BGRorder[] = { BLUE, GREEN, RED, ALPHA };<br />

const ColorChannel * const RGBfirst = &RGBorder[0];<br />

40 const ColorChannel * const BGRfirst = &BGRorder[0];<br />

/* Multiplies a matrix by another one.<br />

*/<br />

45 //**************************************************************************************<br />

void multiplyMatrix( const Matrix &matrix, const Matrix &multMatrix, Matrix *result )<br />

//**************************************************************************************<br />

{<br />

int x, y, i;<br />

50 Matrix tmp = nullMatrix;<br />

for( x = 0; x < 4; ++x )<br />

for( y = 0; y < 4; ++y )<br />

for( i = 0; i < 4; ++i )<br />

55 tmp.data[y][x] += matrix.data[y][i] * multMatrix.data[i][x];<br />

}<br />

*result = tmp;<br />

60 /* Adds a translation to a system matrix.<br />

*/<br />

//*************************************************************<br />

void translateMatrix( Matrix *matrix, const Vector &transl )<br />

//*************************************************************<br />

65 {<br />

Matrix translMatrix = identityMatrix;<br />

// initialize translation matrix<br />

translMatrix.data[0][3] = transl.x;<br />

70 translMatrix.data[1][3] = transl.y;<br />

translMatrix.data[2][3] = transl.z;<br />

75<br />

}<br />

multiplyMatrix( *matrix, translMatrix, matrix );<br />

/* Adds a rotation about x, y, z (in this order) to a system matrix.<br />

*/<br />

//*******************************************************<br />

void rotateMatrix( Matrix *matrix, const Vector &rot )<br />

80 //*******************************************************<br />

{<br />

Matrix rotMatrix[3] = { identityMatrix, identityMatrix, identityMatrix };<br />

Matrix rotation = identityMatrix;<br />

Real angle[3] = { DEGToRAD( rot.x ), DEGToRAD( rot.y ), DEGToRAD( rot.z ) };<br />

85 Real sinAngle, cosAngle;<br />

if( angle[X] != 0.0 )<br />

{<br />

// initialize x rotation matrix<br />

90 sinAngle = sin( angle[X] );<br />

cosAngle = cos( angle[X] );<br />

rotMatrix[X].data[1][1] =<br />

rotMatrix[X].data[2][2] = cosAngle;<br />

95 rotMatrix[X].data[1][2] = −sinAngle;<br />

rotMatrix[X].data[2][1] = sinAngle;<br />

100<br />

}<br />

rotation = rotMatrix[X];


if( angle[Y] != 0.0 )<br />

{<br />

// initialize y rotation matrix<br />

sinAngle = sin( angle[Y] );<br />

105 cosAngle = cos( angle[Y] );<br />

rotMatrix[Y].data[0][0] =<br />

rotMatrix[Y].data[2][2] = cosAngle;<br />

rotMatrix[Y].data[0][2] = sinAngle;<br />

110 rotMatrix[Y].data[2][0] = −sinAngle;<br />

}<br />

multiplyMatrix( rotation, rotMatrix[Y], &rotation );<br />

115 if( angle[Z] != 0.0 )<br />

{<br />

// initialize z rotation matrix<br />

sinAngle = sin( angle[Z] );<br />

cosAngle = cos( angle[Z] );<br />

120<br />

125<br />

}<br />

rotMatrix[Z].data[0][0] =<br />

rotMatrix[Z].data[1][1] = cosAngle;<br />

rotMatrix[Z].data[0][1] = −sinAngle;<br />

rotMatrix[Z].data[1][0] = sinAngle;<br />

multiplyMatrix( rotation, rotMatrix[Z], &rotation );<br />

// rotate matrix<br />

130 multiplyMatrix( *matrix, rotation, matrix );<br />

}<br />

/* Adds a scaling factor to a system matrix.<br />

*/<br />

135 //********************************************************<br />

void scaleMatrix( Matrix *matrix, const Vector &scale )<br />

//********************************************************<br />

{<br />

Matrix sclMatrix = identityMatrix;<br />

140<br />

145<br />

}<br />

// initialize scaling matrix<br />

sclMatrix.data[0][0] = scale.x;<br />

sclMatrix.data[1][1] = scale.y;<br />

sclMatrix.data[2][2] = scale.z;<br />

multiplyMatrix( *matrix, sclMatrix, matrix );<br />

/* Adds a shear to along a coordinate axis to a system matrix.<br />

150 * Parameters sh1 and sh2 are used to define shear factors:<br />

* c = X: sh1 −> Y, sh2 −> Z<br />

* c = Y: sh1 −> Z, sh2 −> X<br />

* c = Z: sh1 −> X, sh2 −> Y<br />

*/<br />

155 //*********************************************************************<br />

void shearMatrix( Matrix *matrix, Coordinate c, Real sh1, Real sh2 )<br />

//*********************************************************************<br />

{<br />

Matrix shMatrix = identityMatrix;<br />

160<br />

// initialize shear matrix<br />

shMatrix.data[(c+1)%3][c] = sh1;<br />

shMatrix.data[(c+2)%3][c] = sh2;<br />

165 multiplyMatrix( *matrix, shMatrix, matrix );<br />

}<br />

/* Determines the inverse of the given matrix by the<br />

* Gauss−Jordan algorithm.<br />

170 */<br />

//*************************************************************<br />

bool invertMatrix( const Matrix &matrix, Matrix *invMatrix )<br />

//*************************************************************<br />

{<br />

175 Real pivotValue, factor;<br />

int pivotPos, column, row;<br />

int maxValCol;<br />

int perm[4] = { 0, 1, 2, 3 };<br />

Matrix mat = matrix;<br />

180<br />

// initialize inverse matrix<br />

*invMatrix = identityMatrix;<br />

for( pivotPos = 0; pivotPos < 4; ++pivotPos )<br />

185 {<br />

// do partial pivoting if pivot element is zero.<br />

if( absVal( mat.data[pivotPos][perm[pivotPos]] ) < RAYEPS )<br />

{<br />

// search right hand side for largest absolute value.<br />

190 for( maxValCol = pivotPos, column = pivotPos + 1; column < 4; ++column )<br />

if(absVal( mat.data[pivotPos][perm[maxValCol]] ) < absVal( mat.data[pivotPos][perm[column]]))<br />

maxValCol = column;<br />

// swap columns<br />

195 swap( perm[maxValCol], perm[pivotPos] );<br />

200 }<br />

// Check new pivot element<br />

// Return false if matrix is singular<br />

if( absVal( mat.data[pivotPos][perm[pivotPos]] ) < RAYEPS ) return false;


deterimine value of pivot element<br />

pivotValue = mat.data[pivotPos][perm[pivotPos]];<br />

205 // scale row if pivot element not equals one<br />

if( pivotValue != 1.0 )<br />

{<br />

// scale row dividing elements by original pivot value<br />

for( column = pivotPos; column < 4; ++column )<br />

210 mat.data[pivotPos][perm[column]] /= pivotValue;<br />

for( column = 0; column < 4; ++column )<br />

invMatrix−>data[pivotPos][column] /= pivotValue;<br />

}<br />

215 // reduce previous and successional elements of<br />

// the pivot column to zero<br />

for( row = 0; row < 4; ++row )<br />

{<br />

if( row == pivotPos ) continue;<br />

220<br />

factor = mat.data[row][perm[pivotPos]];<br />

if( factor == 0.0 ) continue;<br />

// subtract pivot row factor times from current row<br />

225 for( column = pivotPos; column < 4; ++column )<br />

mat.data[row][perm[column]] −= factor * mat.data[pivotPos][perm[column]];<br />

for( column = 0; column < 4; ++column )<br />

invMatrix−>data[row][column] −= factor * invMatrix−>data[pivotPos][column];<br />

}<br />

230 }<br />

// swap rows of inverse matrix according to permutations by pivoting<br />

for( column = 0; column < 4; ++column )<br />

if( perm[column] != column )<br />

235 {<br />

for( int i = 0; i < 4; ++i )<br />

{<br />

swap( invMatrix−>data[perm[column]][i], invMatrix−>data[column][i] );<br />

}<br />

240 swap( perm[perm[column]], perm[column] );<br />

}<br />

245<br />

}<br />

return true;<br />

/* Solves an equation system<br />

* Parameters:<br />

* mat − system matrix<br />

* sol − solution vector, will hold system solution<br />

250 * n − number of matrix columns/lines<br />

*/<br />

//**********************************************************<br />

bool solveEquationSystem( Real mat[], Real sol[], int n )<br />

//**********************************************************<br />

255 {<br />

Real pivotValue, factor;<br />

int pivotPos, column, row;<br />

int maxValCol;<br />

int *perm;<br />

260<br />

// create working matrix on stack<br />

Real *matrix = (Real *)alloca( n * n * sizeof(Real) );<br />

memcpy( matrix, mat, n*n*sizeof(Real) );<br />

265 // create and initialize permutaion table<br />

perm = (int *)alloca( n * sizeof(int) );<br />

for( column = 0; column < n; ++column )<br />

perm[column] = column;<br />

270 for( pivotPos = 0; pivotPos < n; ++pivotPos )<br />

{<br />

// do partial pivoting if pivot element is zero.<br />

if( absVal( matrix[pivotPos * n + perm[pivotPos]] ) < RAYEPS )<br />

{<br />

275 // search right hand side for largest absolute value.<br />

for( maxValCol = pivotPos, column = pivotPos + 1; column < n; ++column )<br />

if(absVal( matrix[pivotPos*n + perm[maxValCol]] ) < absVal( matrix[pivotPos*n + perm[column]]))<br />

maxValCol = column;<br />

280 // swap columns<br />

swap( perm[maxValCol], perm[pivotPos] );<br />

// Check new pivot element<br />

// Return false if system is near singular<br />

285 if( absVal( matrix[pivotPos * n + perm[pivotPos]] ) < RAYEPS ) return false;<br />

}<br />

290<br />

// deterimine value of pivot element<br />

pivotValue = matrix[pivotPos * n + perm[pivotPos]];<br />

// scale row if pivot element not equals one<br />

if( pivotValue != 1.0 )<br />

{<br />

// scale matrix row dividing elements by pivot value<br />

295 for( column = pivotPos; column < n; ++column )<br />

matrix[pivotPos * n + perm[column]] /= pivotValue;<br />

// scale component of solution vector<br />

sol[pivotPos] /= pivotValue;<br />

}<br />

300


educe previous and successional elements of<br />

// the pivot column to zero<br />

for( row = 0; row < n; ++row )<br />

{<br />

305 if( row == pivotPos ) continue;<br />

factor = matrix[row * n + perm[pivotPos]];<br />

if( factor == 0.0 ) continue;<br />

310 // subtract pivot row factor times from current row<br />

for( column = pivotPos; column < n; ++column )<br />

matrix[row * n + perm[column]] −= factor * matrix[pivotPos * n + perm[column]];<br />

// do the same for solution vector<br />

sol[row] −= factor * sol[pivotPos];<br />

315 }<br />

}<br />

// swap rows of solution vector according to permutations by pivoting<br />

for( column = 0; column < n; ++column )<br />

320 if( perm[column] != column )<br />

{<br />

swap( sol[perm[column]], sol[column] );<br />

swap( perm[perm[column]], perm[column] );<br />

}<br />

325<br />

}<br />

return true;<br />

/* Transform a point by the given matrix<br />

330 */<br />

//*******************************************************************************<br />

void transformPoint( const Matrix &matrix, const Point &point, Point *result )<br />

//*******************************************************************************<br />

{<br />

335 Point p = point;<br />

340 }<br />

result−>x = matrix.data[0][0]*p.x + matrix.data[0][1]*p.y + matrix.data[0][2]*p.z + matrix.data[0][3];<br />

result−>y = matrix.data[1][0]*p.x + matrix.data[1][1]*p.y + matrix.data[1][2]*p.z + matrix.data[1][3];<br />

result−>z = matrix.data[2][0]*p.x + matrix.data[2][1]*p.y + matrix.data[2][2]*p.z + matrix.data[2][3];<br />

/* Transform a vector by the given matrix<br />

*/<br />

//********************************************************************************<br />

345 void transformVector( const Matrix &matrix, const Vector &vec, Vector *result )<br />

//********************************************************************************<br />

{<br />

Vector v = vec;<br />

350 result−>x = matrix.data[0][0] * v.x + matrix.data[0][1] * v.y + matrix.data[0][2] * v.z;<br />

result−>y = matrix.data[1][0] * v.x + matrix.data[1][1] * v.y + matrix.data[1][2] * v.z;<br />

result−>z = matrix.data[2][0] * v.x + matrix.data[2][1] * v.y + matrix.data[2][2] * v.z;<br />

}<br />

355 /* Transform a normal vector by the given matrix<br />

*/<br />

//***********************************************************************************<br />

void transformNormal( const Matrix &matrix, const Vector &normal, Vector *result )<br />

//***********************************************************************************<br />

360 {<br />

Vector n = normal;<br />

result−>x = matrix.data[0][0] * n.x + matrix.data[1][0] * n.y + matrix.data[2][0] * n.z;<br />

result−>y = matrix.data[0][1] * n.x + matrix.data[1][1] * n.y + matrix.data[2][1] * n.z;<br />

365 result−>z = matrix.data[0][2] * n.x + matrix.data[1][2] * n.y + matrix.data[2][2] * n.z;<br />

}<br />

/* Determines the vertices of the eight corners of a block.<br />

*/<br />

370 //************************************************************************<br />

void getVertices( const Vector &min, const Vector &max, Vertices vert )<br />

//************************************************************************<br />

{<br />

vert[0] = min;<br />

375 vert[1].x = min.x; vert[1].y = max.y; vert[1].z = min.z;<br />

vert[2].x = min.x; vert[2].y = max.y; vert[2].z = max.z;<br />

vert[3].x = min.x; vert[3].y = min.y; vert[3].z = max.z;<br />

vert[4].x = max.x; vert[4].y = min.y; vert[4].z = min.z;<br />

vert[5].x = max.x; vert[5].y = max.y; vert[5].z = min.z;<br />

380 vert[6] = max;<br />

vert[7].x = max.x; vert[7].y = min.y; vert[7].z = max.z;<br />

}<br />

/* Tests all vertices for being inside a block specified by the<br />

385 * parameters min and max.<br />

*/<br />

//*************************************************************************<br />

bool testVertices( Vertices vert, const Vector &min, const Vector &max )<br />

//*************************************************************************<br />

390 {<br />

for( unsigned int i=0; i < 8; ++i )<br />

{<br />

if( vert[i].x >= min.x && vert[i].y >= min.y && vert[i].z >= min.z &&<br />

vert[i].x


* The edge is specified by the parameters p1 and p2.<br />

* The block is specified by the parameters min and max.<br />

*/<br />

//******************************************************************************************<br />

405 bool testEdge( const Vector &p1, const Vector &p2, const Vector &min, const Vector &max )<br />

//******************************************************************************************<br />

{<br />

Vector n;<br />

Real x, y, z, t;<br />

410<br />

subVector( p2, p1, n );<br />

if( n.x != 0.0 )<br />

{<br />

415 t = (max.x − p1.x) / n.x;<br />

if( (t = 0.0) )<br />

{<br />

y = p1.y + t * n.y;<br />

420 z = p1.z + t * n.z;<br />

if( (y = min.y) && (z = min.z) )<br />

return true;<br />

}<br />

425 t = (min.x − p1.x) / n.x;<br />

if( (t = 0.0) )<br />

{<br />

y = p1.y + t * n.y;<br />

430 z = p1.z + t * n.z;<br />

if( (y = min.y) && (z = min.z) )<br />

return true;<br />

}<br />

}<br />

435<br />

if( n.y != 0.0 )<br />

{<br />

t = (max.y − p1.y) / n.y;<br />

440 if( (t = 0.0) )<br />

{<br />

x = p1.x + t * n.x;<br />

z = p1.z + t * n.z;<br />

if( (x = min.x) && (z = min.z) )<br />

445 return true;<br />

}<br />

t = (min.y − p1.y) / n.y;<br />

450 if( (t = 0.0) )<br />

{<br />

x = p1.x + t * n.x;<br />

z = p1.z + t * n.z;<br />

if( (x = min.x) && (z = min.z) )<br />

455 return true;<br />

}<br />

}<br />

if( n.z != 0.0 )<br />

460 {<br />

t = (max.z − p1.z) / n.z;<br />

if( (t = 0.0) )<br />

{<br />

465 y = p1.y + t * n.y;<br />

x = p1.x + t * n.x;<br />

if( (y = min.y) && (x = min.x) )<br />

return true;<br />

}<br />

470<br />

t = (min.z − p1.z) / n.z;<br />

if( (t = 0.0) )<br />

{<br />

475 y = p1.y + t * n.y;<br />

x = p1.x + t * n.x;<br />

if( (y = min.y) && (x = min.x) )<br />

return true;<br />

}<br />

480 }<br />

}<br />

return false;<br />

485 /* Tests all vertices for being inside a block or all edges<br />

* for intersecting the block’s sides.<br />

* The block is specified by the parameters min and max.<br />

*/<br />

//********************************************************************<br />

490 bool testBox( Vertices vert, const Vector &min, const Vector &max )<br />

//********************************************************************<br />

{<br />

return ( testVertices( vert, min, max ) ||<br />

testEdge( vert[0], vert[1], min, max ) ||<br />

495 testEdge( vert[0], vert[3], min, max ) ||<br />

testEdge( vert[0], vert[4], min, max ) ||<br />

testEdge( vert[5], vert[1], min, max ) ||<br />

testEdge( vert[5], vert[4], min, max ) ||<br />

testEdge( vert[5], vert[6], min, max ) ||<br />

500 testEdge( vert[2], vert[1], min, max ) ||


testEdge( vert[2], vert[6], min, max ) ||<br />

testEdge( vert[2], vert[3], min, max ) ||<br />

testEdge( vert[7], vert[3], min, max ) ||<br />

testEdge( vert[7], vert[6], min, max ) ||<br />

505 testEdge( vert[7], vert[4], min, max ) );<br />

}


Erklärung<br />

1. Mir ist bekannt, dass die Diplomarbeit als Prüfungsleistung in das Eigentum des Freistaats <strong>Bayern</strong><br />

übergeht. Hiermit erkläre ich mein Einverständnis, dass die Fachhochschule Regensburg<br />

diese Prüfungsleistung die Studenten der Fachhochschule Regensburg einsehen lassen darf, <strong>und</strong><br />

dass sie die Abschlussarbeit unter Nennung meines Namens als Urheber veröffentlichen darf.<br />

2. Ich erkläre hiermit, dass ich diese Diplomarbeit selbständig verfasst noch nicht anderweitig für<br />

andere Prüfungszwecke vorgelegt, keine anderen als die angegebenen Quellen <strong>und</strong> Hilfsmittel<br />

benützt sowie wörtliche <strong>und</strong> sinngemäße Zitate als solche gekennzeichnet habe.<br />

Regensburg, den 17. September 2004<br />

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .<br />

Unterschrift<br />

211


Literaturverzeichnis<br />

[1] Barth W., Stürzlinger W., Efficient <strong>Ray</strong> <strong>Tracing</strong> for Bezier and B.Spline Surfaces, Computer<br />

& Graphics, Vol. 17, 1993<br />

[2] Glassner, A. S., An Introduction to <strong>Ray</strong> <strong>Tracing</strong>, Academic Press, 1989<br />

[3] Groth O., <strong>Texturierung</strong> in ein, zwei <strong>und</strong> drei Dimensionen für dreidimensionale Darstelllung.<br />

Diplomarbeit, Fachhochschule Regensburg, 1998<br />

[4] Hill F. S. Jr., Computer Graphics Using OpenGL, 2nd Edition, Prentice Hall, 2001<br />

[5] Lugauer U., Rendering of Curved Surfaces with the Lane-Carpenter Algorithm. Diplomarbeit,<br />

Fachhochschule Regensburg, 1998<br />

[6] McReynolds T., Blythe D., Programming with OpenGL: Advanced Rendering, SIG-<br />

GRAPH ’97<br />

[7] Riepl A., Ein Objektorientiertes, rekursives <strong>Ray</strong> <strong>Tracing</strong>-Verfahren mit verschiedenen<br />

Optimierungen. Diplomarbeit, Fachhochschule Regensburg, 1992<br />

[8] Watt A., M. Watt, Advanced Animation and Rendering Techniques, Adison-Wesley Publ.<br />

Co., 1992<br />

212


Abbildungsverzeichnis<br />

1.1 Gr<strong>und</strong>legende Funktionsweise des <strong>Ray</strong> <strong>Tracing</strong> . . . . . . . . . . . . . . . . 2<br />

1.2 Schnittpunkt- <strong>und</strong> Normalenberechnung bei primitiven Objekten . . . . . . . 6<br />

1.3 Die Transformation <strong>von</strong> Strahl <strong>und</strong> Normalenvektor . . . . . . . . . . . . . . 7<br />

2.1 Verlauf <strong>von</strong> Bernstein-Polynomen vom Grad 3 . . . . . . . . . . . . . . . . . 12<br />

2.2 Eigenschaften <strong>von</strong> Bézierkurven . . . . . . . . . . . . . . . . . . . . . . . . 13<br />

2.3 Geometrische Punktkonstruktion nach de Casteljau . . . . . . . . . . . . . . 15<br />

2.4 Berechnungsschema nach de Casteljau . . . . . . . . . . . . . . . . . . . . . 15<br />

2.5 Aufteilung einer kubischen Bézierkurve . . . . . . . . . . . . . . . . . . . . 16<br />

2.6 Bikubische Bézierfläche mit Kontrollpolyeder . . . . . . . . . . . . . . . . . 17<br />

2.7 Normalenberechnung bei Bézierflächen . . . . . . . . . . . . . . . . . . . . 19<br />

2.8 Unterteilung <strong>von</strong> Bézierflächen . . . . . . . . . . . . . . . . . . . . . . . . . 19<br />

2.9 Die Newton-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />

2.10 Problemfälle bei der Schnittpunktapproximation . . . . . . . . . . . . . . . . 21<br />

2.11 Flächenumhüllung durch Parallelepiped . . . . . . . . . . . . . . . . . . . . 23<br />

2.12 Bewertungskriterien für Bézierkurven . . . . . . . . . . . . . . . . . . . . . 24<br />

2.13 Binärbaum zur Teilflächenorganisation . . . . . . . . . . . . . . . . . . . . . 25<br />

2.14 Pseudocode zum Aufbau des Suchbaumes . . . . . . . . . . . . . . . . . . . 26<br />

2.15 Pseudocode zum rekursiven Durchlauf des Suchbaumes . . . . . . . . . . . . 27<br />

2.16 Annähernd tangential schneidender Strahl . . . . . . . . . . . . . . . . . . . 30<br />

2.17 Unzureichende Approximation durch Gr<strong>und</strong>fläche . . . . . . . . . . . . . . . 31<br />

2.18 Pseudocode des Iterationsablaufs . . . . . . . . . . . . . . . . . . . . . . . . 32<br />

2.19 Fehlerhafte Überschreitung der Flächengrenzen . . . . . . . . . . . . . . . . 33<br />

2.20 Ein berühmtes Beispiel: Der Utah Teapot . . . . . . . . . . . . . . . . . . . 34<br />

3.1 Integration eines <strong>Texturierung</strong>ssystems . . . . . . . . . . . . . . . . . . . . . 36<br />

3.2 Arten des Texture Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

3.3 Die Mercator-Projektion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38<br />

213


ABBILDUNGSVERZEICHNIS 214<br />

3.4 Environment Mapping bei Animationen . . . . . . . . . . . . . . . . . . . . 39<br />

3.5 Environment Mapping mit Kugel als Hüllkörper . . . . . . . . . . . . . . . . 39<br />

3.6 Grenzen des Environment Mapping . . . . . . . . . . . . . . . . . . . . . . 40<br />

3.7 Generierung <strong>von</strong> Rauschen . . . . . . . . . . . . . . . . . . . . . . . . . . . 42<br />

3.8 2D-Plot des generierten Rauschens . . . . . . . . . . . . . . . . . . . . . . . 43<br />

3.9 Texturbeispiele: Karo- <strong>und</strong> Klotz-Textur . . . . . . . . . . . . . . . . . . . . 44<br />

3.10 Generierung der Marmor-Textur . . . . . . . . . . . . . . . . . . . . . . . . 45<br />

3.11 Generierung der Holz-Textur . . . . . . . . . . . . . . . . . . . . . . . . . . 46<br />

3.12 Perturbation der Flächennormale . . . . . . . . . . . . . . . . . . . . . . . . 47<br />

3.13 Texturbeispiel: Bump Mapping . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

3.14 Grenzen des Bump Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . 49<br />

4.1 Die Basisklasse der Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . 55<br />

4.2 Die Spitze der Textur-Klassenhierarchie . . . . . . . . . . . . . . . . . . . . 57<br />

A.1 Ablauf des <strong>Ray</strong> <strong>Tracing</strong>-Prozesses . . . . . . . . . . . . . . . . . . . . . . . 62<br />

A.2 Die Klassenhierarchie der Objekte . . . . . . . . . . . . . . . . . . . . . . . 63<br />

A.3 Die Klassenhierarchie des <strong>Texturierung</strong>ssystems . . . . . . . . . . . . . . . . 64<br />

A.4 Die Klassenhierarchie der Bildformate . . . . . . . . . . . . . . . . . . . . . 65<br />

C.1 Fertig erstelltes Bild der Beispiel-Szene . . . . . . . . . . . . . . . . . . . . 72

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

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!