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
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 : ∅<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